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            if (_routeFinder != null) {
1033                _routeFinder.cancel();  // a previous search must not report after this one
1034            }
1035            _routeFinder = new RouteFinder(this, _origin.getOrder(), _destination.getOrder(),
1036                    _via.getOrder(), _avoid.getOrder(), getDepth());
1037            jmri.util.ThreadingUtil.newThread(_routeFinder).start();
1038        }
1039        return msg;
1040    }
1041
1042    protected void stopRouteFinder() {
1043        if (_routeFinder != null) {
1044            _routeFinder.quit();
1045            _routeFinder = null;
1046        }
1047    }
1048
1049    /* *********************************** Route Selection **************************************/
1050    protected List<BlockOrder> getOrders() {
1051        return _orders;
1052    }
1053
1054    protected BlockOrder getViaBlockOrder() {
1055        return _via.getOrder();
1056    }
1057
1058    protected BlockOrder getAvoidBlockOrder() {
1059        return _avoid.getOrder();
1060    }
1061
1062    private Warrant _tempWarrant;   // only used in pickRoute() method
1063
1064    protected void clearTempWarrant() {
1065        if (_tempWarrant != null) {
1066            _tempWarrant.deAllocate();
1067        }
1068    }
1069
1070    private void showTempWarrant(ArrayList<BlockOrder> orders) {
1071        String s = ("" + Math.random()).substring(4);
1072        if (_tempWarrant == null) {
1073            _tempWarrant = new Warrant("IW" + s + "TEMP", null);
1074            _tempWarrant.setBlockOrders(orders);
1075        }
1076        _tempWarrant.setRoute(true, orders);
1077        // Don't clutter with message - this is a temp display
1078    }
1079
1080    /**
1081     * Callback from RouteFinder - several routes found
1082     *
1083     * @param destNodes the destination blocks
1084     * @param routeTree the routes
1085     */
1086    protected void pickRoute(@Nonnull List<DefaultMutableTreeNode> destNodes, DefaultTreeModel routeTree) {
1087        if (destNodes.size() == 1) {
1088            showRoute(destNodes.get(0), routeTree);
1089            selectedRoute(_orders);
1090            showTempWarrant(_orders);
1091            return;
1092        }
1093        _pickRouteDialog = new JDialog(this, Bundle.getMessage("DialogTitle"), false);
1094        _pickRouteDialog.addWindowListener(new java.awt.event.WindowAdapter() {
1095            @Override
1096            public void windowClosing(java.awt.event.WindowEvent e) {
1097                clearTempWarrant();
1098            }
1099        });
1100        _tempWarrant = null;
1101        JPanel mainPanel = new JPanel();
1102        mainPanel.setLayout(new BorderLayout(5, 5));
1103        JPanel panel = new JPanel();
1104        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
1105        panel.add(new JLabel(Bundle.getMessage("NumberRoutes1", destNodes.size())));
1106        panel.add(new JLabel(Bundle.getMessage("NumberRoutes2")));
1107        JPanel wrapper = new JPanel();
1108        wrapper.add(panel);
1109        mainPanel.add(wrapper, BorderLayout.NORTH);
1110        ButtonGroup buttons = new ButtonGroup();
1111
1112        panel = new JPanel();
1113        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
1114        for (int i = 0; i < destNodes.size(); i++) {
1115            JRadioButton button = new JRadioButton(Bundle.getMessage("RouteSize", i + 1,
1116                    destNodes.get(i).getLevel() + 1));
1117            button.setActionCommand("" + i);
1118            button.addActionListener((ActionEvent e) -> clearTempWarrant() );
1119            buttons.add(button);
1120            panel.add(button);
1121            if (destNodes.size() == 1) {
1122                button.setSelected(true);
1123            }
1124        }
1125        JScrollPane scrollPane = new JScrollPane(panel);
1126        javax.swing.JViewport vp = scrollPane.getViewport();
1127        JRadioButton button = new JRadioButton(Bundle.getMessage("RouteSize", 000, 000));
1128        vp.setPreferredSize(new Dimension(button.getWidth(), _depth*button.getHeight()));
1129        mainPanel.add(scrollPane, BorderLayout.CENTER);
1130
1131        JButton ok = new JButton(Bundle.getMessage("ButtonSelect"));
1132        ok.addActionListener(new ActionListener() {
1133            ButtonGroup buts;
1134            JDialog dialog;
1135            List<DefaultMutableTreeNode> dNodes;
1136            DefaultTreeModel tree;
1137
1138            @Override
1139            public void actionPerformed(ActionEvent e) {
1140                if (buts.getSelection() != null) {
1141                    clearTempWarrant();
1142                    int i = Integer.parseInt(buttons.getSelection().getActionCommand());
1143                    showRoute(dNodes.get(i), tree);
1144                    selectedRoute(_orders);
1145                    showTempWarrant(_orders);
1146                    dialog.dispose();
1147                } else {
1148                    showWarning(Bundle.getMessage("SelectRoute"));
1149                }
1150            }
1151
1152            ActionListener init(ButtonGroup bg, JDialog d, List<DefaultMutableTreeNode> dn,
1153                    DefaultTreeModel t) {
1154                buts = bg;
1155                dialog = d;
1156                dNodes = dn;
1157                tree = t;
1158                return this;
1159            }
1160        }.init(buttons, _pickRouteDialog, destNodes, routeTree));
1161        ok.setMaximumSize(ok.getPreferredSize());
1162        JButton show = new JButton(Bundle.getMessage("ButtonReview"));
1163        show.addActionListener(new ActionListener() {
1164            ButtonGroup buts;
1165            List<DefaultMutableTreeNode> destinationNodes;
1166            DefaultTreeModel tree;
1167
1168            @Override
1169            public void actionPerformed(ActionEvent e) {
1170                if (buts.getSelection() != null) {
1171                    clearTempWarrant();
1172                    int i = Integer.parseInt(buttons.getSelection().getActionCommand());
1173                    showRoute(destinationNodes.get(i), tree);
1174                    showTempWarrant(_orders);
1175                } else {
1176                    showWarning(Bundle.getMessage("SelectRoute"));
1177                }
1178            }
1179
1180            ActionListener init(ButtonGroup bg, List<DefaultMutableTreeNode> dn,
1181                    DefaultTreeModel t) {
1182                buts = bg;
1183                destinationNodes = dn;
1184                tree = t;
1185                return this;
1186            }
1187        }.init(buttons, destNodes, routeTree));
1188        show.setMaximumSize(show.getPreferredSize());
1189        panel = new JPanel();
1190        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
1191        panel.add(Box.createHorizontalGlue());
1192        panel.add(show);
1193        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1194        panel.add(ok);
1195        panel.add(Box.createHorizontalGlue());
1196        wrapper = new JPanel();
1197        wrapper.add(panel);
1198        mainPanel.add(wrapper, BorderLayout.SOUTH);
1199
1200        panel = new JPanel();
1201        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
1202        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1203        panel.add(makeRouteTablePanel());
1204        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1205        panel.add(mainPanel);
1206        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1207
1208        _pickRouteDialog.getContentPane().add(panel);
1209        _pickRouteDialog.setLocation(getLocation().x - 20, getLocation().y + 150);
1210        _pickRouteDialog.pack();
1211        _pickRouteDialog.setVisible(true);
1212    }
1213
1214    protected void showWarning(String msg) {
1215        JmriJOptionPane.showMessageDialog(this, msg,
1216            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
1217    }
1218
1219    /**
1220     * Callback from RouteFinder - exactly one route found
1221     *
1222     * @param destNode destination block
1223     * @param tree     possible routes
1224     */
1225    private void showRoute(DefaultMutableTreeNode destNode, DefaultTreeModel tree) {
1226        TreeNode[] nodes = tree.getPathToRoot(destNode);
1227        _orders = new ArrayList<>();
1228        int count = 0;
1229        for (TreeNode node : nodes) {
1230            BlockOrder bo = (BlockOrder) ((DefaultMutableTreeNode) node).getUserObject();
1231            bo.setIndex(count++);
1232            _orders.add(bo);
1233        }
1234        _routeModel.fireTableDataChanged();
1235        log.debug("showRoute: Route has {} orders.", _orders.size());
1236    }
1237
1238    protected JPanel makeRouteTablePanel() {
1239        JTable routeTable = new JTable(_routeModel);
1240        routeTable.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
1241        for (int i = 0; i < _routeModel.getColumnCount(); i++) {
1242            int width = _routeModel.getPreferredWidth(i);
1243            routeTable.getColumnModel().getColumn(i).setPreferredWidth(width);
1244        }
1245        JScrollPane tablePane = new JScrollPane(routeTable);
1246        Dimension dim = routeTable.getPreferredSize();
1247        dim.height = routeTable.getRowHeight() * 11;
1248        tablePane.getViewport().setPreferredSize(dim);
1249
1250        JPanel routePanel = new JPanel();
1251        routePanel.setLayout(new BoxLayout(routePanel, BoxLayout.Y_AXIS));
1252        JLabel title = new JLabel(Bundle.getMessage("RouteTableTitle"));
1253        routePanel.add(title, BorderLayout.NORTH);
1254        routePanel.add(tablePane);
1255        routePanel.add(Box.createVerticalGlue());
1256        return routePanel;
1257    }
1258
1259    /**
1260     * Callback from RouteFinder - no routes found
1261     *
1262     * @param tree   routes
1263     * @param origin starting block
1264     * @param dest   ending block
1265     */
1266    protected void debugRoute(DefaultTreeModel tree, BlockOrder origin, BlockOrder dest) {
1267        if (JmriJOptionPane.YES_OPTION != JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("NoRoute",
1268                new Object[]{origin.getBlock().getDisplayName(),
1269                    origin.getPathName(), origin.getExitName(), dest.getBlock().getDisplayName(),
1270                    dest.getEntryName(), dest.getPathName(), getDepth()}),
1271                Bundle.getMessage("WarningTitle"), JmriJOptionPane.YES_NO_OPTION,
1272                JmriJOptionPane.WARNING_MESSAGE)) {
1273            return;
1274        }
1275        if (_debugFrame != null) {
1276            _debugFrame.dispose();
1277        }
1278        _debugFrame = new JFrame(Bundle.getMessage("DebugRoute"));
1279        javax.swing.JTree dTree = new javax.swing.JTree(tree);
1280        dTree.setShowsRootHandles(true);
1281        dTree.setScrollsOnExpand(true);
1282        dTree.setExpandsSelectedPaths(true);
1283        JScrollPane treePane = new JScrollPane(dTree);
1284        treePane.getViewport().setPreferredSize(new Dimension(900, 300));
1285        _debugFrame.getContentPane().add(treePane);
1286        _debugFrame.setVisible(true);
1287        _debugFrame.pack();
1288    }
1289
1290    protected void clearRoute() {
1291        _orders = new ArrayList<>();
1292        clearFrames();
1293        clearFields();
1294        _focusedField = _origin;
1295        _routeModel.fireTableDataChanged();
1296    }
1297
1298    private void clearFrames() {
1299
1300        if (_debugFrame != null) {
1301            _debugFrame.dispose();
1302            _debugFrame = null;
1303        }
1304        if (_pickRouteDialog != null) {
1305            _pickRouteDialog.dispose();
1306            _pickRouteDialog = null;
1307        }
1308        closeProfileTable();
1309
1310        if (_pickListFrame != null) {
1311            _pickListFrame.dispose();
1312            _pickListFrame = null;
1313        }
1314    }
1315
1316    private void clearFields() {
1317        _origin.clearFields();
1318        _destination.clearFields();
1319        _via.clearFields();
1320        _avoid.clearFields();
1321    }
1322
1323    protected String routeIsValid() {
1324        if (_orders == null || _orders.isEmpty()) {
1325            return Bundle.getMessage("noBlockOrders");
1326        }
1327        if (_orders.size() < 2) {
1328            return Bundle.getMessage("NoRouteSet", _origin.getBlockName(), _destination.getBlockName());
1329        }
1330        BlockOrder blockOrder = _orders.get(0);
1331        String msg = pathIsValid(blockOrder.getBlock(), blockOrder.getPathName());
1332        if (msg == null) {
1333            for (int i = 1; i < _orders.size(); i++) {
1334                BlockOrder nextBlockOrder = _orders.get(i);
1335                msg = pathIsValid(nextBlockOrder.getBlock(), nextBlockOrder.getPathName());
1336                if (msg != null) {
1337                    return msg;
1338                }
1339                if (!blockOrder.getExitName().equals(nextBlockOrder.getEntryName())) {
1340                    return Bundle.getMessage("disconnectedRoute",
1341                            blockOrder.getBlock().getDisplayName(), nextBlockOrder.getBlock().getDisplayName());
1342                }
1343                blockOrder = nextBlockOrder;
1344            }
1345        }
1346        return msg;
1347    }
1348
1349    protected static String pathIsValid(OBlock block, String pathName) {
1350        if (block == null) {
1351            return Bundle.getMessage("PathInvalid", pathName, "null");
1352        }
1353        List<Path> list = block.getPaths();
1354        if (list.isEmpty()) {
1355            return Bundle.getMessage("WarningTitle");
1356        }
1357        if (pathName != null) {
1358            for (int i = 0; i < list.size(); i++) {
1359                OPath path = (OPath) list.get(i);
1360                //if (log.isDebugEnabled()) log.debug("pathIsValid: pathName= "+pathName+", i= "+i+", path is "+path.getName());
1361                if (pathName.equals(path.getName())) {
1362                    if (path.getFromPortal() == null && path.getToPortal() == null) {
1363                        return Bundle.getMessage("PathNeedsPortal", pathName, block.getDisplayName());
1364                    }
1365                    return null;
1366                }
1367            }
1368        }
1369        return Bundle.getMessage("PathInvalid", pathName, block.getDisplayName());
1370    }
1371
1372    @Override
1373    public void dispose() {
1374        clearFrames();
1375        super.dispose();
1376    }
1377
1378    /* ************************ Route Table ******************************/
1379    private class RouteTableModel extends AbstractTableModel {
1380
1381        static final int BLOCK_COLUMN = 0;
1382        static final int ENTER_PORTAL_COL = 1;
1383        static final int PATH_COLUMN = 2;
1384        static final int DEST_PORTAL_COL = 3;
1385        static final int NUMCOLS = 4;
1386
1387        RouteTableModel() {
1388            super();
1389        }
1390
1391        @Override
1392        public int getColumnCount() {
1393            return NUMCOLS;
1394        }
1395
1396        @Override
1397        public int getRowCount() {
1398            if (_orders==null) {
1399                return 0;
1400            }
1401            return _orders.size();
1402        }
1403
1404        @Override
1405        public String getColumnName(int col) {
1406            switch (col) {
1407                case BLOCK_COLUMN:
1408                    return Bundle.getMessage("BlockCol");
1409                case ENTER_PORTAL_COL:
1410                    return Bundle.getMessage("EnterPortalCol");
1411                case PATH_COLUMN:
1412                    return Bundle.getMessage("PathCol");
1413                case DEST_PORTAL_COL:
1414                    return Bundle.getMessage("DestPortalCol");
1415                default:
1416                    // fall through
1417                    break;
1418            }
1419            return "";
1420        }
1421
1422        @Override
1423        public boolean isCellEditable(int row, int col) {
1424            return false;
1425        }
1426
1427        @Override
1428        public Class<?> getColumnClass(int col) {
1429            return String.class;
1430        }
1431
1432        public int getPreferredWidth(int col) {
1433            return new JTextField(15).getPreferredSize().width;
1434        }
1435
1436        @Override
1437        public Object getValueAt(int row, int col) {
1438            // some error checking
1439            if (_orders==null || row >= _orders.size()) {
1440                return "";
1441            }
1442            BlockOrder bo = _orders.get(row);
1443            // some error checking
1444            if (bo == null) {
1445                log.error("BlockOrder is null");
1446                return "";
1447            }
1448            switch (col) {
1449                case BLOCK_COLUMN:
1450                    return bo.getBlock().getDisplayName();
1451                case ENTER_PORTAL_COL:
1452                    return bo.getEntryName();
1453                case PATH_COLUMN:
1454                    return bo.getPathName();
1455                case DEST_PORTAL_COL:
1456                    if (row == _orders.size() - 1) {
1457                        return "";
1458                    }
1459                    return bo.getExitName();
1460                default:
1461                    // fall through
1462                    break;
1463            }
1464            return "";
1465        }
1466
1467        @Override
1468        public void setValueAt(Object value, int row, int col) {
1469            if (_orders==null) {
1470                return;
1471            }
1472            BlockOrder bo = _orders.get(row);
1473            switch (col) {
1474                case BLOCK_COLUMN:
1475                    OBlock block = InstanceManager.getDefault(OBlockManager.class).getOBlock((String) value);
1476                    if (block != null) {
1477                        bo.setBlock(block);
1478                    }
1479                    break;
1480                case ENTER_PORTAL_COL:
1481                    bo.setEntryName((String) value);
1482                    break;
1483                case PATH_COLUMN:
1484                    bo.setPathName((String) value);
1485                    break;
1486                case DEST_PORTAL_COL:
1487                    bo.setExitName((String) value);
1488                    break;
1489                default:
1490                // do nothing
1491            }
1492            fireTableRowsUpdated(row, row);
1493        }
1494    }
1495
1496    /**
1497     * Puts label message to the Left
1498     *
1499     * @param vertical Label orientation true = above, false = left
1500     * @param comp     Component to put into JPanel
1501     * @param text    Bundle keyword for label message
1502     * @param tooltip  Bundle keyword for tooltip message
1503     * @return Panel containing Component
1504     */
1505    protected static JPanel makeTextBoxPanel(boolean vertical, @Nonnull JComponent comp, String text, String tooltip) {
1506        JPanel panel = new JPanel();
1507        JLabel label = new JLabel(Bundle.getMessage(text));
1508        if (vertical) {
1509            panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
1510            label.setAlignmentX(Component.CENTER_ALIGNMENT);
1511            comp.setAlignmentX(Component.CENTER_ALIGNMENT);
1512            panel.add(Box.createVerticalStrut(STRUT_SIZE));
1513        } else {
1514            panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
1515            label.setAlignmentX(Component.LEFT_ALIGNMENT);
1516            comp.setAlignmentX(Component.RIGHT_ALIGNMENT);
1517            panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1518        }
1519        panel.add(label);
1520        if (!vertical) {
1521            panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1522        }
1523        panel.add(comp);
1524        if (vertical) {
1525            panel.add(Box.createVerticalStrut(STRUT_SIZE));
1526        } else {
1527            panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1528        }
1529        if (comp instanceof JTextField || comp instanceof JComboBox) {
1530            comp.setBackground(Color.white);
1531        }
1532        if (tooltip != null) {
1533            String tipText = Bundle.getMessage(tooltip);
1534            panel.setToolTipText(tipText);
1535            comp.setToolTipText(tipText);
1536            label.setToolTipText(tipText);
1537        }
1538        panel.setMaximumSize(new Dimension(350, comp.getPreferredSize().height));
1539        panel.setMinimumSize(new Dimension(80, comp.getPreferredSize().height));
1540        return panel;
1541    }
1542
1543    /**
1544     * Make a horizontal panel for the input of data
1545     * Puts label message to the Left, 2nd component (button) to the right
1546     *
1547     * @param comp     Component for input of data
1548     * @param button   2nd Component for panel, usually a button
1549     * @param label    Bundle keyword for label message
1550     * @param tooltip  Bundle keyword for tooltip message
1551     * @return Panel containing Components
1552     */
1553    protected static JPanel makeTextAndButtonPanel(JComponent comp, JComponent button, JLabel label, String tooltip) {
1554        JPanel panel = new JPanel();
1555        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
1556        label.setAlignmentX(JComponent.LEFT_ALIGNMENT);
1557        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1558        panel.add(label);
1559        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1560        panel.add(Box.createHorizontalGlue());
1561
1562        comp.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
1563        panel.add(comp);
1564        if (comp instanceof JTextField || comp instanceof JComboBox) {
1565            comp.setBackground(Color.white);
1566        }
1567        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1568        button.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
1569        panel.add(button);
1570        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1571
1572        if (tooltip != null) {
1573            String tipText = Bundle.getMessage(tooltip);
1574            panel.setToolTipText(tipText);
1575            comp.setToolTipText(tipText);
1576            button.setToolTipText(tipText);
1577            label.setToolTipText(tipText);
1578        }
1579        panel.setMaximumSize(new Dimension(350, comp.getPreferredSize().height));
1580        panel.setMinimumSize(new Dimension(50, comp.getPreferredSize().height));
1581        return panel;
1582    }
1583    /**
1584     * Puts label message to the Right
1585     *
1586     * @param comp    Component to put into JPanel
1587     * @param label   Bundle keyword for label message
1588     * @param tooltip Bundle keyword for tooltip message
1589     * @return Panel containing Component
1590     */
1591    protected static JPanel makeTextBoxPanel(JComponent comp, String label, String tooltip) {
1592        JPanel panel = new JPanel();
1593        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
1594        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1595        comp.setAlignmentX(JComponent.LEFT_ALIGNMENT);
1596        comp.setMaximumSize(new Dimension(300, comp.getPreferredSize().height));
1597        comp.setMinimumSize(new Dimension(30, comp.getPreferredSize().height));
1598        panel.add(comp);
1599        if (comp instanceof JTextField || comp instanceof JComboBox) {
1600            comp.setBackground(Color.white);
1601            JLabel l = new JLabel(Bundle.getMessage(label));
1602            l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
1603            l.setToolTipText(Bundle.getMessage(tooltip));
1604            panel.add(l);
1605        } else if (comp instanceof AbstractButton) {
1606            ((AbstractButton) comp).setText(Bundle.getMessage(label));
1607        }
1608        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
1609        if (tooltip != null) {
1610            String tipText = Bundle.getMessage(tooltip);
1611            panel.setToolTipText(tipText);
1612            comp.setToolTipText(tipText);
1613        }
1614        panel.setMaximumSize(new Dimension(350, comp.getPreferredSize().height));
1615        panel.setMinimumSize(new Dimension(80, comp.getPreferredSize().height));
1616        return panel;
1617    }
1618
1619    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WarrantRoute.class);
1620
1621}