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