001package jmri.jmrit.logix;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Component;
006import java.awt.Dimension;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.awt.event.FocusEvent;
010import java.awt.event.FocusListener;
011import java.awt.event.MouseEvent;
012import java.beans.PropertyChangeListener;
013import java.util.ArrayList;
014import java.util.List;
015import java.util.Map;
016
017import javax.annotation.CheckForNull;
018import javax.annotation.Nonnull;
019
020import javax.swing.AbstractButton;
021import javax.swing.BorderFactory;
022import javax.swing.Box;
023import javax.swing.BoxLayout;
024import javax.swing.ButtonGroup;
025import javax.swing.JButton;
026import javax.swing.JComboBox;
027import javax.swing.JComponent;
028import javax.swing.JDialog;
029import javax.swing.JFrame;
030import javax.swing.JLabel;
031import javax.swing.JPanel;
032import javax.swing.JRadioButton;
033import javax.swing.JScrollPane;
034import javax.swing.JTable;
035import javax.swing.JTextField;
036import javax.swing.table.AbstractTableModel;
037import javax.swing.tree.DefaultMutableTreeNode;
038import javax.swing.tree.DefaultTreeModel;
039import javax.swing.tree.TreeNode;
040
041import jmri.InstanceManager;
042import jmri.Path;
043import jmri.implementation.SignalSpeedMap;
044import jmri.jmrit.picker.PickListModel;
045import jmri.jmrit.roster.Roster;
046import jmri.jmrit.roster.RosterEntry;
047import jmri.jmrit.roster.RosterSpeedProfile;
048import jmri.util.JmriJFrame;
049import jmri.util.swing.JmriJOptionPane;
050
051/**
052 * Make panels for WarrantFrame and NXFrame windows that create and edit
053 * Warrants.
054 * <p>
055 * Input panels for defining a train's route from an eNtry OBlock to an eXit
056 * OBlock. Routes are defined by choosing the originating block, the path on
057 * which the train start and the exit Portal through which it will leave the
058 * block. Also it is required that a Destination block is chosen and the path
059 * and Portal through which the train will arrive. The Portal selections
060 * establish the direction information. Optionally, additional blocks can be
061 * specified requiring the train to pass through or avoid entering.
062 * <p>
063 * Input panels to describe the train. accesses the roster for some info.
064 *
065 * @author Peter Cressman
066 *
067 */
068abstract class WarrantRoute extends jmri.util.JmriJFrame implements ActionListener, PropertyChangeListener {
069
070    enum Location {
071        ORIGIN, DEST, VIA, AVOID
072    }
073
074    enum Display {
075        MPH("mph"), KPH("kph"), MMPS("mmps"), INPS("inps"), IN("in"), CM("cm");
076        String _bundleKey;
077        Display(String bundleName) {
078            _bundleKey = bundleName;
079        }
080        @Override
081        public String toString() {
082            return Bundle.getMessage(_bundleKey);
083        }
084    }
085
086    static class DisplayButton extends JButton {
087        private Display pref;
088        DisplayButton(Display p) {
089            super();
090            DisplayButton.this.setDisplayPref(p);
091        }
092        void setDisplayPref(Display p) {
093            pref = p;
094            setText(p.toString());
095        }
096        Display getDisplyPref() {
097            return pref;
098        }
099    }
100
101    protected RouteLocation _origin = new RouteLocation(Location.ORIGIN);
102    protected RouteLocation _destination = new RouteLocation(Location.DEST);
103    protected RouteLocation _via = new RouteLocation(Location.VIA);
104    protected RouteLocation _avoid = new RouteLocation(Location.AVOID);
105    protected RouteLocation _focusedField;
106
107    protected SpeedUtil _speedUtil;
108    protected Display _displayPref; // speed units preference
109    protected Display _units;       // distance units preference
110    protected float _scale = 87.1f;
111
112    static int STRUT_SIZE = 10;
113    private int _depth = 20;
114
115    static String PAD = "               ";
116    private JDialog _pickRouteDialog;
117    private final RouteTableModel _routeModel;
118    protected ArrayList<BlockOrder> _orders;
119    private JFrame _debugFrame;
120    private RouteFinder _routeFinder;
121    private final JTextField _searchDepth = new JTextField(5);
122    private JButton _calculateButton = new JButton(Bundle.getMessage("Calculate"));
123
124    private final JComboBox<String> _rosterBox = new JComboBox<>();
125    private final AddressTextField _dccNumBox = new AddressTextField();
126    private final JTextField _trainNameBox = new JTextField(6);
127    private final JButton _viewProfile = new JButton(Bundle.getMessage("ViewProfile"));
128    private JmriJFrame _spTable = null;
129    private JmriJFrame _pickListFrame;
130
131
132    /**
133     * Only subclasses can create this
134     */
135    protected WarrantRoute() {
136        super(false, true);
137        log.debug("newInstance");
138        _searchDepth.setText(Integer.toString(_depth));
139        _routeModel = new RouteTableModel();
140        _speedUtil = new SpeedUtil();
141
142        int interpretation = SignalSpeedMap.SPEED_KMPH;
143        WarrantPreferences wp = WarrantPreferences.getDefault();
144        if (wp != null) {
145            interpretation = WarrantPreferences.getDefault().getInterpretation();
146            _scale = wp.getLayoutScale();
147        }
148        switch (interpretation) {
149            case SignalSpeedMap.SPEED_MPH:
150                _displayPref = Display.MPH;
151                _units = Display.IN;
152                break;
153            case SignalSpeedMap.SPEED_KMPH:
154                _displayPref = Display.KPH;
155                _units = Display.CM;
156                break;
157            default:
158                _displayPref = Display.INPS;
159                _units = Display.IN;
160                break;
161        }
162        setupRoster();
163    }
164
165    protected abstract void selectedRoute(ArrayList<BlockOrder> orders);
166    protected abstract void maxThrottleEventAction();
167
168    @Override
169    public abstract void propertyChange(java.beans.PropertyChangeEvent e);
170
171    protected void setSpeedUtil(SpeedUtil sp) {
172        _speedUtil = sp;
173    }
174
175    static class AddressTextField extends JTextField implements FocusListener {
176        public AddressTextField() {
177            super();
178            addFocusListener(AddressTextField.this);
179        }
180        @Override
181        public void focusGained(FocusEvent e) {
182
183        }
184        @Override
185        public void focusLost(FocusEvent e) {
186            fireActionPerformed();
187        }
188    }
189
190    /* ************************* Panel for Route search depth **********************/
191    /**
192     * @return How many nodes deep the tree search should be
193     */
194    private int getDepth() {
195        try {
196            int i = Integer.parseInt(_searchDepth.getText());
197            if (i > 2 ) {
198                _depth = i;
199            }
200        } catch (NumberFormatException nfe) {
201            // ignore
202        }
203        return _depth;
204    }
205
206    protected JPanel searchDepthPanel(boolean vertical) {
207        _searchDepth.setText(Integer.toString(_depth));
208        JPanel p = new JPanel();
209        p.add(Box.createHorizontalGlue());
210        p.add(makeTextBoxPanel(vertical, _searchDepth, "SearchDepth", "ToolTipSearchDepth"));
211        _searchDepth.setColumns(5);
212        p.add(Box.createHorizontalGlue());
213        return p;
214    }
215
216    protected JPanel calculatePanel(boolean vertical) {
217        _calculateButton.setMaximumSize(_calculateButton.getPreferredSize());
218        _calculateButton.addActionListener(new ActionListener() {
219            @Override
220            public void actionPerformed(ActionEvent e) {
221                clearTempWarrant();
222                calculate();
223            }
224        });
225
226        JButton stopButton = new JButton(Bundle.getMessage("Stop"));
227        stopButton.addActionListener(new ActionListener() {
228            @Override
229            public void actionPerformed(ActionEvent e) {
230                stopRouteFinder();
231            }
232        });
233
234        JPanel panel = new JPanel();
235        panel.add(makeTextBoxPanel(vertical, _calculateButton, "CalculateRoute", null));
236        panel.add(makeTextBoxPanel(vertical, stopButton, "StopSearch", null));
237        return panel;
238    }
239    public JPanel makePickListPanel() {
240        JButton button = new JButton(Bundle.getMessage("MenuBlockPicker"));
241        button.setMaximumSize(_calculateButton.getPreferredSize());
242        button.addActionListener(new ActionListener() {
243            @Override
244            public void actionPerformed(ActionEvent e) {
245                if (_pickListFrame !=null) {
246                    _pickListFrame.dispose();
247                }
248                _pickListFrame = new JmriJFrame();
249                PickListModel<OBlock> model = PickListModel.oBlockPickModelInstance();
250                _pickListFrame.add(new JScrollPane(model.makePickTable()));
251                _pickListFrame.pack();
252                _pickListFrame.setVisible(true);
253            }
254        });
255        JPanel p = new JPanel();
256        p.add(button);
257        return p;
258    }
259
260
261    /* ************************* Train ID info: Loco Address, etc **********************/
262    /**
263     * Make panel containing TextFields for Train name and address and ComboBox
264     * for Roster entries. called from:
265     * WarrantFrame.makeBorderedTrainPanel() at init of WarrantFrame
266     * NXFrame.makeAutoRunPanel() at init of NXFrame
267     *
268     *
269     * @param comp optional panel to add
270     * @return panel
271     */
272    protected JPanel makeTrainIdPanel(JPanel comp) {
273        JPanel trainPanel = new JPanel();
274        trainPanel.setLayout(new BoxLayout(trainPanel, BoxLayout.LINE_AXIS));
275        trainPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
276
277        JPanel panel = new JPanel();
278        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
279        panel.add(makeTextBoxPanel(false, _trainNameBox, "TrainName", "noTrainName"));
280        panel.add(makeTextBoxPanel(false, _rosterBox, "Roster", null));
281        panel.add(Box.createVerticalStrut(2));
282        panel.add(makeTextBoxPanel(false, _dccNumBox, "DccAddress", null));
283        _dccNumBox.addActionListener((ActionEvent e) -> {
284            checkAddress();
285        });
286
287        JPanel p = new JPanel();
288        p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
289        p.add(_viewProfile);
290        _viewProfile.addActionListener((ActionEvent e) -> {
291            showProfile();
292        });
293        panel.add(p);
294        if (comp != null) {
295            panel.add(comp);
296        }
297        trainPanel.add(panel);
298        trainPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
299
300        return trainPanel;
301    }
302
303    private void setupRoster() {
304        List<RosterEntry> list = Roster.getDefault().matchingList(null, null, null, null, null, null, null);
305        _rosterBox.setRenderer(new jmri.jmrit.roster.swing.RosterEntryListCellRenderer());
306        _rosterBox.addItem(" ");
307        _rosterBox.addItem(Bundle.getMessage("noSuchAddress"));
308        for (int i = 0; i < list.size(); i++) {
309            RosterEntry r = list.get(i);
310            _rosterBox.addItem(r.titleString());
311        }
312        _rosterBox.setMaximumSize(_rosterBox.getPreferredSize());
313        _rosterBox.addActionListener((ActionEvent e) -> {
314            checkAddress();
315        });
316    }
317
318    private void showProfile() {
319        closeProfileTable();
320
321        String id = _speedUtil.getRosterId();
322        if (id == null || id.isEmpty()) {
323            return;
324        }
325        if (Roster.getDefault().getEntryForId(id) == null) {
326            String rosterId = JmriJOptionPane.showInputDialog(this,
327                    Bundle.getMessage("makeRosterEntry", _speedUtil.getAddress()),
328                    Bundle.getMessage("QuestionTitle"),
329                    JmriJOptionPane.QUESTION_MESSAGE);
330            log.debug("Create roster entry {}", rosterId);
331            if (rosterId == null || rosterId.isEmpty()) {
332                rosterId = id;
333            }
334            RosterEntry rosterEntry = _speedUtil.makeRosterEntry(rosterId);
335            if (rosterEntry == null) {
336                return;
337            }
338            Roster.getDefault().addEntry(rosterEntry);
339            WarrantManager mgr = InstanceManager.getDefault(WarrantManager.class);
340            RosterSpeedProfile mergeProfile = _speedUtil.getMergeProfile();
341            mgr.setMergeProfile(rosterId, mergeProfile);
342            mgr.getMergeProfiles().remove(id);
343            _speedUtil.setRosterId(rosterId);
344            id = rosterId;
345        }
346
347        JPanel viewPanel = makeViewPanel(id);
348        if (viewPanel == null) {
349            return;
350        }
351        _spTable = new JmriJFrame(false, true);
352        JPanel framePanel = new JPanel();
353        framePanel.setLayout(new BoxLayout(framePanel, BoxLayout.PAGE_AXIS));
354        framePanel.add(Box.createGlue());
355
356        framePanel.add(viewPanel);
357        _spTable.getContentPane().add(framePanel);
358        _spTable.pack();
359        _spTable.setVisible(true);
360    }
361
362    private JPanel makeViewPanel(String id) {
363        RosterSpeedProfile speedProfile = _speedUtil.getMergeProfile();
364        RosterEntry re = Roster.getDefault().getEntryForId(id);
365        RosterSpeedProfile rosterSpeedProfile;
366        if (re != null) {
367            rosterSpeedProfile = re.getSpeedProfile();
368            if (rosterSpeedProfile == null) {
369                rosterSpeedProfile = new RosterSpeedProfile(re);
370                re.setSpeedProfile(rosterSpeedProfile);
371            }
372        } else {
373            rosterSpeedProfile = null;
374        }
375        if ((speedProfile == null || speedProfile.getProfileSize() == 0) &&
376                (rosterSpeedProfile == null || rosterSpeedProfile.getProfileSize() == 0)) {
377            _viewProfile.setEnabled(false);
378            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("NoSpeedProfile", id),
379                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
380            return null;
381        } else {
382            _viewProfile.setEnabled(true);
383        }
384        JPanel viewPanel = new JPanel();
385        viewPanel.setLayout(new BoxLayout(viewPanel, BoxLayout.PAGE_AXIS));
386        viewPanel.add(Box.createGlue());
387        JPanel panel = new JPanel();
388        panel.add(MergePrompt.makeEditInfoPanel(re));
389        viewPanel.add(panel);
390
391        JPanel spPanel = new JPanel();
392        spPanel.setLayout(new BoxLayout(spPanel, BoxLayout.LINE_AXIS));
393        spPanel.add(Box.createGlue());
394
395        if (rosterSpeedProfile != null ) {
396            Map<Integer, Boolean> anomilies = MergePrompt.validateSpeedProfile(rosterSpeedProfile);
397            spPanel.add(MergePrompt.makeSpeedProfilePanel("rosterSpeedProfile", rosterSpeedProfile,  false, anomilies));
398            spPanel.add(Box.createGlue());
399        }
400        if (speedProfile != null) {
401            Map<Integer, Boolean> anomaly = MergePrompt.validateSpeedProfile(speedProfile);
402            spPanel.add(MergePrompt.makeSpeedProfilePanel("mergedSpeedProfile", speedProfile, true, anomaly));
403            spPanel.add(Box.createGlue());
404        }
405
406        viewPanel.add(spPanel);
407        return viewPanel;
408    }
409
410
411    protected void closeProfileTable() {
412        if (_spTable != null) {
413            String id = _speedUtil.getRosterId();
414            if (id != null) {
415                RosterSpeedProfile speedProfile = _speedUtil.getMergeProfile();
416                InstanceManager.getDefault(WarrantManager.class).setMergeProfile(id, speedProfile);
417            }
418            _spTable.dispose();
419            _spTable = null;
420        }
421    }
422
423    // called by WarrantFrame.setup()
424    protected String setTrainInfo(String name) {
425        if (log.isDebugEnabled()) {
426            log.debug("setTrainInfo for: {}", name);
427        }
428        setTrainName(name);
429        _dccNumBox.setText(_speedUtil.getAddress());
430        setRosterBox();
431        if (name == null) {
432            RosterEntry re = _speedUtil.getRosterEntry();
433            if (re != null) {
434                setTrainName(re.getRoadNumber());
435                setRosterBox();
436            } else {
437                setTrainName(_speedUtil.getAddress());
438            }
439        }
440        return null;
441    }
442
443    private void setRosterBox() {
444        String id = _speedUtil.getRosterId();
445        if (id != null && id.equals(_rosterBox.getSelectedItem())) {
446            return;
447        }
448        if (id != null && id.charAt(0) != '$' && id.charAt(id.length()-1) !='$') {
449            _rosterBox.setSelectedItem(id);
450        } else {
451            _rosterBox.setSelectedItem(Bundle.getMessage("noSuchAddress"));
452        }
453    }
454
455    protected void setTrainName(String name) {
456        _trainNameBox.setText(name);
457    }
458
459    protected String getTrainName() {
460        String trainName = _trainNameBox.getText();
461        if (trainName == null || trainName.length() == 0) {
462            trainName = _dccNumBox.getText();
463        }
464        return trainName;
465    }
466
467    private void checkAddress() {
468        String msg = setAddress();
469        if (msg != null) {
470            JmriJOptionPane.showMessageDialog(this, msg,
471                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
472        }
473    }
474
475    /**
476     * Called to make final consistency check on loco address before running warrant
477     * @return error message
478     */
479    protected String setAddress() {
480        String msg = null;
481        String suAddr = _speedUtil.getAddress();
482        String addrText = _dccNumBox.getText();
483        String suId = _speedUtil.getRosterId();
484        boolean textChange = false;
485        if ( !addrText.equals(suAddr) || suId == null) {
486            textChange = true;
487            if (!_speedUtil.setAddress(_dccNumBox.getText())) {
488                msg = Bundle.getMessage("BadDccAddress", _dccNumBox.getText());
489            } else {   // else address OK.
490                suAddr = _speedUtil.getAddress();
491                _dccNumBox.setText(suAddr);  // add protocol string
492                suId = _speedUtil.getRosterId();
493                maxThrottleEventAction();
494                if (suId != null && !(suId.charAt(0) == '$' && suId.charAt(suId.length()-1) =='$')) {
495                    _rosterBox.setSelectedItem(suId);
496                } else {
497                    _rosterBox.setSelectedItem(Bundle.getMessage("noSuchAddress"));
498                    return null;
499                }
500            }
501        }
502
503        String id = (String)_rosterBox.getSelectedItem();
504        RosterEntry re = Roster.getDefault().getEntryForId(id);
505        boolean isRoster = (re != null);
506        suId = _speedUtil.getRosterId();
507        if (suId != null && suId.charAt(0) == '$' && suId.charAt(suId.length()-1) =='$') {
508            isRoster = true;
509        }
510        if (!textChange && !isRoster) {
511            _dccNumBox.setText(null);
512            return null;
513        }
514        if (re != null) {
515           if (!re.getDccLocoAddress().equals(_speedUtil.getDccAddress())) {
516               _speedUtil.setRosterId(id);
517           }
518           _dccNumBox.setText(re.getDccLocoAddress().toString());
519           maxThrottleEventAction();
520           msg = null;
521        } else if (msg == null) {
522            _rosterBox.setSelectedItem(Bundle.getMessage("noSuchAddress"));
523        }
524        return msg;
525    }
526
527    protected String getAddress() {
528        return _dccNumBox.getText();
529    }
530
531    protected String checkLocoAddress() {
532        if (_speedUtil.getDccAddress() == null) {
533            return Bundle.getMessage("BadDccAddress", _dccNumBox.getText());
534        }
535        return null;
536    }
537
538    protected void calculate() {
539        String msg = findRoute();
540        if (msg != null) {
541            JmriJOptionPane.showMessageDialog(this, msg,
542                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
543        }
544    }
545
546    /* ****************************** route info *******************/
547    /**
548     * Does the action on each of the 4 RouteLocation panels
549     *
550     * @param e the action event
551     */
552    @Override
553    public void actionPerformed(ActionEvent e) {
554        Object obj = e.getSource();
555        if (log.isTraceEnabled()) {
556            log.trace("actionPerformed: source {} id= {}, ActionCommand= {}", 
557                ((Component) obj).getName(), e.getID(), e.getActionCommand());
558        }
559        doAction(obj);
560    }
561
562    @SuppressWarnings("unchecked") // parameter can be any of several types, including JComboBox<String>
563    void doAction(Object obj) {
564        if (obj instanceof JTextField) {
565            JTextField box = (JTextField) obj;
566            if ( !_origin.checkBlockBox(box) && !_destination.checkBlockBox(box) && !_via.checkBlockBox(box) ) {
567                    _avoid.checkBlockBox(box);
568            }
569        } else {
570            JComboBox<String> box = (JComboBox<String>) obj;
571            if (!_origin.checkPathBox(box) && !_destination.checkPathBox(box) && !_via.checkPathBox(box)) {
572                if (!_avoid.checkPathBox(box)) {
573                    if (_origin.checkPortalBox(box)) {
574                        _origin.setOrderExitPortal();
575                    }
576                    if (_destination.checkPortalBox(box)) {
577                        _destination.setOrderEntryPortal();
578                    }
579                }
580            }
581        }
582    }
583
584    protected JPanel makeBlockPanels(boolean add) {
585        JPanel panel = new JPanel();
586        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
587
588        JPanel oPanel = _origin.makePanel("OriginBlock", "OriginToolTip", "PathName", "ExitPortalName", this);
589        panel.add(oPanel);
590
591        oPanel = _destination.makePanel("DestBlock", "DestToolTip", "PathName", "EntryPortalName", this);
592        panel.add(oPanel);
593
594        oPanel = _via.makePanel("ViaBlock", "ViaToolTip", "PathName", null, this);
595
596        JPanel aPanel = _avoid.makePanel("AvoidBlock", "AvoidToolTip", "PathName", null, this);
597
598        if (add) {
599            JPanel pLeft = new JPanel();
600            pLeft.setLayout(new BoxLayout(pLeft, BoxLayout.PAGE_AXIS));
601            pLeft.add(oPanel);
602            pLeft.add(aPanel);
603
604            JPanel pRight = new JPanel();
605            pRight.setLayout(new BoxLayout(pRight, BoxLayout.PAGE_AXIS));
606            pRight.add(searchDepthPanel(true));
607            pRight.add(makePickListPanel());
608            pRight.add(calculatePanel(true));
609
610            JPanel p = new JPanel();
611            p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
612            p.add(pLeft);
613            p.add(pRight);
614            panel.add(p);
615        } else {
616            panel.add(oPanel);
617            panel.add(aPanel);
618        }
619        return panel;
620    }
621
622    private JPanel makeLabelCombo(String title, JComboBox<String> box, String tooltip) {
623
624        JPanel p = new JPanel();
625        p.setLayout(new BorderLayout());
626        p.setToolTipText(Bundle.getMessage(tooltip));
627        box.setToolTipText(Bundle.getMessage(tooltip));
628        JLabel l = new JLabel(PAD + Bundle.getMessage(title) + PAD);
629        p.add(l, BorderLayout.NORTH);
630        l.setLabelFor(box);
631        p.add(box, BorderLayout.CENTER);
632        box.setBackground(Color.white);
633        box.addActionListener(this);
634        box.setAlignmentX(JComponent.CENTER_ALIGNMENT);
635        return p;
636    }
637
638    private boolean setOriginBlock() {
639        return _origin.setBlock();
640    }
641
642    private boolean setDestinationBlock() {
643        return _destination.setBlock();
644    }
645
646    private boolean setViaBlock() {
647        return _via.setBlock();
648    }
649
650    private boolean setAvoidBlock() {
651        return _avoid.setBlock();
652    }
653
654    /* ********** route blocks **************************/
655    protected class RouteLocation extends java.awt.event.MouseAdapter {
656
657        Location location;
658        private BlockOrder order;
659        JTextField blockBox = new JTextField();
660        private final JComboBox<String> pathBox = new JComboBox<>();
661        JComboBox<String> portalBox;
662
663        RouteLocation(Location loc) {
664            location = loc;
665            if (location == Location.ORIGIN || location == Location.DEST) {
666                portalBox = new JComboBox<>();
667            }
668        }
669
670        private JPanel makePanel(String title, String tooltip, String box1Name, String box2Name, WarrantRoute parent) {
671            JPanel oPanel = new JPanel();
672            oPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
673                    Bundle.getMessage(title),
674                    javax.swing.border.TitledBorder.CENTER,
675                    javax.swing.border.TitledBorder.TOP));
676            JPanel hPanel = new JPanel();
677            hPanel.setLayout(new BoxLayout(hPanel, BoxLayout.LINE_AXIS));
678            hPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
679            hPanel.add(makeBlockBox(tooltip));
680            hPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
681            JPanel pPanel = new JPanel();
682            pPanel.setLayout(new BoxLayout(pPanel, BoxLayout.LINE_AXIS));
683            pPanel.add(makeLabelCombo(box1Name, pathBox, tooltip));
684            pPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
685
686            if (box2Name != null) {
687                pPanel.add(makeLabelCombo(box2Name, portalBox, tooltip));
688                pPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
689            }
690            hPanel.add(pPanel);
691            oPanel.add(hPanel);
692            pPanel.setToolTipText(Bundle.getMessage(tooltip));
693            hPanel.setToolTipText(Bundle.getMessage(tooltip));
694            oPanel.setToolTipText(Bundle.getMessage(tooltip));
695
696            blockBox.addActionListener(parent);
697            blockBox.addPropertyChangeListener(parent);
698            blockBox.addMouseListener(this);
699
700            return oPanel;
701        }
702
703        private JPanel makeBlockBox(String tooltip) {
704            blockBox.setDragEnabled(true);
705            blockBox.setTransferHandler(new jmri.util.DnDStringImportHandler());
706            blockBox.setColumns(20);
707            blockBox.setAlignmentX(Component.CENTER_ALIGNMENT);
708            JPanel p = new JPanel();
709            p.setLayout(new BorderLayout());
710            p.setToolTipText(Bundle.getMessage(tooltip));
711            blockBox.setToolTipText(Bundle.getMessage(tooltip));
712            JLabel l = new JLabel(Bundle.getMessage("BlockName"));
713            p.add(l, BorderLayout.NORTH);
714            l.setLabelFor(blockBox);
715            p.add(blockBox, BorderLayout.CENTER);
716            return p;
717        }
718
719        private void clearFields() {
720            setBlock(null);
721        }
722
723        private boolean checkBlockBox(JTextField box) {
724            if (box == blockBox) {
725                setBlock(getEndPointBlock());
726                return true;
727            }
728            return false;
729        }
730
731        private boolean checkPathBox(JComboBox<String> box) {
732            if (box == pathBox) {
733                if (portalBox != null) {
734                    setPortalBox(order);
735                }
736                return true;
737            }
738            return false;
739        }
740
741        private boolean checkPortalBox(JComboBox<String> box) {
742            return (box == portalBox);
743        }
744
745        private void setOrderEntryPortal() {
746            if (order != null) {
747                order.setEntryName((String) portalBox.getSelectedItem());
748            }
749        }
750
751        private void setOrderExitPortal() {
752            if (order != null) {
753                order.setExitName((String) portalBox.getSelectedItem());
754            }
755        }
756
757        protected void setOrder(BlockOrder o) {
758            if (o != null) {
759                // setting blockBox text triggers doAction, so allow that to finish
760                order = new BlockOrder(o);
761                OBlock block = o.getBlock();
762                blockBox.setText(block.getDisplayName());
763                setPathBox(block);
764                setPathName(o.getPathName());
765                setPortalBox(o);
766                if (location == Location.DEST) {
767                    setPortalName(o.getEntryName());
768                } else if (location == Location.ORIGIN) {
769                    setPortalName(o.getExitName());
770                }
771            }
772        }
773
774        protected BlockOrder getOrder() {
775            return order;
776        }
777
778        private void setPortalName(String name) {
779            portalBox.setSelectedItem(name);
780        }
781
782        private void setPathName(String name) {
783            pathBox.setSelectedItem(name);
784        }
785
786        protected String getBlockName() {
787            return blockBox.getText();
788        }
789
790        private OBlock getEndPointBlock() {
791            String text = blockBox.getText();
792            int idx = text.indexOf(java.awt.event.KeyEvent.VK_TAB);
793            if (idx > 0) {
794                if (idx + 1 < text.length()) {
795                    text = text.substring(idx + 1);
796                } else {
797                    text = text.substring(0, idx);
798                }
799            }
800            blockBox.setText(text);
801            OBlock block = InstanceManager.getDefault(OBlockManager.class).getOBlock(text);
802            if (block == null && text.length() > 0) {
803                JmriJOptionPane.showMessageDialog(blockBox.getParent(), Bundle.getMessage("BlockNotFound", text),
804                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
805            }
806            return block;
807        }
808
809        private boolean setBlock() {
810            return setBlock(getEndPointBlock());
811        }
812
813        private boolean setBlock(OBlock block) {
814            boolean result = true;
815            if (block == null) {
816                result = false;
817                order = null;
818            } else {
819                if (order != null && block == order.getBlock()
820                        && pathIsValid(block, order.getPathName()) == null) {
821                    result = true;
822                } else {
823                    if (pathsAreValid(block)) {
824                        order = new BlockOrder(block);
825                        if (!setPathBox(block)) {
826                            result = false;
827                        } else {
828                            setPortalBox(order);
829                        }
830                    } else {
831                        result = false;
832                    }
833                }
834            }
835            if (result) {
836                // block cannot be null here. it is protected by result==true
837                if (block != null) {
838                    blockBox.setText(block.getDisplayName());
839                }
840                order.setPathName((String) pathBox.getSelectedItem());
841                if (location == Location.DEST) {
842                    order.setEntryName((String) portalBox.getSelectedItem());
843                } else if (location == Location.ORIGIN) {
844                    order.setExitName((String) portalBox.getSelectedItem());
845                }
846                setNextLocation();
847            } else {
848                blockBox.setText(null);
849                pathBox.removeAllItems();
850                if (portalBox != null) {
851                    portalBox.removeAllItems();
852                }
853            }
854            return result;
855        }
856
857        private boolean setPathBox(OBlock block) {
858            pathBox.removeAllItems();
859            if (portalBox != null) {
860                portalBox.removeAllItems();
861            }
862            if (block == null) {
863                return false;
864            }
865            List<Path> list = block.getPaths();
866            if (list.isEmpty()) {
867                JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoPaths", block.getDisplayName()),
868                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
869                return false;
870            }
871            for (int i = 0; i < list.size(); i++) {
872                pathBox.addItem(((OPath) list.get(i)).getName());
873            }
874            return true;
875        }
876
877        private void setPortalBox(@CheckForNull BlockOrder order) {
878            if (portalBox == null) {
879                return;
880            }
881            portalBox.removeAllItems();
882            if (order == null) {
883                return;
884            }
885            String pathName = (String) pathBox.getSelectedItem();
886            order.setPathName(pathName);
887            OPath path = order.getPath();
888            if (path != null) {
889                Portal portal = path.getFromPortal();
890                if (portal != null) {
891                    String name = portal.getName();
892                    if (name != null) {
893                        portalBox.addItem(name);
894                    }
895                }
896                portal = path.getToPortal();
897                if (portal != null) {
898                    String name = portal.getName();
899                    if (name != null) {
900                        portalBox.addItem(name);
901                    }
902                }
903                if (log.isTraceEnabled()) {
904                    log.debug("setPortalBox: Path {} set in block {}", path.getName(), order.getBlock().getDisplayName());
905                }
906            } else {
907                if (log.isDebugEnabled()) {
908                    log.debug("setPortalBox: Path {} not found in block {}", pathName, order.getBlock().getDisplayName());
909                }
910                order.setPathName(null);
911            }
912        }
913
914        private void setNextLocation() {
915            switch (location) {
916                case ORIGIN:
917                    _focusedField = _destination;
918                    break;
919                case DEST:
920                    _focusedField = _via;
921                    break;
922                case VIA:
923                    _focusedField = _avoid;
924                    break;
925                case AVOID:
926                    _focusedField = _origin;
927                    break;
928                default:
929                    log.warn("Unhandled next location code: {}", location);
930                    break;
931            }
932        }
933
934        @Override
935        public void mouseClicked(MouseEvent e) {
936            _focusedField = this;
937        }
938        @Override
939        public void mousePressed(MouseEvent e) {
940            _focusedField = this;
941        }
942    }       // end RouteLocation
943
944    protected void mouseClickedOnBlock(OBlock block) {
945        if (_focusedField != null) {
946            _focusedField.setBlock(block);
947        } else {
948            _origin.setBlock(block);
949        }
950    }
951
952    boolean pathsAreValid(@Nonnull OBlock block) {
953        List<Path> list = block.getPaths();
954        if (list.isEmpty()) {
955            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("NoPaths", block.getDisplayName()),
956                Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
957            return false;
958        }
959        for (int i = 0; i < list.size(); i++) {
960            OPath path = (OPath) list.get(i);
961            if (path.getFromPortal() == null && path.getToPortal() == null) {
962                JmriJOptionPane.showMessageDialog(this, 
963                    Bundle.getMessage("PathNeedsPortal", path.getName(), block.getDisplayName()),
964                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
965                return false;
966            }
967        }
968        return true;
969    }
970
971    /* ****************************** Finding the route ********************************/
972    /**
973     * Gather parameters to search for a route
974     *
975     * @return Error message, if any
976     */
977    private String findRoute() {
978        // read and verify origin and destination blocks/paths/portals
979        String msg;
980        BlockOrder order;
981        String pathName;
982        if (setOriginBlock()) {
983            order = _origin.getOrder();
984            pathName = order.getPathName();
985            if (pathName != null) {
986                if (order.getExitName() == null) {
987                    msg = Bundle.getMessage("SetExitPortal", Bundle.getMessage("OriginBlock"));
988                } else {
989                    msg = pathIsValid(order.getBlock(), pathName);
990                }
991            } else {
992                msg = Bundle.getMessage("SetPath", Bundle.getMessage("OriginBlock"));
993            }
994        } else {
995            msg = Bundle.getMessage("SetEndPoint", Bundle.getMessage("OriginBlock"));
996        }
997        if (msg == null) {
998            if (setDestinationBlock()) {
999                order = _destination.getOrder();
1000                pathName = order.getPathName();
1001                if (pathName != null) {
1002                    if (order.getEntryName() == null) {
1003                        msg = Bundle.getMessage("SetEntryPortal", Bundle.getMessage("DestBlock"));
1004                    } else {
1005                        msg = pathIsValid(order.getBlock(), pathName);
1006                    }
1007                } else {
1008                    msg = Bundle.getMessage("SetPath", Bundle.getMessage("DestBlock"));
1009                }
1010            } else {
1011                msg = Bundle.getMessage("SetEndPoint", Bundle.getMessage("DestBlock"));
1012            }
1013        }
1014        if (msg == null) {
1015            if (setViaBlock()) {
1016                order = _via.getOrder();
1017                if (order != null && order.getPathName() == null) {
1018                    msg = Bundle.getMessage("SetPath", Bundle.getMessage("ViaBlock"));
1019                }
1020            }
1021        }
1022        if (msg == null) {
1023            if (setAvoidBlock()) {
1024                order = _avoid.getOrder();
1025                if (order != null && order.getPathName() == null) {
1026                    msg = Bundle.getMessage("SetPath", Bundle.getMessage("AvoidBlock"));
1027                }
1028            }
1029        }
1030        if (msg == null) {
1031            log.debug("Params OK. findRoute() is creating a RouteFinder");
1032            _routeFinder = new RouteFinder(this, _origin.getOrder(), _destination.getOrder(),
1033                    _via.getOrder(), _avoid.getOrder(), getDepth());
1034            jmri.util.ThreadingUtil.newThread(_routeFinder).start();
1035        }
1036        return msg;
1037    }
1038
1039    protected void stopRouteFinder() {
1040        if (_routeFinder != null) {
1041            _routeFinder.quit();
1042            _routeFinder = null;
1043        }
1044    }
1045
1046    /* *********************************** Route Selection **************************************/
1047    protected List<BlockOrder> getOrders() {
1048        return _orders;
1049    }
1050
1051    protected BlockOrder getViaBlockOrder() {
1052        return _via.getOrder();
1053    }
1054
1055    protected BlockOrder getAvoidBlockOrder() {
1056        return _avoid.getOrder();
1057    }
1058
1059    private Warrant _tempWarrant;   // only used in pickRoute() method
1060
1061    protected void clearTempWarrant() {
1062        if (_tempWarrant != null) {
1063            _tempWarrant.deAllocate();
1064        }
1065    }
1066
1067    private void showTempWarrant(ArrayList<BlockOrder> orders) {
1068        String s = ("" + Math.random()).substring(4);
1069        if (_tempWarrant == null) {
1070            _tempWarrant = new Warrant("IW" + s + "TEMP", null);
1071            _tempWarrant.setBlockOrders(orders);
1072        }
1073        _tempWarrant.setRoute(true, orders);
1074        // Don't clutter with message - this is a temp display
1075    }
1076
1077    /**
1078     * Callback from RouteFinder - several routes found
1079     *
1080     * @param destNodes the destination blocks
1081     * @param routeTree the routes
1082     */
1083    protected void pickRoute(@Nonnull List<DefaultMutableTreeNode> destNodes, DefaultTreeModel routeTree) {
1084        if (destNodes.size() == 1) {
1085            showRoute(destNodes.get(0), routeTree);
1086            selectedRoute(_orders);
1087            showTempWarrant(_orders);
1088            return;
1089        }
1090        _pickRouteDialog = new JDialog(this, Bundle.getMessage("DialogTitle"), false);
1091        _pickRouteDialog.addWindowListener(new java.awt.event.WindowAdapter() {
1092            @Override
1093            public void windowClosing(java.awt.event.WindowEvent e) {
1094                clearTempWarrant();
1095            }
1096        });
1097        _tempWarrant = null;
1098        JPanel mainPanel = new JPanel();
1099        mainPanel.setLayout(new BorderLayout(5, 5));
1100        JPanel panel = new JPanel();
1101        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
1102        panel.add(new JLabel(Bundle.getMessage("NumberRoutes1", destNodes.size())));
1103        panel.add(new JLabel(Bundle.getMessage("NumberRoutes2")));
1104        JPanel wrapper = new JPanel();
1105        wrapper.add(panel);
1106        mainPanel.add(wrapper, BorderLayout.NORTH);
1107        ButtonGroup buttons = new ButtonGroup();
1108
1109        panel = new JPanel();
1110        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
1111        for (int i = 0; i < destNodes.size(); i++) {
1112            JRadioButton button = new JRadioButton(Bundle.getMessage("RouteSize", i + 1,
1113                    destNodes.get(i).getLevel() + 1));
1114            button.setActionCommand("" + i);
1115            button.addActionListener((ActionEvent e) -> clearTempWarrant() );
1116            buttons.add(button);
1117            panel.add(button);
1118            if (destNodes.size() == 1) {
1119                button.setSelected(true);
1120            }
1121        }
1122        JScrollPane scrollPane = new JScrollPane(panel);
1123        javax.swing.JViewport vp = scrollPane.getViewport();
1124        JRadioButton button = new JRadioButton(Bundle.getMessage("RouteSize", 000, 000));
1125        vp.setPreferredSize(new Dimension(button.getWidth(), _depth*button.getHeight()));
1126        mainPanel.add(scrollPane, BorderLayout.CENTER);
1127
1128        JButton ok = new JButton(Bundle.getMessage("ButtonSelect"));
1129        ok.addActionListener(new ActionListener() {
1130            ButtonGroup buts;
1131            JDialog dialog;
1132            List<DefaultMutableTreeNode> dNodes;
1133            DefaultTreeModel tree;
1134
1135            @Override
1136            public void actionPerformed(ActionEvent e) {
1137                if (buts.getSelection() != null) {
1138                    clearTempWarrant();
1139                    int i = Integer.parseInt(buttons.getSelection().getActionCommand());
1140                    showRoute(dNodes.get(i), tree);
1141                    selectedRoute(_orders);
1142                    showTempWarrant(_orders);
1143                    dialog.dispose();
1144                } else {
1145                    showWarning(Bundle.getMessage("SelectRoute"));
1146                }
1147            }
1148
1149            ActionListener init(ButtonGroup bg, JDialog d, List<DefaultMutableTreeNode> dn,
1150                    DefaultTreeModel t) {
1151                buts = bg;
1152                dialog = d;
1153                dNodes = dn;
1154                tree = t;
1155                return this;
1156            }
1157        }.init(buttons, _pickRouteDialog, destNodes, routeTree));
1158        ok.setMaximumSize(ok.getPreferredSize());
1159        JButton show = new JButton(Bundle.getMessage("ButtonReview"));
1160        show.addActionListener(new ActionListener() {
1161            ButtonGroup buts;
1162            List<DefaultMutableTreeNode> destinationNodes;
1163            DefaultTreeModel tree;
1164
1165            @Override
1166            public void actionPerformed(ActionEvent e) {
1167                if (buts.getSelection() != null) {
1168                    clearTempWarrant();
1169                    int i = Integer.parseInt(buttons.getSelection().getActionCommand());
1170                    showRoute(destinationNodes.get(i), tree);
1171                    showTempWarrant(_orders);
1172                } else {
1173                    showWarning(Bundle.getMessage("SelectRoute"));
1174                }
1175            }
1176
1177            ActionListener init(ButtonGroup bg, List<DefaultMutableTreeNode> dn,
1178                    DefaultTreeModel t) {
1179                buts = bg;
1180                destinationNodes = dn;
1181                tree = t;
1182                return this;
1183            }
1184        }.init(buttons, destNodes, routeTree));
1185        show.setMaximumSize(show.getPreferredSize());
1186        panel = new JPanel();
1187        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
1188        panel.add(Box.createHorizontalGlue());
1189        panel.add(show);
1190        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1191        panel.add(ok);
1192        panel.add(Box.createHorizontalGlue());
1193        wrapper = new JPanel();
1194        wrapper.add(panel);
1195        mainPanel.add(wrapper, BorderLayout.SOUTH);
1196
1197        panel = new JPanel();
1198        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
1199        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1200        panel.add(makeRouteTablePanel());
1201        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1202        panel.add(mainPanel);
1203        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1204
1205        _pickRouteDialog.getContentPane().add(panel);
1206        _pickRouteDialog.setLocation(getLocation().x - 20, getLocation().y + 150);
1207        _pickRouteDialog.pack();
1208        _pickRouteDialog.setVisible(true);
1209    }
1210
1211    protected void showWarning(String msg) {
1212        JmriJOptionPane.showMessageDialog(this, msg,
1213            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
1214    }
1215
1216    /**
1217     * Callback from RouteFinder - exactly one route found
1218     *
1219     * @param destNode destination block
1220     * @param tree     possible routes
1221     */
1222    private void showRoute(DefaultMutableTreeNode destNode, DefaultTreeModel tree) {
1223        TreeNode[] nodes = tree.getPathToRoot(destNode);
1224        _orders = new ArrayList<>();
1225        int count = 0;
1226        for (TreeNode node : nodes) {
1227            BlockOrder bo = (BlockOrder) ((DefaultMutableTreeNode) node).getUserObject();
1228            bo.setIndex(count++);
1229            _orders.add(bo);
1230        }
1231        _routeModel.fireTableDataChanged();
1232        log.debug("showRoute: Route has {} orders.", _orders.size());
1233    }
1234
1235    protected JPanel makeRouteTablePanel() {
1236        JTable routeTable = new JTable(_routeModel);
1237        routeTable.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
1238        for (int i = 0; i < _routeModel.getColumnCount(); i++) {
1239            int width = _routeModel.getPreferredWidth(i);
1240            routeTable.getColumnModel().getColumn(i).setPreferredWidth(width);
1241        }
1242        JScrollPane tablePane = new JScrollPane(routeTable);
1243        Dimension dim = routeTable.getPreferredSize();
1244        dim.height = routeTable.getRowHeight() * 11;
1245        tablePane.getViewport().setPreferredSize(dim);
1246
1247        JPanel routePanel = new JPanel();
1248        routePanel.setLayout(new BoxLayout(routePanel, BoxLayout.Y_AXIS));
1249        JLabel title = new JLabel(Bundle.getMessage("RouteTableTitle"));
1250        routePanel.add(title, BorderLayout.NORTH);
1251        routePanel.add(tablePane);
1252        routePanel.add(Box.createVerticalGlue());
1253        return routePanel;
1254    }
1255
1256    /**
1257     * Callback from RouteFinder - no routes found
1258     *
1259     * @param tree   routes
1260     * @param origin starting block
1261     * @param dest   ending block
1262     */
1263    protected void debugRoute(DefaultTreeModel tree, BlockOrder origin, BlockOrder dest) {
1264        if (JmriJOptionPane.YES_OPTION != JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("NoRoute",
1265                new Object[]{origin.getBlock().getDisplayName(),
1266                    origin.getPathName(), origin.getExitName(), dest.getBlock().getDisplayName(),
1267                    dest.getEntryName(), dest.getPathName(), getDepth()}),
1268                Bundle.getMessage("WarningTitle"), JmriJOptionPane.YES_NO_OPTION,
1269                JmriJOptionPane.WARNING_MESSAGE)) {
1270            return;
1271        }
1272        if (_debugFrame != null) {
1273            _debugFrame.dispose();
1274        }
1275        _debugFrame = new JFrame(Bundle.getMessage("DebugRoute"));
1276        javax.swing.JTree dTree = new javax.swing.JTree(tree);
1277        dTree.setShowsRootHandles(true);
1278        dTree.setScrollsOnExpand(true);
1279        dTree.setExpandsSelectedPaths(true);
1280        JScrollPane treePane = new JScrollPane(dTree);
1281        treePane.getViewport().setPreferredSize(new Dimension(900, 300));
1282        _debugFrame.getContentPane().add(treePane);
1283        _debugFrame.setVisible(true);
1284        _debugFrame.pack();
1285    }
1286
1287    protected void clearRoute() {
1288        _orders = new ArrayList<>();
1289        clearFrames();
1290        clearFields();
1291        _focusedField = _origin;
1292        _routeModel.fireTableDataChanged();
1293    }
1294
1295    private void clearFrames() {
1296
1297        if (_debugFrame != null) {
1298            _debugFrame.dispose();
1299            _debugFrame = null;
1300        }
1301        if (_pickRouteDialog != null) {
1302            _pickRouteDialog.dispose();
1303            _pickRouteDialog = null;
1304        }
1305        closeProfileTable();
1306
1307        if (_pickListFrame != null) {
1308            _pickListFrame.dispose();
1309            _pickListFrame = null;
1310        }
1311    }
1312
1313    private void clearFields() {
1314        _origin.clearFields();
1315        _destination.clearFields();
1316        _via.clearFields();
1317        _avoid.clearFields();
1318    }
1319
1320    protected String routeIsValid() {
1321        if (_orders == null || _orders.isEmpty()) {
1322            return Bundle.getMessage("noBlockOrders");
1323        }
1324        if (_orders.size() < 2) {
1325            return Bundle.getMessage("NoRouteSet", _origin.getBlockName(), _destination.getBlockName());
1326        }
1327        BlockOrder blockOrder = _orders.get(0);
1328        String msg = pathIsValid(blockOrder.getBlock(), blockOrder.getPathName());
1329        if (msg == null) {
1330            for (int i = 1; i < _orders.size(); i++) {
1331                BlockOrder nextBlockOrder = _orders.get(i);
1332                msg = pathIsValid(nextBlockOrder.getBlock(), nextBlockOrder.getPathName());
1333                if (msg != null) {
1334                    return msg;
1335                }
1336                if (!blockOrder.getExitName().equals(nextBlockOrder.getEntryName())) {
1337                    return Bundle.getMessage("disconnectedRoute",
1338                            blockOrder.getBlock().getDisplayName(), nextBlockOrder.getBlock().getDisplayName());
1339                }
1340                blockOrder = nextBlockOrder;
1341            }
1342        }
1343        return msg;
1344    }
1345
1346    static protected String pathIsValid(OBlock block, String pathName) {
1347        if (block == null) {
1348            return Bundle.getMessage("PathInvalid", pathName, "null");
1349        }
1350        List<Path> list = block.getPaths();
1351        if (list.isEmpty()) {
1352            return Bundle.getMessage("WarningTitle");
1353        }
1354        if (pathName != null) {
1355            for (int i = 0; i < list.size(); i++) {
1356                OPath path = (OPath) list.get(i);
1357                //if (log.isDebugEnabled()) log.debug("pathIsValid: pathName= "+pathName+", i= "+i+", path is "+path.getName());
1358                if (pathName.equals(path.getName())) {
1359                    if (path.getFromPortal() == null && path.getToPortal() == null) {
1360                        return Bundle.getMessage("PathNeedsPortal", pathName, block.getDisplayName());
1361                    }
1362                    return null;
1363                }
1364            }
1365        }
1366        return Bundle.getMessage("PathInvalid", pathName, block.getDisplayName());
1367    }
1368
1369    @Override
1370    public void dispose() {
1371        clearFrames();
1372        super.dispose();
1373    }
1374
1375    /* ************************ Route Table ******************************/
1376    private class RouteTableModel extends AbstractTableModel {
1377
1378        static final int BLOCK_COLUMN = 0;
1379        static final int ENTER_PORTAL_COL = 1;
1380        static final int PATH_COLUMN = 2;
1381        static final int DEST_PORTAL_COL = 3;
1382        static final int NUMCOLS = 4;
1383
1384        RouteTableModel() {
1385            super();
1386        }
1387
1388        @Override
1389        public int getColumnCount() {
1390            return NUMCOLS;
1391        }
1392
1393        @Override
1394        public int getRowCount() {
1395            if (_orders==null) {
1396                return 0;
1397            }
1398            return _orders.size();
1399        }
1400
1401        @Override
1402        public String getColumnName(int col) {
1403            switch (col) {
1404                case BLOCK_COLUMN:
1405                    return Bundle.getMessage("BlockCol");
1406                case ENTER_PORTAL_COL:
1407                    return Bundle.getMessage("EnterPortalCol");
1408                case PATH_COLUMN:
1409                    return Bundle.getMessage("PathCol");
1410                case DEST_PORTAL_COL:
1411                    return Bundle.getMessage("DestPortalCol");
1412                default:
1413                    // fall through
1414                    break;
1415            }
1416            return "";
1417        }
1418
1419        @Override
1420        public boolean isCellEditable(int row, int col) {
1421            return false;
1422        }
1423
1424        @Override
1425        public Class<?> getColumnClass(int col) {
1426            return String.class;
1427        }
1428
1429        public int getPreferredWidth(int col) {
1430            return new JTextField(15).getPreferredSize().width;
1431        }
1432
1433        @Override
1434        public Object getValueAt(int row, int col) {
1435            // some error checking
1436            if (_orders==null || row >= _orders.size()) {
1437                return "";
1438            }
1439            BlockOrder bo = _orders.get(row);
1440            // some error checking
1441            if (bo == null) {
1442                log.error("BlockOrder is null");
1443                return "";
1444            }
1445            switch (col) {
1446                case BLOCK_COLUMN:
1447                    return bo.getBlock().getDisplayName();
1448                case ENTER_PORTAL_COL:
1449                    return bo.getEntryName();
1450                case PATH_COLUMN:
1451                    return bo.getPathName();
1452                case DEST_PORTAL_COL:
1453                    if (row == _orders.size() - 1) {
1454                        return "";
1455                    }
1456                    return bo.getExitName();
1457                default:
1458                    // fall through
1459                    break;
1460            }
1461            return "";
1462        }
1463
1464        @Override
1465        public void setValueAt(Object value, int row, int col) {
1466            if (_orders==null) {
1467                return;
1468            }
1469            BlockOrder bo = _orders.get(row);
1470            switch (col) {
1471                case BLOCK_COLUMN:
1472                    OBlock block = InstanceManager.getDefault(OBlockManager.class).getOBlock((String) value);
1473                    if (block != null) {
1474                        bo.setBlock(block);
1475                    }
1476                    break;
1477                case ENTER_PORTAL_COL:
1478                    bo.setEntryName((String) value);
1479                    break;
1480                case PATH_COLUMN:
1481                    bo.setPathName((String) value);
1482                    break;
1483                case DEST_PORTAL_COL:
1484                    bo.setExitName((String) value);
1485                    break;
1486                default:
1487                // do nothing
1488            }
1489            fireTableRowsUpdated(row, row);
1490        }
1491    }
1492
1493    /**
1494     * Puts label message to the Left
1495     *
1496     * @param vertical Label orientation true = above, false = left
1497     * @param comp     Component to put into JPanel
1498     * @param text    Bundle keyword for label message
1499     * @param tooltip  Bundle keyword for tooltip message
1500     * @return Panel containing Component
1501     */
1502    protected static JPanel makeTextBoxPanel(boolean vertical, @Nonnull JComponent comp, String text, String tooltip) {
1503        JPanel panel = new JPanel();
1504        JLabel label = new JLabel(Bundle.getMessage(text));
1505        if (vertical) {
1506            panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
1507            label.setAlignmentX(Component.CENTER_ALIGNMENT);
1508            comp.setAlignmentX(Component.CENTER_ALIGNMENT);
1509            panel.add(Box.createVerticalStrut(STRUT_SIZE));
1510        } else {
1511            panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
1512            label.setAlignmentX(Component.LEFT_ALIGNMENT);
1513            comp.setAlignmentX(Component.RIGHT_ALIGNMENT);
1514            panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1515        }
1516        panel.add(label);
1517        if (!vertical) {
1518            panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1519        }
1520        panel.add(comp);
1521        if (vertical) {
1522            panel.add(Box.createVerticalStrut(STRUT_SIZE));
1523        } else {
1524            panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1525        }
1526        if (comp instanceof JTextField || comp instanceof JComboBox) {
1527            comp.setBackground(Color.white);
1528        }
1529        if (tooltip != null) {
1530            String tipText = Bundle.getMessage(tooltip);
1531            panel.setToolTipText(tipText);
1532            comp.setToolTipText(tipText);
1533            label.setToolTipText(tipText);
1534        }
1535        panel.setMaximumSize(new Dimension(350, comp.getPreferredSize().height));
1536        panel.setMinimumSize(new Dimension(80, comp.getPreferredSize().height));
1537        return panel;
1538    }
1539
1540    /**
1541     * Make a horizontal panel for the input of data
1542     * Puts label message to the Left, 2nd component (button) to the right
1543     *
1544     * @param comp     Component for input of data
1545     * @param button   2nd Component for panel, usually a button
1546     * @param label    Bundle keyword for label message
1547     * @param tooltip  Bundle keyword for tooltip message
1548     * @return Panel containing Components
1549     */
1550    static protected JPanel makeTextAndButtonPanel(JComponent comp, JComponent button, JLabel label, String tooltip) {
1551        JPanel panel = new JPanel();
1552        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
1553        label.setAlignmentX(JComponent.LEFT_ALIGNMENT);
1554        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1555        panel.add(label);
1556        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1557        panel.add(Box.createHorizontalGlue());
1558
1559        comp.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
1560        panel.add(comp);
1561        if (comp instanceof JTextField || comp instanceof JComboBox) {
1562            comp.setBackground(Color.white);
1563        }
1564        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1565        button.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
1566        panel.add(button);
1567        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1568
1569        if (tooltip != null) {
1570            String tipText = Bundle.getMessage(tooltip);
1571            panel.setToolTipText(tipText);
1572            comp.setToolTipText(tipText);
1573            button.setToolTipText(tipText);
1574            label.setToolTipText(tipText);
1575        }
1576        panel.setMaximumSize(new Dimension(350, comp.getPreferredSize().height));
1577        panel.setMinimumSize(new Dimension(50, comp.getPreferredSize().height));
1578        return panel;
1579    }
1580    /**
1581     * Puts label message to the Right
1582     *
1583     * @param comp    Component to put into JPanel
1584     * @param label   Bundle keyword for label message
1585     * @param tooltip Bundle keyword for tooltip message
1586     * @return Panel containing Component
1587     */
1588    static protected JPanel makeTextBoxPanel(JComponent comp, String label, String tooltip) {
1589        JPanel panel = new JPanel();
1590        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
1591        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1592        comp.setAlignmentX(JComponent.LEFT_ALIGNMENT);
1593        comp.setMaximumSize(new Dimension(300, comp.getPreferredSize().height));
1594        comp.setMinimumSize(new Dimension(30, comp.getPreferredSize().height));
1595        panel.add(comp);
1596        if (comp instanceof JTextField || comp instanceof JComboBox) {
1597            comp.setBackground(Color.white);
1598            JLabel l = new JLabel(Bundle.getMessage(label));
1599            l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
1600            l.setToolTipText(Bundle.getMessage(tooltip));
1601            panel.add(l);
1602        } else if (comp instanceof AbstractButton) {
1603            ((AbstractButton) comp).setText(Bundle.getMessage(label));
1604        }
1605        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1606        if (tooltip != null) {
1607            String tipText = Bundle.getMessage(tooltip);
1608            panel.setToolTipText(tipText);
1609            comp.setToolTipText(tipText);
1610        }
1611        panel.setMaximumSize(new Dimension(350, comp.getPreferredSize().height));
1612        panel.setMinimumSize(new Dimension(80, comp.getPreferredSize().height));
1613        return panel;
1614    }
1615
1616    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WarrantRoute.class);
1617
1618}