001package jmri.jmrit.logix;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Component;
006import java.awt.Dimension;
007import java.awt.FlowLayout;
008import java.awt.FontMetrics;
009import java.awt.event.*;
010import java.awt.MouseInfo;
011import java.awt.Point;
012import java.awt.Rectangle;
013import java.util.ArrayList;
014import java.util.List;
015
016import javax.swing.*;
017import javax.swing.table.*;
018import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
019
020import jmri.InstanceManager;
021import jmri.NamedBean;
022import jmri.NamedBeanHandle;
023import jmri.SpeedStepMode;
024import jmri.jmrit.picker.PickListModel;
025import jmri.jmrit.logix.ThrottleSetting.Command;
026import jmri.jmrit.logix.ThrottleSetting.CommandValue;
027import jmri.jmrit.logix.ThrottleSetting.ValueType;
028
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * WarrantFame creates and edits Warrants
034 * <br>
035 * <hr>
036 * This file is part of JMRI.
037 * <p>
038 * JMRI is free software; you can redistribute it and/or modify it under the
039 * terms of version 2 of the GNU General Public License as published by the Free
040 * Software Foundation. See the "COPYING" file for a copy of this license.
041 * <p>
042 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
043 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
044 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
045 *
046 * @author  Pete Cressman Copyright (C) 2009, 2010
047 */
048public class WarrantFrame extends WarrantRoute {
049
050    int _rowHeight;
051    private Warrant _warrant;       // unregistered warrant - may be a copy of a registered warrant
052    private Warrant _saveWarrant;
053    private ThrottleTableModel _commandModel;
054    private JTable _commandTable;
055    private JScrollPane _throttlePane;
056
057    private ArrayList<ThrottleSetting> _throttleCommands = new ArrayList<>();
058    private long _startTime;
059    private float _speedFactor;
060    private float _speed;
061    private long _TTP = 0;
062    private boolean _forward = true;
063    LearnThrottleFrame _learnThrottle = null;       // need access for JUnit test
064    static Color myGreen = new Color(0, 100, 0);
065
066    JTextField _sysNameBox;
067    JTextField _userNameBox;
068
069    JTabbedPane _tabbedPane;
070    JPanel _routePanel;
071    JPanel _commandPanel;
072    JRadioButton _isSCWarrant = new JRadioButton(Bundle.getMessage("SmallLayoutTrainAutomater"), false);
073    JRadioButton _isWarrant = new JRadioButton(Bundle.getMessage("NormalWarrant"), true);
074    private DisplayButton _speedUnits;
075    private JLabel _unitsLabel;
076    private float _speedConversion;
077    JCheckBox    _runForward = new JCheckBox(Bundle.getMessage("Forward"));
078    JFormattedTextField _speedFactorTextField = new JFormattedTextField();
079    JFormattedTextField _TTPtextField = new JFormattedTextField();
080    JCheckBox    _noRampBox = new JCheckBox();
081    JCheckBox    _shareRouteBox = new JCheckBox();
082    JCheckBox    _addTracker = new JCheckBox();
083    JCheckBox    _runETOnlyBox = new JCheckBox();
084    JRadioButton _eStop = new JRadioButton(Bundle.getMessage("EStop"), false);
085    JRadioButton _halt = new JRadioButton(Bundle.getMessage("Halt"), false);
086    JRadioButton _resume = new JRadioButton(Bundle.getMessage("Resume"), false);
087    JRadioButton _abort = new JRadioButton(Bundle.getMessage("Abort"), false);
088    JRadioButton _invisible = new JRadioButton();
089    JTextField   _statusBox = new JTextField(90);
090    JRadioButton _showRoute = new JRadioButton(Bundle.getMessage("showRoute"), false);
091    JRadioButton _showScript = new JRadioButton(Bundle.getMessage("showScript"), false);
092
093    JTextField _searchStatus = new JTextField();
094
095    /*
096     * Constructor for opening an existing warrant for editing
097     */
098    protected WarrantFrame(Warrant w) {
099        super();
100        // w is registered
101        _saveWarrant = w;
102        // temp unregistered version until editing is saved.
103        _warrant = new Warrant(w.getSystemName(), w.getUserName());
104        setup(_saveWarrant);
105        init();
106    }
107
108    /*
109     * Constructor for creating a new warrant or copy or concatenation of warrants)
110     * Called by WarrantTableAction
111     */
112    protected WarrantFrame(Warrant startW, Warrant endW) {
113        super();
114        WarrantManager mgr = InstanceManager.getDefault(WarrantManager.class);
115        String sName = mgr.getAutoSystemName();
116        while (mgr.getBySystemName(sName) != null) {
117            mgr.updateAutoNumber(sName);
118            sName = mgr.getAutoSystemName();
119        }
120        _warrant = new Warrant(sName, null);
121        if (startW != null) {   
122            setup(startW);
123            if (endW != null) {     // concatenate warrants
124                BlockOrder bo = _orders.get(_orders.size()-1);
125                List<BlockOrder> orders = endW.getBlockOrders();
126                bo.setExitName(endW.getfirstOrder().getExitName());
127                 for (int i = 1; i < orders.size(); i++) {
128                    _orders.add(new BlockOrder(orders.get(i)));
129                }
130                _destination.setOrder(endW.getLastOrder());
131                if (_via.getOrder() == null) {
132                    _via.setOrder(endW.getViaOrder());
133                }
134                if (_avoid.getOrder() == null) {
135                    _avoid.setOrder(endW.getAvoidOrder());
136                }
137                for (ThrottleSetting ts :endW.getThrottleCommands()) {
138                    _throttleCommands.add(new ThrottleSetting(ts));
139                }
140            } // else copy startW
141        }   // else create new warrant
142        init();
143    }
144
145    /**
146     * Set up parameters from an existing warrant. note that _warrant is unregistered.
147     */
148    private void setup(Warrant warrant) {
149        _origin.setOrder(warrant.getfirstOrder());
150        _destination.setOrder(warrant.getLastOrder());
151        _via.setOrder(warrant.getViaOrder());
152        _avoid.setOrder(warrant.getAvoidOrder());
153        List<BlockOrder> list = warrant.getBlockOrders();
154        _orders = new ArrayList<>(list.size());
155        for (BlockOrder bo : list) {
156            _orders.add(new BlockOrder(bo));
157        }
158
159        if (warrant instanceof SCWarrant) {
160            _speedFactor = ((SCWarrant)warrant).getSpeedFactor();
161            _TTP = ((SCWarrant)warrant).getTimeToPlatform();
162            _forward = ((SCWarrant)warrant).getForward();
163        }
164        for (ThrottleSetting ts : warrant.getThrottleCommands()) {
165            _throttleCommands.add(new ThrottleSetting(ts));
166        }
167        _shareRouteBox.setSelected(warrant.getShareRoute());
168        _warrant.setShareRoute(warrant.getShareRoute());
169        _addTracker.setSelected(warrant.getAddTracker());
170        _warrant.setAddTracker(warrant.getAddTracker());
171        _noRampBox.setSelected(warrant.getNoRamp());
172        _warrant.setNoRamp(warrant.getNoRamp());
173        _runETOnlyBox.setSelected(warrant.getRunBlind());
174        _warrant.setRunBlind(warrant.getRunBlind());
175        setTrainName(warrant.getTrainName());
176        _warrant.setTrainName(warrant.getTrainName());
177        
178        SpeedUtil spU = warrant.getSpeedUtil();
179        setSpeedUtil(_warrant.getSpeedUtil());
180        _speedUtil.setDccAddress(spU.getDccAddress());
181        _speedUtil.setRosterId(spU.getRosterId());
182        if (_speedUtil.getDccAddress() != null) {
183            setTrainInfo(warrant.getTrainName());
184        } else {
185            setTrainName(warrant.getTrainName());
186        }
187
188        ActionListener checkBoxChange = new ActionListener() {
189            @Override
190            public void actionPerformed(ActionEvent e) {
191                _dirty = true;
192            }
193        };
194        _shareRouteBox.addActionListener(checkBoxChange);
195        _addTracker.addActionListener(checkBoxChange);
196        _noRampBox.addActionListener(checkBoxChange);
197        _runETOnlyBox.addActionListener(checkBoxChange);
198    }
199
200    private void init() {
201        _commandModel = new ThrottleTableModel();
202
203        JPanel contentPane = new JPanel();
204        contentPane.setLayout(new BorderLayout(5, 5));
205
206        contentPane.add(makeTopPanel(), BorderLayout.NORTH);
207
208        _tabbedPane = new JTabbedPane();
209        _tabbedPane.addTab(Bundle.getMessage("MakeRoute"), makeFindRouteTabPanel());
210        _tabbedPane.addTab(Bundle.getMessage("RecordPlay"), makeSetPowerTabPanel());
211        contentPane.add(_tabbedPane, BorderLayout.CENTER);
212
213        contentPane.add(makeEditableButtonPanel(), BorderLayout.SOUTH);
214        if (_orders != null && !_orders.isEmpty()) {
215            _tabbedPane.setSelectedIndex(1);
216        }
217        if (!_throttleCommands.isEmpty()) {
218            _showScript.setSelected(true);
219        }
220        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
221        addWindowListener(new java.awt.event.WindowAdapter() {
222            @Override
223            public void windowClosing(java.awt.event.WindowEvent e) {
224                if (_dirty && _warrant.getRunMode() == Warrant.MODE_NONE) {
225                    // if runMode != MODE_NONE, this is probably a panic shutdown. Don't halt it.
226                    if (askClose() && !save()) {
227                        return;
228                    }
229                }
230                closeProfileTable();
231                WarrantTableAction.getDefault().closeWarrantFrame();
232            }
233        });
234
235        makeMenus();
236        setTitle(Bundle.getMessage("editing", _warrant.getDisplayName()));
237        setContentPane(contentPane);
238        setLocation(0, 100);
239        setVisible(true);
240        pack();
241        _dirty = false;
242    }
243
244    private boolean askClose() {
245        if (JOptionPane.showConfirmDialog(this, Bundle.getMessage("saveOrClose"),
246                Bundle.getMessage("QuestionTitle"), JOptionPane.YES_NO_OPTION,
247                JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
248            return true;
249        }
250        return false;
251    }
252 
253    private JPanel makeTopPanel() {
254        JPanel topPanel = new JPanel();
255        topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.PAGE_AXIS));
256
257        JPanel panel = new JPanel();
258        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
259        panel.add(Box.createHorizontalStrut(2 * STRUT_SIZE));
260        panel.add(new JLabel(Bundle.getMessage("LabelSystemName")));
261        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
262        if (_saveWarrant != null) {
263            _sysNameBox = new JTextField(_saveWarrant.getSystemName());
264            _sysNameBox.setEditable(false);
265            _userNameBox = new JTextField(_saveWarrant.getUserName());
266        } else {
267            _sysNameBox = new JTextField(_warrant.getSystemName());
268            _userNameBox = new JTextField(_warrant.getUserName());
269        }
270        _sysNameBox.setBackground(Color.white);
271        panel.add(_sysNameBox);
272        panel.add(Box.createHorizontalStrut(2 * STRUT_SIZE));
273        panel.add(new JLabel(Bundle.getMessage("LabelUserName")));
274        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
275        panel.add(_userNameBox);
276        panel.add(Box.createHorizontalStrut(2 * STRUT_SIZE));
277        topPanel.add(panel);
278        topPanel.add(Box.createVerticalStrut(STRUT_SIZE));
279
280        return topPanel;
281    }
282
283    private JPanel makeFindRouteTabPanel() {
284        JPanel tab1 = new JPanel();
285        tab1.setLayout(new BoxLayout(tab1, BoxLayout.LINE_AXIS));
286        tab1.add(Box.createHorizontalStrut(STRUT_SIZE));
287
288        JPanel topLeft = new JPanel();
289        topLeft.setLayout(new BoxLayout(topLeft, BoxLayout.PAGE_AXIS));
290
291        topLeft.add(makeBlockPanels(false));
292
293        topLeft.add(Box.createVerticalStrut(2 * STRUT_SIZE));
294        tab1.add(topLeft);
295
296        tab1.add(Box.createHorizontalStrut(STRUT_SIZE));
297        JPanel topRight = new JPanel();
298        topRight.setLayout(new BoxLayout(topRight, BoxLayout.LINE_AXIS));
299
300        JPanel panel = new JPanel();
301        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
302        panel.add(Box.createVerticalStrut(2 * STRUT_SIZE));
303        panel.add(calculatePanel(true));
304        panel.add(Box.createVerticalStrut(2 * STRUT_SIZE));
305        panel.add(searchDepthPanel(true));
306
307        JPanel p = new JPanel();
308        p.setLayout(new BoxLayout(p, BoxLayout.PAGE_AXIS));
309        p.add(makeTextBoxPanel(true, _searchStatus, "SearchRoute", null));
310        _searchStatus.setEditable(false);
311        p.add(Box.createVerticalGlue());
312        panel.add(p);
313
314        _searchStatus.setBackground(Color.white);
315        _searchStatus.setEditable(false);
316        panel.add(Box.createRigidArea(new Dimension(10,
317                topLeft.getPreferredSize().height - panel.getPreferredSize().height)));
318        panel.add(Box.createVerticalStrut(STRUT_SIZE));
319        panel.add(Box.createVerticalGlue());
320        topRight.add(panel);
321        topRight.add(Box.createHorizontalStrut(STRUT_SIZE));
322
323        PickListModel<OBlock> pickListModel = PickListModel.oBlockPickModelInstance();
324        topRight.add(new JScrollPane(pickListModel.makePickTable()));
325        Dimension dim = topRight.getPreferredSize();
326        topRight.setMinimumSize(dim);
327        tab1.add(topRight);
328        tab1.add(Box.createHorizontalStrut(STRUT_SIZE));
329        return tab1;
330    }
331
332    private JPanel makeSetPowerTabPanel() {
333        JPanel tab2 = new JPanel();
334        tab2.setLayout(new BoxLayout(tab2, BoxLayout.PAGE_AXIS));
335        tab2.add(makeTabMidPanel());
336
337        JPanel panel = new JPanel();
338        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
339
340        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
341        panel.add(makeBorderedTrainPanel());
342        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
343        JPanel typePanel = makeTypePanel();
344        JPanel edge = new JPanel();
345        edge.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
346                Bundle.getMessage("SelectType"),
347                javax.swing.border.TitledBorder.CENTER,
348                javax.swing.border.TitledBorder.TOP));
349        edge.add(typePanel);
350        panel.add(edge);
351        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
352        
353        JPanel scParamPanel = makeSCParamPanel();
354        edge = new JPanel();
355        edge.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
356                Bundle.getMessage("SetSCParameters"),
357                javax.swing.border.TitledBorder.CENTER,
358                javax.swing.border.TitledBorder.TOP));
359        edge.add(scParamPanel);
360        panel.add(edge);
361        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
362        
363        JPanel learnPanel = makeRecordPanel();
364        edge = new JPanel();
365        edge.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
366                Bundle.getMessage("LearnMode"),
367                javax.swing.border.TitledBorder.CENTER,
368                javax.swing.border.TitledBorder.TOP));
369        edge.add(learnPanel);
370        panel.add(edge);
371        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
372        
373        JPanel paramsPanel = makeRunParmsPanel();
374        edge = new JPanel();
375        edge.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
376                Bundle.getMessage("RunParameters"),
377                javax.swing.border.TitledBorder.CENTER,
378                javax.swing.border.TitledBorder.TOP));
379        edge.add(paramsPanel);
380        panel.add(edge);
381        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
382        
383        JPanel runPanel = makePlaybackPanel();
384        edge = new JPanel();
385        edge.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
386                Bundle.getMessage("RunTrain"),
387                javax.swing.border.TitledBorder.CENTER,
388                javax.swing.border.TitledBorder.TOP));
389        edge.add(runPanel);
390        panel.add(edge);
391        panel.add(Box.createHorizontalStrut(STRUT_SIZE));
392        panel.setPreferredSize(panel.getPreferredSize());
393        tab2.add(panel);
394        
395        _isSCWarrant.addActionListener(new ActionListener() {
396            @Override
397            public void actionPerformed(ActionEvent e) {
398                setPanelEnabled(scParamPanel,true);
399                setPanelEnabled(learnPanel,false);
400                setPanelEnabled(paramsPanel,false);
401                setPanelEnabled(runPanel,false);
402            }
403        });
404        if (_saveWarrant != null && _saveWarrant instanceof SCWarrant) {
405            setPanelEnabled(scParamPanel,true);
406            setPanelEnabled(learnPanel,false);
407            setPanelEnabled(paramsPanel,false);
408            setPanelEnabled(runPanel,false);
409            _isSCWarrant.setVisible(true);
410        }
411
412        _isWarrant.addActionListener(new ActionListener() {
413            @Override
414            public void actionPerformed(ActionEvent e) {
415                setPanelEnabled(scParamPanel,false);
416                setPanelEnabled(learnPanel,true);
417                setPanelEnabled(paramsPanel,true);
418                setPanelEnabled(runPanel,true);
419            }
420        });
421
422        panel = new JPanel();
423        panel.add(makeTextBoxPanel(false, _statusBox, "Status", null));
424        _statusBox.setEditable(false);
425        _statusBox.setMinimumSize(new Dimension(300, _statusBox.getPreferredSize().height));
426        _statusBox.setMaximumSize(new Dimension(900, _statusBox.getPreferredSize().height));
427        panel.add(_statusBox);
428        tab2.add(panel);
429
430        return tab2;
431    }
432
433    private void setPanelEnabled(JPanel panel, Boolean isEnabled) {
434        panel.setEnabled(isEnabled);
435
436        Component[] components = panel.getComponents();
437
438        for(int i = 0; i < components.length; i++) {
439            if("javax.swing.JPanel".equals(components[i].getClass().getName()))  {
440                setPanelEnabled((JPanel) components[i], isEnabled);
441            }
442
443            components[i].setEnabled(isEnabled);
444        }
445    }
446
447    private JPanel makeBorderedTrainPanel() {
448        JPanel trainPanel = makeTrainIdPanel(null);
449
450        JPanel edge = new JPanel();
451        edge.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
452                Bundle.getMessage("SetPower"),
453                javax.swing.border.TitledBorder.CENTER,
454                javax.swing.border.TitledBorder.TOP));
455        edge.add(trainPanel);
456        return edge;
457    }
458
459    private JPanel makeTypePanel() {
460        JPanel typePanel = new JPanel();
461        typePanel.setLayout(new BoxLayout(typePanel, BoxLayout.LINE_AXIS));
462        typePanel.add(Box.createHorizontalStrut(STRUT_SIZE));
463
464        JPanel wTypePanel = new JPanel();
465        wTypePanel.setLayout(new BoxLayout(wTypePanel, BoxLayout.PAGE_AXIS));
466        wTypePanel.add(Box.createVerticalStrut(STRUT_SIZE));
467        ButtonGroup group = new ButtonGroup();
468        group.add(_isSCWarrant);
469        group.add(_isWarrant);
470        _isSCWarrant.setToolTipText(Bundle.getMessage("SCW_Tooltip"));
471        _isWarrant.setToolTipText(Bundle.getMessage("W_Tooltip"));
472        wTypePanel.add(_isSCWarrant);
473        wTypePanel.add(_isWarrant);
474        typePanel.add(wTypePanel);
475        return typePanel;
476    }
477    
478    private void addSpeeds() {
479        float speed = 0.0f;
480        for (ThrottleSetting ts :_throttleCommands) {
481            CommandValue cmdVal = ts.getValue();
482            ValueType valType = cmdVal.getType();
483            switch (valType) {
484                case VAL_FLOAT:
485                    speed = _speedUtil.getTrackSpeed(cmdVal.getFloat());
486                    break;
487                case VAL_TRUE:
488                    _speedUtil.setIsForward(true);
489                    break;
490                case VAL_FALSE:
491                    _speedUtil.setIsForward(false);
492                    break;
493                default:
494            }
495            ts.setTrackSpeed(speed);
496        }
497        _commandModel.fireTableDataChanged();
498        showCommands(true);
499    }
500
501    private JPanel makeSCParamPanel() {
502        JPanel scParamPanel = new JPanel();
503        scParamPanel.setLayout(new BoxLayout(scParamPanel, BoxLayout.PAGE_AXIS));
504        scParamPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
505
506        scParamPanel.add(_runForward);
507        _runForward.setSelected(_forward);
508        
509        JPanel ttpPanel = new JPanel();
510        ttpPanel.setLayout(new BoxLayout(ttpPanel, BoxLayout.LINE_AXIS));
511        JLabel ttp_l = new JLabel(Bundle.getMessage("TTP"));
512        _TTPtextField.setValue(Long.valueOf(_TTP));
513        _TTPtextField.setColumns(6);
514        ttp_l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
515        _TTPtextField.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
516        ttpPanel.add(Box.createVerticalStrut(STRUT_SIZE));
517        ttpPanel.add(ttp_l);
518        ttpPanel.add(_TTPtextField);
519        ttpPanel.setToolTipText(Bundle.getMessage("TTPtoolTip"));
520        scParamPanel.add(ttpPanel);
521        
522        JPanel sfPanel = new JPanel();
523        sfPanel.setLayout(new BoxLayout(sfPanel, BoxLayout.LINE_AXIS));
524        JLabel sf_l = new JLabel(Bundle.getMessage("SF"));
525        _speedFactorTextField.setValue(Long.valueOf((long)(100*_speedFactor)));
526        _speedFactorTextField.setColumns(3);
527        sf_l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
528        _speedFactorTextField.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
529        sfPanel.add(Box.createVerticalStrut(STRUT_SIZE));
530        sfPanel.add(sf_l);
531        sfPanel.add(_speedFactorTextField);
532        sfPanel.setToolTipText(Bundle.getMessage("sfToolTip"));
533        scParamPanel.add(sfPanel);        
534
535        if (_isWarrant.isSelected()) {
536            setPanelEnabled(scParamPanel,false);
537        }
538        return scParamPanel;
539    }
540
541    private JPanel makeRecordPanel() {
542        JPanel learnPanel = new JPanel();
543        learnPanel.setLayout(new BoxLayout(learnPanel, BoxLayout.LINE_AXIS));
544        learnPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
545
546
547        JPanel startStopPanel = new JPanel();
548        startStopPanel.setLayout(new BoxLayout(startStopPanel, BoxLayout.PAGE_AXIS));
549        startStopPanel.add(Box.createVerticalStrut(STRUT_SIZE));
550        JButton startButton = new JButton(Bundle.getMessage("Start"));
551        startButton.addActionListener(new ActionListener() {
552            @Override
553            public void actionPerformed(ActionEvent e) {
554                clearTempWarrant();
555                _tabbedPane.setSelectedIndex(1);
556                showCommands(true);
557                runLearnModeTrain();
558            }
559        });
560        JButton stopButton = new JButton(Bundle.getMessage("Stop"));
561        stopButton.addActionListener(new ActionListener() {
562            @Override
563            public void actionPerformed(ActionEvent e) {
564                stopRunTrain();
565            }
566        });
567        startButton.setAlignmentX(JComponent.CENTER_ALIGNMENT);
568        stopButton.setAlignmentX(JComponent.CENTER_ALIGNMENT);
569        startStopPanel.add(startButton);
570        startStopPanel.add(Box.createVerticalStrut(STRUT_SIZE));
571        startStopPanel.add(stopButton);
572        startStopPanel.add(Box.createRigidArea(new Dimension(30 + stopButton.getPreferredSize().width, 10)));
573        learnPanel.add(startStopPanel);
574
575        return learnPanel;
576    }
577
578    private JPanel makeRunParmsPanel() {
579        JPanel paramsPanel = new JPanel();
580        paramsPanel.setLayout(new BoxLayout(paramsPanel, BoxLayout.LINE_AXIS));
581        paramsPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
582
583        JPanel panel = new JPanel();
584        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
585        panel.add(Box.createVerticalStrut(STRUT_SIZE));
586        panel.add(makeTextBoxPanel(_shareRouteBox, "ShareRoute", "ToolTipShareRoute"));
587        panel.add(makeTextBoxPanel(_addTracker, "AddTracker", "ToolTipAddTracker"));
588        panel.add(makeTextBoxPanel(_noRampBox, "NoRamping", "ToolTipNoRamping"));
589        panel.add(makeTextBoxPanel(_runETOnlyBox, "RunETOnly", "ToolTipRunETOnly"));
590
591        paramsPanel.add(panel);
592        return paramsPanel;
593    }
594
595    private JPanel makePlaybackPanel() {
596        JPanel runPanel = new JPanel();
597        runPanel.setLayout(new BoxLayout(runPanel, BoxLayout.LINE_AXIS));
598        runPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
599
600        JPanel panel = new JPanel();
601        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
602        //panel.add(Box.createVerticalStrut(STRUT_SIZE));
603        JPanel bPanel = new JPanel();
604        bPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
605        JButton runButton = new JButton(Bundle.getMessage("ARun"));
606        runButton.addActionListener(new ActionListener() {
607            @Override
608            public void actionPerformed(ActionEvent e) {
609                runTrain();
610            }
611        });
612        bPanel.add(runButton);
613        panel.add(bPanel);
614        runPanel.add(panel);
615        runPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
616
617        panel = new JPanel();
618        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
619        ButtonGroup group = new ButtonGroup();
620        group.add(_halt);
621        group.add(_resume);
622        group.add(_eStop);
623        group.add(_abort);
624        group.add(_invisible);
625        panel.add(_halt);
626        panel.add(_resume);
627        panel.add(_eStop);
628        panel.add(_abort);
629        runPanel.add(panel);
630
631        _halt.addActionListener(new ActionListener() {
632            @Override
633            public void actionPerformed(ActionEvent e) {
634                doControlCommand(Warrant.HALT);
635            }
636        });
637        _resume.addActionListener(new ActionListener() {
638            @Override
639            public void actionPerformed(ActionEvent e) {
640                doControlCommand(Warrant.RESUME);
641            }
642        });
643        _eStop.addActionListener(new ActionListener() {
644            @Override
645            public void actionPerformed(ActionEvent e) {
646                doControlCommand(Warrant.ESTOP);
647            }
648        });
649        _abort.addActionListener(new ActionListener() {
650            @Override
651            public void actionPerformed(ActionEvent e) {
652                doControlCommand(Warrant.ABORT);
653            }
654        });
655        runPanel.add(panel);
656        return runPanel;
657    }
658
659    private JPanel makeTabMidPanel() {
660        JPanel midPanel = new JPanel();
661        midPanel.setLayout(new BoxLayout(midPanel, BoxLayout.PAGE_AXIS));
662
663        JPanel tablePanel = new JPanel();
664        tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.LINE_AXIS));
665        tablePanel.add(Box.createHorizontalStrut(5));
666        _routePanel = makeRouteTablePanel();
667        tablePanel.add(_routePanel);
668        tablePanel.add(makeThrottleTablePanel());
669        JPanel buttonPanel = new JPanel();
670        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
671        ButtonGroup group = new ButtonGroup();
672        group.add(_showRoute);
673        group.add(_showScript);
674        buttonPanel.add(_showRoute);
675        buttonPanel.add(_showScript);
676        boolean show = (!_throttleCommands.isEmpty());
677        showCommands(show);
678        _showScript.setSelected(show);
679        _showRoute.addActionListener(new ActionListener() {
680            @Override
681            public void actionPerformed(ActionEvent e) {
682                showCommands(false);
683            }
684        });
685        _showScript.addActionListener(new ActionListener() {
686            @Override
687            public void actionPerformed(ActionEvent e) {
688                showCommands(true);
689            }
690        });
691
692        if (_saveWarrant!= null && _saveWarrant instanceof SCWarrant) {
693            _showRoute.setSelected(true);
694            showCommands(false);
695            setPanelEnabled(buttonPanel,false);
696        }
697        _isSCWarrant.addActionListener(new ActionListener() {
698            @Override
699            public void actionPerformed(ActionEvent e) {
700                _showRoute.setSelected(true);
701                showCommands(false);
702                setPanelEnabled(buttonPanel,false);
703            }
704        });
705        _isWarrant.addActionListener(new ActionListener() {
706            @Override
707            public void actionPerformed(ActionEvent e) {
708                setPanelEnabled(buttonPanel,true);
709            }
710        });
711
712        midPanel.add(buttonPanel);
713        midPanel.add(Box.createVerticalStrut(STRUT_SIZE));
714        midPanel.add(tablePanel);
715        midPanel.add(Box.createVerticalStrut(STRUT_SIZE));
716
717        return midPanel;
718    }
719
720    private void showCommands(boolean setCmds) {
721        _routePanel.setVisible(!setCmds);
722        _commandPanel.setVisible(setCmds);
723    }
724
725    private void speedUnitsAction() {
726        switch (_displayPref) {
727            case MPH:
728                _displayPref = Display.KPH;
729                _speedConversion = _scale * 3.6f;
730                setFormatter("kph");
731                break;
732            case KPH:
733                _displayPref = Display.MMPS;
734                _speedConversion = 1000;
735                _unitsLabel.setText(Bundle.getMessage("trackSpeed"));
736                setFormatter("mmps");
737                break;
738            case MMPS:
739                _displayPref = Display.INPS;
740                _speedConversion = 39.37f;
741                setFormatter("inps");
742                break;
743            case INPS:
744            default:
745                _displayPref = Display.MPH;
746                _speedConversion = 2.23694f * _scale;
747                _unitsLabel.setText(Bundle.getMessage("scaleSpeed"));
748                setFormatter("mph");
749                break;
750            }
751            _speedUnits.setDisplayPref(_displayPref);
752            addSpeeds();
753    }
754
755    private void setFormatter(String title) {
756        JTableHeader header = _commandTable.getTableHeader();
757        TableColumnModel colMod = header.getColumnModel();
758        TableColumn tabCol = colMod.getColumn(ThrottleTableModel.SPEED_COLUMN);
759        tabCol.setHeaderValue(Bundle.getMessage(title));
760        header.repaint();
761    }
762
763    private JPanel makeThrottleTablePanel() {
764        _commandTable = new JTable(_commandModel);
765        DefaultCellEditor ed = (DefaultCellEditor)_commandTable.getDefaultEditor(String.class);
766        ed.setClickCountToStart(1);
767
768        TableColumnModel columnModel = _commandTable.getColumnModel();
769        for (int i = 0; i < _commandModel.getColumnCount(); i++) {
770            int width = _commandModel.getPreferredWidth(i);
771            columnModel.getColumn(i).setPreferredWidth(width);
772        }
773        TableColumn cmdColumn = columnModel.getColumn(ThrottleTableModel.COMMAND_COLUMN);
774        cmdColumn.setCellEditor(new CommandCellEditor(new JComboBox<Command>()));
775        cmdColumn.setCellRenderer(new CommandCellRenderer());
776        cmdColumn.setMinWidth(40);
777
778        TableColumn valueColumn = columnModel.getColumn(ThrottleTableModel.VALUE_COLUMN);
779        valueColumn.setCellEditor(new ValueCellEditor(new JTextField()));
780
781        _throttlePane = new JScrollPane(_commandTable);
782        _rowHeight = _commandTable.getRowHeight();
783        Dimension dim = _commandTable.getPreferredSize();
784        dim.height = _rowHeight * 10;
785        _throttlePane.getViewport().setPreferredSize(dim);
786
787        JPanel buttonPanel = new JPanel();
788        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.PAGE_AXIS));
789        buttonPanel.add(Box.createVerticalStrut(4 * STRUT_SIZE));
790
791        JButton insertButton = new JButton(Bundle.getMessage("buttonInsertRow"));
792        insertButton.addActionListener(new ActionListener() {
793            @Override
794            public void actionPerformed(ActionEvent e) {
795                insertRow();
796            }
797        });
798        buttonPanel.add(insertButton);
799        buttonPanel.add(Box.createVerticalStrut(2 * STRUT_SIZE));
800
801        JButton deleteButton = new JButton(Bundle.getMessage("buttonDeleteRow"));
802        deleteButton.addActionListener(new ActionListener() {
803            @Override
804            public void actionPerformed(ActionEvent e) {
805                deleteRow();
806            }
807        });
808        buttonPanel.add(deleteButton);
809        buttonPanel.add(Box.createVerticalStrut(2*STRUT_SIZE));
810
811        if (_displayPref.equals(Display.MMPS) || _displayPref.equals(Display.INPS)) {
812            _unitsLabel = new JLabel(Bundle.getMessage("trackSpeed"));
813        } else {
814            _unitsLabel = new JLabel(Bundle.getMessage("scaleSpeed"));
815        }
816        _unitsLabel.setHorizontalAlignment(SwingConstants.CENTER);
817
818        _speedUnits = new DisplayButton(_displayPref);
819        FontMetrics fm = _speedUnits.getFontMetrics(_speedUnits.getFont());
820        int width = Math.max(fm.stringWidth(Display.KPH.toString()), 
821                Math.max(fm.stringWidth(Display.MPH.toString()), 
822                        fm.stringWidth(Display.MMPS.toString())));
823        Dimension d = _speedUnits.getPreferredSize();
824        d.width = width + 40;
825        _speedUnits.setMaximumSize(d);
826        _speedUnits.setMinimumSize(d);
827        _speedUnits.setPreferredSize(d);
828        _speedUnits.addActionListener((ActionEvent evt)-> speedUnitsAction());
829
830        buttonPanel.add(_unitsLabel);
831        buttonPanel.add(_speedUnits);
832
833        _commandPanel = new JPanel();
834        _commandPanel.setLayout(new BoxLayout(_commandPanel, BoxLayout.PAGE_AXIS));
835        JLabel title = new JLabel(Bundle.getMessage("CommandTableTitle"));
836        JPanel panel = new JPanel();
837        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
838        JPanel p = new JPanel();
839        p.add(_throttlePane);
840        panel.add(p);
841        buttonPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
842        panel.add(buttonPanel);
843        buttonPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
844        _commandPanel.add(title);
845        _commandPanel.add(panel);
846        _commandPanel.add(Box.createGlue());
847        return _commandPanel;
848    }
849
850    private void insertRow() {
851        int row = _commandTable.getSelectedRow();
852        if (row < 0) {
853            showWarning(Bundle.getMessage("selectRow"));
854            return;
855        }
856        row++;
857        _throttleCommands.add(row, new ThrottleSetting());
858        _commandModel.fireTableDataChanged();
859        _commandTable.setRowSelectionInterval(row, row);
860    }
861
862    private void deleteRow() {
863        int row = _commandTable.getSelectedRow();
864        if (row < 0) {
865            showWarning(Bundle.getMessage("selectRow"));
866            return;
867        }
868        ThrottleSetting cmd = _throttleCommands.get(row);
869        if (cmd != null && cmd.getCommand() != null) {
870            if (cmd.getCommand().equals(Command.NOOP)) {
871                showWarning(Bundle.getMessage("cannotDeleteNoop"));
872                return;
873            }
874            long time = cmd.getTime();
875            if ((row + 1) < _throttleCommands.size()) {
876                time += _throttleCommands.get(row + 1).getTime();
877                _throttleCommands.get(row + 1).setTime(time);
878            }
879        }
880        _throttleCommands.remove(row);
881        _commandModel.fireTableDataChanged();
882    }
883
884    /**
885     * Save, Cancel, Delete buttons
886     */
887    private JPanel makeEditableButtonPanel() {
888        JPanel buttonPanel = new JPanel();
889        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
890        buttonPanel.add(Box.createHorizontalStrut(10 * STRUT_SIZE));
891
892        JPanel panel = new JPanel();
893        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
894        JButton saveButton = new JButton(Bundle.getMessage("ButtonSave"));
895        saveButton.addActionListener(new ActionListener() {
896            @Override
897            public void actionPerformed(ActionEvent e) {
898                if (save()) {
899                    WarrantTableAction.getDefault().closeWarrantFrame();
900                }
901            }
902        });
903        panel.add(saveButton);
904        panel.add(Box.createVerticalStrut(STRUT_SIZE));
905        buttonPanel.add(panel);
906        buttonPanel.add(Box.createHorizontalStrut(3 * STRUT_SIZE));
907
908        panel = new JPanel();
909        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
910        JButton copyButton = new JButton(Bundle.getMessage("ButtonCopy"));
911        copyButton.addActionListener(new ActionListener() {
912            @Override
913            public void actionPerformed(ActionEvent e) {
914                WarrantTableAction.getDefault().makeWarrantFrame(_saveWarrant, null);
915            }
916        });
917        panel.add(copyButton);
918        panel.add(Box.createVerticalStrut(STRUT_SIZE));
919        buttonPanel.add(panel);
920        buttonPanel.add(Box.createHorizontalStrut(3 * STRUT_SIZE));
921
922        panel = new JPanel();
923        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
924        JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
925        cancelButton.addActionListener(new ActionListener() {
926            @Override
927            public void actionPerformed(ActionEvent e) {
928                close();
929            }
930        });
931        panel.add(cancelButton);
932        panel.add(Box.createVerticalStrut(STRUT_SIZE));
933        buttonPanel.add(panel);
934        buttonPanel.add(Box.createHorizontalStrut(3 * STRUT_SIZE));
935
936        buttonPanel.add(Box.createHorizontalGlue());
937        return buttonPanel;
938    }
939
940    private void doControlCommand(int cmd) {
941        if (log.isDebugEnabled()) {
942            log.debug("actionPerformed on doControlCommand  cmd= {}", cmd);
943        }
944        int runMode = _warrant.getRunMode();
945        if (runMode == Warrant.MODE_NONE) {
946            JOptionPane.showMessageDialog(this,
947                    Bundle.getMessage("NotRunning", _warrant.getDisplayName()),
948                    Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
949        } else if (runMode == Warrant.MODE_LEARN && cmd != Warrant.ABORT) {
950            JOptionPane.showMessageDialog(this,
951                    Bundle.getMessage("LearnInvalidControl", _warrant.getDisplayName()),
952                    Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
953        } else {
954            _warrant.controlRunTrain(cmd);
955        }
956        _invisible.setSelected(true);
957    }
958
959    private void makeMenus() {
960        setTitle(Bundle.getMessage("TitleWarrant", _warrant.getDisplayName()));
961        JMenuBar menuBar = new JMenuBar();
962        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
963        fileMenu.add(new jmri.configurexml.StoreMenu());
964        menuBar.add(fileMenu);
965        setJMenuBar(menuBar);
966        addHelpMenu("package.jmri.jmrit.logix.CreateEditWarrant", true);
967    }
968
969    private void clearCommands() {
970        _throttleCommands = new ArrayList<>();
971        _commandModel.fireTableDataChanged();
972        _searchStatus.setText("");
973    }
974
975    @Override
976    protected void selectedRoute(ArrayList<BlockOrder> orders) {
977        clearCommands();
978        _tabbedPane.setSelectedIndex(1);
979    }
980
981    /**
982     * Sets address and block orders and does checks
983     * Non-null return is fatal
984     */
985    private String checkTrainId() {
986        String msg = setAddress();       // sets SpeedUtil address in 'this' (WarrantRoute)
987        if (msg == null) {
988            msg = routeIsValid();
989        }
990        if (msg == null) {
991            List<BlockOrder> orders = getOrders();
992            msg = _warrant.setRoute(false, orders);     // calls allocateRoute
993        }
994        if (msg == null) {
995            msg = _warrant.checkforTrackers(); 
996        }
997        if (msg == null) {
998            msg = checkLocoAddress();
999        }
1000        return msg;
1001    }
1002
1003    private String checkThrottleCommands() {
1004        if (_throttleCommands.size() <= getOrders().size() + 1) {
1005            return Bundle.getMessage("NoCommands", _warrant.getDisplayName());
1006        } else {
1007            for (int i=0; i<_throttleCommands.size(); i++) {
1008                ThrottleSetting ts = _throttleCommands.get(i);
1009                Command cmd = ts.getCommand();
1010                CommandValue val = ts.getValue();
1011                if (val==null || cmd==null) {
1012                    return Bundle.getMessage("BadThrottleSetting", i+1);
1013                }
1014                ValueType valType = val.getType();
1015                if (valType==null) {
1016                    return Bundle.getMessage("BadThrottleSetting", i+1);
1017                }
1018                switch (cmd) {
1019                    case SPEED:
1020                        if (valType != ValueType.VAL_FLOAT) {
1021                            return Bundle.getMessage("badThrottleCommand", 
1022                                    i+1, cmd.toString(), valType.toString());
1023                        }
1024                        float f = ts.getValue().getFloat();
1025                        if (f > 1 || f < 0) {
1026                            return Bundle.getMessage("badSpeed", f);
1027                        }
1028                        break;
1029                    case NOOP:
1030                        if (valType != ValueType.VAL_NOOP) {
1031                            return Bundle.getMessage("badThrottleCommand", 
1032                                    i+1, cmd.toString(), valType.toString());
1033                        }
1034                        break;
1035                    case FORWARD:
1036                        if (valType != ValueType.VAL_TRUE && valType != ValueType.VAL_FALSE) {
1037                            return Bundle.getMessage("badThrottleCommand", 
1038                                    i+1, cmd.toString(), valType.toString());
1039                        }
1040                        break;
1041                    case FKEY:
1042                    case LATCHF:
1043                        if (valType != ValueType.VAL_ON && valType != ValueType.VAL_OFF) {
1044                            return Bundle.getMessage("badThrottleCommand", 
1045                                    i+1, cmd.toString(), valType.toString());
1046                        }
1047                        break;
1048                    case SET_SENSOR:
1049                    case WAIT_SENSOR:
1050                        if (valType != ValueType.VAL_ACTIVE && valType != ValueType.VAL_INACTIVE) {
1051                            return Bundle.getMessage("badThrottleCommand", 
1052                                    i+1, cmd.toString(), valType.toString());
1053                        }
1054                        String msg = WarrantFrame.checkBeanName(cmd, ts.getBeanDisplayName());
1055                        if (msg != null) {
1056                            return msg + '\n' + Bundle.getMessage("badThrottleCommand", 
1057                                    i+1, cmd.toString(), valType.toString());
1058                        }
1059                        break;
1060                    case RUN_WARRANT:
1061                        if (valType != ValueType.VAL_INT) {
1062                            return Bundle.getMessage("badThrottleCommand", 
1063                                    i+1, cmd.toString(), valType.toString());
1064                        }
1065                        msg = WarrantFrame.checkBeanName(cmd, ts.getBeanDisplayName());
1066                        if (msg != null) {
1067                            return msg + '\n' + Bundle.getMessage("badThrottleCommand", 
1068                                    i+1, cmd.toString(), valType.toString());
1069                        }
1070                        break;
1071                    case SPEEDSTEP:
1072                        if (valType != ValueType.VAL_STEP) {
1073                            return Bundle.getMessage("badThrottleCommand", 
1074                                    i+1, cmd.toString(), valType.toString());
1075                        }
1076                        break;
1077                    default:
1078                        return Bundle.getMessage("BadThrottleSetting", i+1);
1079                }
1080            }
1081        }
1082        return null;
1083    }
1084
1085    static String checkBeanName(Command command, String beanName) {
1086        switch(command) {
1087            case SET_SENSOR:
1088            case WAIT_SENSOR:
1089                if (InstanceManager.sensorManagerInstance().getSensor(beanName) == null) {
1090                    return Bundle.getMessage("BadSensor", beanName);
1091                }
1092                break;
1093            case RUN_WARRANT:
1094                if (InstanceManager.getDefault(WarrantManager.class).getWarrant(beanName) == null) {
1095                    return Bundle.getMessage("BadWarrant", beanName);
1096                }
1097                break;
1098            default:
1099                if (InstanceManager.getDefault(OBlockManager.class).getOBlock(beanName) == null) {
1100                    return Bundle.getMessage("BlockNotFound", beanName);
1101                }
1102                break;
1103        }
1104        return null;
1105    }
1106
1107    private void runLearnModeTrain() {
1108        _warrant.setSpeedUtil(_speedUtil);  // transfer SpeedUtil to warrant
1109        String msg = null;
1110        if (isRunning()) {
1111            msg = Bundle.getMessage("CannotRun", _warrant.getDisplayName(),
1112                    Bundle.getMessage("TrainRunning", _warrant.getTrainName()));
1113        }
1114        if (msg == null) {
1115            msg = checkTrainId();
1116        }
1117        if (msg == null) {
1118            msg = _warrant.checkRoute();
1119        }
1120        if (msg == null) {
1121            msg = WarrantTableFrame.getDefault().getModel().checkAddressInUse(_warrant);
1122        }
1123        toFront();
1124
1125        if (msg != null) {
1126            JOptionPane.showMessageDialog(this, Bundle.getMessage("LearnError", msg),
1127                    Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
1128            _warrant.deAllocate();
1129            setStatusText(msg, Color.red);
1130            return;
1131        }
1132
1133        if (_throttleCommands.size() > 0) {
1134            if (JOptionPane.showConfirmDialog(this, Bundle.getMessage("deleteCommand"),
1135                    Bundle.getMessage("QuestionTitle"), JOptionPane.YES_NO_OPTION,
1136                    JOptionPane.QUESTION_MESSAGE) == JOptionPane.NO_OPTION) {
1137                return;
1138            }
1139            _throttleCommands = new ArrayList<>();
1140            _commandModel.fireTableDataChanged();
1141        }
1142
1143        msg = _warrant.checkStartBlock();
1144        if (msg != null) {
1145            if (msg.equals("warnStart")) {
1146                msg = Bundle.getMessage("warnStart", getTrainName(), _warrant.getCurrentBlockName());
1147                JOptionPane.showMessageDialog(this, msg,
1148                        Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
1149                setStatusText(msg, Color.red);
1150                return;
1151            } else if (msg.equals("BlockDark")) {
1152                msg = Bundle.getMessage("BlockDark", _warrant.getCurrentBlockName(), getTrainName());
1153                if (JOptionPane.NO_OPTION == JOptionPane.showConfirmDialog(this,
1154                        Bundle.getMessage("OkToRun", msg), Bundle.getMessage("QuestionTitle"),
1155                        JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)) {
1156                    stopRunTrain();
1157                    setStatusText(msg, Color.red);
1158                    return;
1159                }
1160            }
1161            setStatusText(msg, Color.black);
1162        }
1163
1164        if (_learnThrottle == null) {
1165            _learnThrottle = new LearnThrottleFrame(this);
1166        } else {
1167            _learnThrottle.setVisible(true);
1168        }
1169
1170        _warrant.setTrainName(getTrainName());
1171        _startTime = System.currentTimeMillis();
1172        _speed = 0.0f;
1173        
1174        _warrant.addPropertyChangeListener(this);
1175
1176        msg = _warrant.setRunMode(Warrant.MODE_LEARN, _speedUtil.getDccAddress(), _learnThrottle,
1177                _throttleCommands, _runETOnlyBox.isSelected());
1178        if (msg != null) {
1179            stopRunTrain();
1180            JOptionPane.showMessageDialog(this, msg, Bundle.getMessage("WarningTitle"),
1181                    JOptionPane.WARNING_MESSAGE);
1182            setStatusText(msg, Color.red);
1183        }
1184    }
1185
1186    private void runTrain() {
1187        _warrant.setSpeedUtil(_speedUtil);  // transfer SpeedUtil to warrant
1188        String msg = null;
1189        if (isRunning()) {
1190            msg = Bundle.getMessage("CannotRun", _warrant.getDisplayName(),
1191                    Bundle.getMessage("TrainRunning", _warrant.getTrainName()));
1192        }
1193        if (msg == null) {
1194            _warrant.setTrainName(getTrainName());
1195            _warrant.setShareRoute(_shareRouteBox.isSelected());
1196            _warrant.setAddTracker(_addTracker.isSelected());
1197            _warrant.setNoRamp(_noRampBox.isSelected());
1198        }
1199        if (msg == null) {
1200            msg = checkTrainId();
1201        }
1202        if (msg == null) {
1203            msg = checkThrottleCommands();
1204            if (msg == null) {
1205                if (!_warrant.hasRouteSet() && _runETOnlyBox.isSelected()) {
1206                    msg = Bundle.getMessage("BlindRouteNotSet", _warrant.getDisplayName());
1207                }
1208            }
1209        }
1210        if (msg == null) {
1211            WarrantTableModel model = WarrantTableFrame.getDefault().getModel(); 
1212            msg = model.checkAddressInUse(_warrant);
1213            if (msg == null) {
1214                model.addNXWarrant(_warrant);
1215            }
1216        }
1217        toFront();
1218        if (msg != null) {
1219            JOptionPane.showMessageDialog(this, msg, Bundle.getMessage("WarningTitle"),
1220                    JOptionPane.WARNING_MESSAGE);
1221            _warrant.deAllocate();
1222            setStatusText(msg, Color.black);
1223            return;
1224        }
1225        _warrant.addPropertyChangeListener(this);
1226        
1227        msg = _warrant.setRunMode(Warrant.MODE_RUN, _speedUtil.getDccAddress(), null,
1228                _throttleCommands, _runETOnlyBox.isSelected());
1229        if (msg != null) {
1230            clearWarrant();
1231            JOptionPane.showMessageDialog(this, msg,
1232                    Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
1233            setStatusText(msg, Color.red);
1234            return;
1235        } else
1236        
1237        msg = _warrant.checkStartBlock();
1238        if (msg != null) {
1239            if (msg.equals("warnStart")) {
1240                msg = Bundle.getMessage("warnStart", _warrant.getTrainName(), _warrant.getCurrentBlockName());
1241            } else if (msg.equals("BlockDark")) {
1242                msg = Bundle.getMessage("BlockDark", _warrant.getCurrentBlockName(), _warrant.getTrainName());
1243            }
1244            if (JOptionPane.NO_OPTION == JOptionPane.showConfirmDialog(this,
1245                    Bundle.getMessage("OkToRun", msg), Bundle.getMessage("QuestionTitle"),
1246                    JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)) {
1247                clearWarrant();
1248                setStatusText(msg, Color.red);
1249                return;
1250            } else {
1251                setStatusText(_warrant.getRunningMessage(), myGreen);
1252            }
1253        }
1254    }
1255
1256    /*
1257     * Stop a MODE_LEARN warrant, i.e. non-registered member _warrant
1258     */
1259    private void stopRunTrain() {
1260        if (_learnThrottle != null) {
1261            _learnThrottle.dispose();
1262            _learnThrottle = null;
1263        }
1264        if (_warrant == null) {
1265            return;
1266        }
1267
1268        if (_warrant.getRunMode() == Warrant.MODE_LEARN) {
1269            List<BlockOrder> orders = getOrders();
1270            if (orders!=null && orders.size()>1) {
1271                BlockOrder bo = _warrant.getCurrentBlockOrder();
1272                if (bo!=null) {
1273                    OBlock lastBlock = orders.get(orders.size() - 1).getBlock();
1274                    OBlock currentBlock = bo.getBlock();
1275                    if (!lastBlock.equals(currentBlock)) {
1276                        if ((lastBlock.getState() & OBlock.UNDETECTED) != 0
1277                                && currentBlock.equals(orders.get(orders.size() - 2).getBlock())) {
1278                            setThrottleCommand("NoOp", Bundle.getMessage("Mark"), lastBlock.getDisplayName());
1279                            setStatusText(Bundle.getMessage("LearningStop"), myGreen);
1280                        } else {
1281                            JOptionPane.showMessageDialog(this, Bundle.getMessage("IncompleteScript", lastBlock),
1282                                    Bundle.getMessage("WarningTitle"),
1283                                    JOptionPane.WARNING_MESSAGE);
1284                        }
1285                    } else {
1286                        setStatusText(Bundle.getMessage("LearningStop"), myGreen);
1287                    }
1288                }
1289            }
1290        }
1291        clearWarrant();
1292    }
1293
1294    private void clearWarrant() {
1295        if (_warrant != null) {
1296            _warrant.stopWarrant(false, true);
1297            _warrant.removePropertyChangeListener(this);
1298        }
1299    }
1300
1301    protected Warrant getWarrant() {
1302        return _warrant;
1303    }
1304
1305    protected void setStatusText(String msg, Color c) {
1306        _statusBox.setForeground(c);
1307        _statusBox.setText(msg);
1308    }
1309
1310    @Override
1311    protected void maxThrottleEventAction() {
1312    }
1313
1314    /**
1315     * Property names from Warrant: "runMode" - from setRunMode "controlChange"
1316     * - from controlRunTrain "blockChange" - from goingActive "allocate" - from
1317     * allocateRoute, deAllocate "setRoute" - from setRoute, goingActive
1318     * Property names from Engineer: "Command" - from run "SpeedRestriction" -
1319     * ThrottleRamp run Property names from RouteFinder: "RouteSearch" - from
1320     * run
1321     */
1322    @Override
1323    public void propertyChange(java.beans.PropertyChangeEvent e) {
1324        String property = e.getPropertyName();
1325        if (property.equals("DnDrop")) {
1326            doAction(e.getSource());
1327        } else if (e.getSource() instanceof Warrant && _warrant.equals(e.getSource())) {
1328            if (log.isDebugEnabled())
1329                log.debug("propertyChange \"{}\" old= {} new= {} source= {}",
1330                        property, e.getOldValue(), e.getNewValue(), e.getSource().getClass().getName());
1331            String msg = null;
1332            Color color = myGreen;
1333            switch (_warrant.getRunMode()) {
1334                case Warrant.MODE_NONE:
1335                    _warrant.removePropertyChangeListener(this);
1336                    if (e.getPropertyName().equals("runMode")) {
1337                        int newMode = ((Integer) e.getNewValue()).intValue();
1338                        if (newMode==Warrant.MODE_ABORT) {
1339                            msg =Bundle.getMessage("warrantAbort",
1340                                    _warrant.getTrainName(),
1341                                    _warrant.getDisplayName());
1342                        } else {
1343                            int oldMode = ((Integer) e.getOldValue()).intValue();
1344                            if (oldMode != Warrant.MODE_NONE) {
1345                                OBlock curBlock = _warrant.getCurrentBlockOrder().getBlock();
1346                                OBlock lastBlock = _warrant.getLastOrder().getBlock();
1347                                if (lastBlock.equals(curBlock)) {
1348                                    msg = Bundle.getMessage("warrantComplete",
1349                                            _warrant.getTrainName(), _warrant.getDisplayName(),
1350                                            lastBlock.getDisplayName());
1351                                    color = Color.green;
1352                                } else {
1353                                    msg = Bundle.getMessage("warrantEnd",
1354                                            _warrant.getTrainName(), _warrant.getDisplayName(),
1355                                            lastBlock.getDisplayName());
1356                                    color = Color.red;
1357                                }
1358                            }
1359                        }
1360                    }
1361                    break;
1362                case Warrant.MODE_LEARN:
1363                    if (property.equals("blockChange")) {
1364                        OBlock oldBlock = (OBlock) e.getOldValue();
1365                        OBlock newBlock = (OBlock) e.getNewValue();
1366                        if (newBlock == null) {
1367                            stopRunTrain();
1368                            msg =Bundle.getMessage("ChangedRoute",
1369                                            _warrant.getDisplayName(),
1370                                            oldBlock.getDisplayName(),
1371                                            _warrant.getTrainName());
1372                            color = Color.red;
1373                        } else {
1374                            setThrottleCommand("NoOp", Bundle.getMessage("Mark"), ((OBlock) e.getNewValue()).getDisplayName());
1375                            msg = Bundle.getMessage("TrackerBlockEnter",
1376                                            _warrant.getTrainName(),
1377                                            newBlock.getDisplayName());
1378                        }
1379                    } else if (property.equals("abortLearn")) {
1380                        stopRunTrain();
1381                        int oldIdx = ((Integer) e.getOldValue()).intValue();
1382                        int newIdx = ((Integer) e.getNewValue()).intValue();
1383                        if (oldIdx > newIdx) {
1384                            msg = Bundle.getMessage("LearnAbortOccupied",
1385                                            _warrant.getBlockAt(oldIdx),
1386                                            _warrant.getDisplayName());
1387                            color = Color.red;
1388                        } else {
1389                            msg = Bundle.getMessage("warrantAbort",
1390                                            _warrant.getTrainName(),
1391                                            _warrant.getDisplayName());
1392                            color = Color.red;
1393                        }
1394                    } else {
1395                        msg = Bundle.getMessage("Learning", _warrant.getCurrentBlockName());
1396                        color = Color.black;
1397                    }
1398                    break;
1399                case Warrant.MODE_RUN:
1400                case Warrant.MODE_MANUAL:
1401                    if (e.getPropertyName().equals("blockChange")) {
1402                        OBlock oldBlock = (OBlock) e.getOldValue();
1403                        OBlock newBlock = (OBlock) e.getNewValue();
1404                        if (newBlock == null) {
1405                            msg = Bundle.getMessage("ChangedRoute",
1406                                            _warrant.getDisplayName(),
1407                                            oldBlock.getDisplayName(),
1408                                            _warrant.getTrainName());
1409                            color = Color.red;
1410                        } else {
1411                            msg = Bundle.getMessage("TrackerBlockEnter",
1412                                            _warrant.getTrainName(),
1413                                            newBlock.getDisplayName());
1414                        }
1415                    } else if (e.getPropertyName().equals("blockRelease")) {
1416                        return;
1417                    } else if (e.getPropertyName().equals("ReadyToRun")) {
1418                        msg = _warrant.getRunningMessage();
1419                    } else if (e.getPropertyName().equals("SpeedChange")) {
1420                        msg = _warrant.getRunningMessage();
1421                        color = Color.black;
1422                    } else if (e.getPropertyName().equals("SpeedRestriction")) {
1423                        msg = Bundle.getMessage("speedChange", _warrant.getTrainName(),
1424                                _warrant.getCurrentBlockName(), e.getNewValue());
1425                        color = Color.black;
1426                    } else if (e.getPropertyName().equals("runMode")) {
1427                        int oldMode = ((Integer) e.getOldValue()).intValue();
1428                        int newMode = ((Integer) e.getNewValue()).intValue();
1429                        if (oldMode == Warrant.MODE_NONE) {
1430                            if (newMode != Warrant.MODE_NONE) {
1431                                msg = Bundle.getMessage("warrantStart",
1432                                        _warrant.getTrainName(), _warrant.getDisplayName(),
1433                                        _warrant.getCurrentBlockName(),
1434                                        Bundle.getMessage(Warrant.MODES[newMode]));
1435                                if (_warrant.getState()==Warrant.HALT) {
1436                                    JOptionPane.showMessageDialog(this, _warrant.getRunningMessage(),
1437                                        Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
1438                                }
1439                            }
1440                        }
1441                    } else if (e.getPropertyName().equals("controlChange")) {
1442                        int runState = ((Integer) e.getOldValue()).intValue();
1443                        int newCntrl = ((Integer) e.getNewValue()).intValue();
1444                        String stateStr = null;
1445                        if (runState < 0) {
1446                            stateStr = Bundle.getMessage(Warrant.MODES[-runState]);
1447                        } else {
1448                            stateStr = Bundle.getMessage(Warrant.RUN_STATE[runState],
1449                                    _warrant.getCurrentBlockName());
1450                        }
1451                        msg = Bundle.getMessage("controlChange",
1452                                _warrant.getTrainName(), stateStr,
1453                                Bundle.getMessage(Warrant.CNTRL_CMDS[newCntrl]));
1454                                color = Color.black;
1455                    } else if (e.getPropertyName().equals("throttleFail")) {
1456                        msg = Bundle.getMessage("ThrottleFail",
1457                                _warrant.getTrainName(), e.getNewValue());
1458                        color = Color.red;
1459                    } else {
1460                        return;
1461                    }
1462                    break;
1463                default:
1464            }
1465            setStatusText(msg, color);
1466        }
1467        invalidate();
1468    }
1469
1470    protected void setThrottleCommand(String cmd, String value) {
1471        String bName = Bundle.getMessage("NoBlock");
1472        BlockOrder bo = _warrant.getCurrentBlockOrder();
1473        if (bo != null) {
1474            OBlock block = bo.getBlock();
1475            if (block != null) {
1476                bName = block.getDisplayName();
1477            }
1478        }
1479        if  (cmd.equals("Forward")) {
1480            _speedUtil.setIsForward(Boolean.parseBoolean(value));
1481        }
1482        setThrottleCommand(cmd, value, bName);
1483    }
1484    
1485    protected void setSpeedCommand(float speed) {
1486        if (_warrant.getSpeedUtil().profileHasSpeedInfo()) {
1487            _speed = _warrant.getSpeedUtil().getTrackSpeed(speed);  // mm/ms            
1488        } else {
1489            _speed = 0.0f;
1490        }
1491        setThrottleCommand("speed", Float.toString(speed));
1492    }
1493
1494    private void setThrottleCommand(String cmd, String value, String bName) {
1495        long endTime = System.currentTimeMillis();
1496        long time = endTime - _startTime;
1497        _startTime = endTime;
1498        ThrottleSetting ts = new ThrottleSetting(time, cmd, value, bName, _speed);
1499        if (log.isDebugEnabled()) {
1500            log.debug("setThrottleCommand= {}", ts.toString());
1501        }
1502        _throttleCommands.add(ts);
1503        _commandModel.fireTableDataChanged();
1504
1505        scrollCommandTable(_commandModel.getRowCount());
1506    }
1507
1508    private void scrollCommandTable(int row) {
1509        JScrollBar bar = _throttlePane.getVerticalScrollBar();
1510        bar.setValue(row * _rowHeight);
1511        bar.invalidate();
1512    }
1513
1514    /**
1515     * Called by WarrantTableAction before closing the editing of this warrant 
1516     * @return true if this warrant or its pre-editing version is running
1517     */
1518    public boolean isRunning() {
1519        if (_warrant.getRunMode() != Warrant.MODE_NONE ||
1520                (_saveWarrant != null && _saveWarrant.getRunMode() != Warrant.MODE_NONE)) {
1521            JOptionPane.showMessageDialog(this, Bundle.getMessage("CannotEdit", _warrant.getDisplayName()),
1522                    Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
1523            return true;
1524        }
1525        return false;
1526    }
1527
1528    /**
1529     * Verify that commands are correct
1530     * @return true if commands are OK
1531     */
1532    private boolean save() {
1533        boolean fatal = false;
1534        if (isRunning()) {
1535            return false;
1536        }
1537        String msg = routeIsValid();
1538        if (msg != null) {
1539            msg = Bundle.getMessage("SaveError", msg);
1540            fatal = true;
1541        }
1542        if (msg == null) {
1543            msg = checkLocoAddress();
1544        }
1545        if (msg == null && !_isSCWarrant.isSelected()) {
1546            msg = checkThrottleCommands();
1547            if (msg != null) {
1548                msg = Bundle.getMessage("BadData", msg);
1549                fatal = true;
1550            }
1551        }
1552
1553        WarrantManager mgr = InstanceManager.getDefault(WarrantManager.class); 
1554        if (msg == null) {
1555            if (_saveWarrant != null) {
1556                if ((_saveWarrant instanceof SCWarrant && !_isSCWarrant.isSelected()) ||
1557                        (!(_saveWarrant instanceof SCWarrant) && _isSCWarrant.isSelected())) {
1558                    // _saveWarrant already registered, but is not the correct class.
1559                    mgr.deregister(_saveWarrant);
1560                    _warrant = mgr.createNewWarrant(
1561                            _sysNameBox.getText(), _userNameBox.getText(), _isSCWarrant.isSelected(), (long)_TTPtextField.getValue());
1562                } else {
1563                    String uName = _userNameBox.getText();
1564                    if (uName.length() > 0 && !uName.equals(_saveWarrant.getUserName()) &&
1565                           mgr.getWarrant(uName) != null) {
1566                        fatal = true;
1567                        msg = Bundle.getMessage("WarrantExists", _userNameBox.getText());
1568                    } else {
1569                        _warrant = _saveWarrant;    // update registered warrant 
1570                    }
1571                }
1572            } else {
1573                if (_warrant == null) {
1574                    _warrant = mgr.createNewWarrant(
1575                            _sysNameBox.getText(), _userNameBox.getText(), 
1576                            _isSCWarrant.isSelected(), (long)_TTPtextField.getValue());
1577                }
1578            }
1579        }
1580        if (_warrant == null) { // find out why
1581            if (_userNameBox.getText().length() > 0 && mgr.getByUserName(_userNameBox.getText()) != null) {
1582                msg = Bundle.getMessage("WarrantExists", _userNameBox.getText());
1583            } else if (mgr.getBySystemName(_sysNameBox.getText()) != null) {
1584                msg = Bundle.getMessage("WarrantExists", _sysNameBox.getText());
1585            } else {
1586                msg = Bundle.getMessage("IWSystemName",  _sysNameBox.getText());
1587            }
1588            fatal = true;
1589        }
1590        if (msg == null && _userNameBox.getText().length() == 0) {
1591            msg = Bundle.getMessage("NoUserName",  _sysNameBox.getText());            
1592        }
1593        if (msg != null) {
1594            if (fatal) {
1595                JOptionPane.showMessageDialog(this, msg,
1596                        Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
1597                return false;
1598            }
1599            int result = JOptionPane.showConfirmDialog(this, Bundle.getMessage("SaveQuestion", msg), Bundle.getMessage("QuestionTitle"),
1600                    JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
1601            if (result==JOptionPane.NO_OPTION) {
1602                if (_warrant != null) {
1603                    mgr.deregister(_warrant);
1604                }
1605                return false;
1606            }
1607        }
1608
1609        if (_isSCWarrant.isSelected()) {
1610            ((SCWarrant)_warrant).setForward(_runForward.isSelected());
1611            ((SCWarrant)_warrant).setTimeToPlatform((long)_TTPtextField.getValue());
1612            long sf = (long)_speedFactorTextField.getValue();
1613            float sf_float = sf;
1614            ((SCWarrant)_warrant).setSpeedFactor(sf_float / 100);
1615        }
1616        _warrant.setTrainName(getTrainName());
1617        _warrant.setRunBlind(_runETOnlyBox.isSelected());
1618        _warrant.setShareRoute(_shareRouteBox.isSelected());
1619        _warrant.setAddTracker(_addTracker.isSelected());
1620        _warrant.setNoRamp(_noRampBox.isSelected());
1621        _warrant.setUserName(_userNameBox.getText());
1622
1623        _warrant.setViaOrder(getViaBlockOrder());
1624        _warrant.setAvoidOrder(getAvoidBlockOrder());
1625        _warrant.setBlockOrders(getOrders());
1626        _warrant.setThrottleCommands(_throttleCommands);
1627        _warrant.setSpeedUtil(_speedUtil);  // transfer SpeedUtil to warrant
1628        if (_saveWarrant == null) {
1629            try {
1630                mgr.register(_warrant);
1631            } catch (jmri.NamedBean.DuplicateSystemNameException dsne) {
1632                // ignore
1633            }
1634            _saveWarrant = _warrant;
1635        }
1636
1637        if (log.isDebugEnabled()) log.debug("warrant {} saved _train {} name= {}",
1638                _warrant.getDisplayName(), _speedUtil.getRosterId(), getTrainName());
1639        WarrantTableAction.getDefault().updateWarrantMenu();
1640        WarrantTableFrame.getDefault().getModel().fireTableDataChanged();
1641        _dirty = false;
1642        return true;
1643    }
1644
1645    protected List<ThrottleSetting> getThrottleCommands() {
1646        return _throttleCommands;
1647    }
1648
1649    // shut down, but don't dispose
1650    protected void close() {
1651        _dirty = false;
1652        clearTempWarrant();
1653        stopRunTrain();
1654        closeProfileTable();
1655        setVisible(false);
1656    }
1657
1658    //=============== Throttle Command Table ==========================\\
1659    //=============== VALUE_COLUMN editing/rendering ==================\\
1660
1661    static String[]  TRUE_FALSE = {ValueType.VAL_TRUE.toString(), ValueType.VAL_FALSE.toString()};
1662    static String[]  ON_OFF = {ValueType.VAL_ON.toString(), ValueType.VAL_OFF.toString()};
1663    static String[]  SENSOR_STATES = {ValueType.VAL_ACTIVE.toString(), ValueType.VAL_INACTIVE.toString()};
1664
1665    class ValueCellEditor extends DefaultCellEditor {
1666
1667        private ComboDialog editorDialog;
1668        private TextDialog textDialog;
1669        private String currentText;
1670        private JTextField editorComponent;
1671
1672        ValueCellEditor(JTextField textField) {
1673            super(textField);
1674            setClickCountToStart(1);
1675            log.debug("valueCellEditor Ctor");
1676        }
1677
1678        @SuppressWarnings("unchecked") // getComponent call requires an unchecked cast
1679        @Override
1680        public Component getTableCellEditorComponent(JTable table, Object value,
1681                boolean isSelected, int row, int col) {
1682            if (log.isDebugEnabled()) {
1683                log.debug("getValueCellEditorComponent: row= {}, column= {} selected = {} value= {}",
1684                        row, col, isSelected, value);
1685            }
1686            currentText = value.toString();
1687            editorComponent = (JTextField)super.getTableCellEditorComponent(table, value, isSelected, row, col);
1688            Command cmd = (Command)_commandModel.getValueAt(row, ThrottleTableModel.COMMAND_COLUMN);
1689            Rectangle cellRect = table.getCellRect (row, col, false);
1690            Dimension dim = new Dimension(cellRect.width, cellRect.height);
1691
1692            switch (cmd) {
1693                case FORWARD:
1694                    showComboDialog(TRUE_FALSE, dim);
1695                    break;
1696                case FKEY:
1697                case LATCHF:
1698                    showComboDialog(ON_OFF, dim);
1699                    break;
1700                case SET_SENSOR:
1701                case WAIT_SENSOR:
1702                    showComboDialog(SENSOR_STATES, dim);
1703                    break;
1704                case SPEEDSTEP:
1705                    String[] items = new String[SpeedStepMode.values().length];
1706                    int i = 0;
1707                    for (SpeedStepMode sm : SpeedStepMode.values()) {
1708                        items[i++] = sm.name; //sm.toString();
1709                    }
1710                    showComboDialog(items, dim);
1711                    break;
1712                default:
1713                    showTextDialog(dim);
1714                    break;
1715            }
1716            return editorComponent;
1717        }
1718
1719        void showTextDialog(Dimension dim) {
1720            log.debug("valueCellEditor.showTextDialog");
1721            textDialog = new TextDialog();
1722            textDialog._textField.setText(currentText);
1723
1724            class CellMaker implements Runnable {
1725                Dimension dim;
1726                CellMaker(Dimension d) {
1727                    dim = d;
1728                }
1729                @Override
1730                public void run() {
1731                    log.debug("Run valueCellEditor.TextDialog");
1732                    Point p = editorComponent.getLocationOnScreen();
1733                    textDialog.setLocation(p.x, p.y);
1734                    textDialog.setPreferredSize(dim);
1735                    textDialog.pack();
1736                    textDialog.setVisible(true);
1737                }
1738            }
1739            CellMaker t = new CellMaker(dim);
1740            javax.swing.SwingUtilities.invokeLater(t);
1741        }
1742
1743
1744        class TextDialog extends JDialog implements FocusListener {
1745            JTextField _textField;
1746            TextDialog _this;
1747            TextDialog() {
1748                super((JFrame)null, false);
1749                _this = this;
1750                _textField = new JTextField();
1751                _textField.addFocusListener(this);
1752                _textField.setForeground(Color.RED);
1753                getContentPane().add(_textField);
1754                setUndecorated(true);
1755            }
1756            
1757            @Override
1758            public void  focusGained(FocusEvent e) {
1759            }
1760            @Override
1761            public void focusLost(FocusEvent e) {
1762                currentText = _textField.getText();
1763                editorComponent.setText(currentText);
1764                fireEditingStopped();
1765                _this.dispose();
1766            }
1767        }
1768
1769        void showComboDialog(String[] items, Dimension dim) {
1770            editorDialog = new ComboDialog(items);
1771            log.debug("valueCellEditor.showComboDialog");
1772
1773            class CellMaker implements Runnable {
1774                Dimension dim;
1775                CellMaker(Dimension d) {
1776                    dim = d;
1777                }
1778                @Override
1779                public void run() {
1780                    log.debug("Run valueCellEditor.showDialog");
1781                    Point p = editorComponent.getLocationOnScreen();
1782                    editorDialog.setLocation(p.x, p.y);
1783                    editorDialog.setPreferredSize(dim);
1784                    editorDialog.pack();
1785                    editorDialog.setVisible(true);
1786                }
1787            }
1788            CellMaker t = new CellMaker(dim);
1789            javax.swing.SwingUtilities.invokeLater(t);
1790        }
1791
1792        class ComboDialog extends JDialog implements ItemListener, FocusListener {  
1793            JComboBox<String> _comboBox;
1794            ComboDialog _this;
1795
1796            ComboDialog(String[] items) {
1797                super((JFrame)null, false);
1798                _this = this;
1799                _comboBox = new JComboBox<String>();
1800                _comboBox.addItemListener(this);
1801                _comboBox.addFocusListener(this);
1802                _comboBox.setForeground(Color.RED);
1803                for (String item : items) {
1804                    _comboBox.addItem(item);
1805                }
1806                _comboBox.removeItem(Command.NOOP.toString());
1807                getContentPane().add(_comboBox);
1808                setUndecorated(true);
1809            }
1810
1811            @Override
1812            public void itemStateChanged(ItemEvent e) {
1813                currentText = (String)_comboBox.getSelectedItem();
1814                editorComponent.setText(currentText);
1815                fireEditingStopped();
1816                _this.dispose();
1817           }
1818            @Override
1819            public void  focusGained(FocusEvent e) {
1820            }
1821            @Override
1822            public void focusLost(FocusEvent e) {
1823                currentText = (String)_comboBox.getSelectedItem();
1824                editorComponent.setText(currentText);
1825                fireEditingStopped();
1826                _this.dispose();
1827            }
1828        }
1829    }
1830
1831//=============== COMMAND_COLUMN editing/rendering ===============\\
1832
1833    class CommandCellEditor extends DefaultCellEditor {
1834        CommandCellEditor(JComboBox<Command> comboBox) {
1835            super(comboBox);
1836            log.debug("New JComboBox<String> CommandCellEditor");
1837        }
1838
1839        @SuppressWarnings("unchecked") // getComponent call requires an unchecked cast
1840        @Override
1841        public Component getTableCellEditorComponent(JTable table, Object value,
1842                boolean isSelected, int row, int column) {
1843            if (log.isDebugEnabled()) {
1844                log.debug("getTableCellEditorComponent: row= {}, column= {} selected = {}",
1845                        row, column, isSelected);
1846            }
1847            JComboBox<Command> comboBox = (JComboBox<Command>)getComponent();
1848            cellPt = MouseInfo.getPointerInfo().getLocation();
1849            comboBox.removeAllItems();
1850            for (Command cmd : Command.values()) {
1851                comboBox.addItem(cmd);
1852            }
1853            return super.getTableCellEditorComponent(table, value, isSelected, row, column);
1854        }
1855    }
1856
1857    Point cellPt;   // point to display key
1858
1859    class CommandCellRenderer extends DefaultTableCellRenderer {
1860        public CommandCellRenderer() {
1861            super();
1862            log.debug("New JComboBox<String> CommandCellRenderer");
1863        }
1864
1865        @SuppressWarnings("unchecked") // getComponent call requires an unchecked cast
1866        @Override
1867        public Component getTableCellRendererComponent(JTable table, Object value,
1868                boolean isSelected, boolean hasFocus, int row, int column) {
1869            Command cmd = (Command)value;
1870            int key = _throttleCommands.get(row).getKeyNum();
1871            if (cmd == null) {
1872                setText(null);
1873            } else if (Command.FKEY.equals(cmd)) {
1874                setText(Bundle.getMessage("FKey", key));
1875            } else if (Command.LATCHF.equals(cmd)) {
1876                setText(Bundle.getMessage("FKeyMomemtary", key));
1877            } else {
1878                setText(cmd.toString());
1879            }
1880            return this;
1881        }
1882    }
1883
1884    static class EditDialog extends JDialog  {
1885        SpinnerNumberModel _keyNumModel;
1886        ThrottleSetting _ts;
1887        Command _cmd;
1888
1889        EditDialog(JFrame frame, ThrottleSetting ts, Command cmd) {
1890            super(frame, true);
1891            _ts = ts;
1892            _cmd = cmd;
1893            int key = ts.getKeyNum();
1894            if (key < 0) {
1895                key = 0;
1896            }
1897            _keyNumModel = new SpinnerNumberModel(key, 0, 28, 1);
1898            JSpinner keyNums = new JSpinner(_keyNumModel);
1899            JPanel panel = new JPanel();
1900            panel.setLayout(new BorderLayout());
1901            panel.add(new JLabel(Bundle.getMessage("editFunctionKey")), BorderLayout.NORTH);
1902            panel.add(keyNums, BorderLayout.CENTER);
1903
1904            JPanel p = new JPanel();
1905            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
1906            JButton doneButton;
1907            doneButton = new JButton(Bundle.getMessage("ButtonDone"));
1908            doneButton.addActionListener((ActionEvent a) -> done());
1909            p.add(doneButton);
1910
1911            JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
1912            cancelButton.addActionListener((ActionEvent a) -> this.dispose());
1913            p.add(cancelButton);            
1914            panel.add(p, BorderLayout.SOUTH);
1915            getContentPane().add(panel);
1916            setUndecorated(true);
1917        }
1918
1919        public void done() {
1920            int i = (Integer)_keyNumModel.getValue();
1921            _ts.setKeyNum(i);
1922            _ts.setCommand(_cmd);
1923            this.dispose();
1924        }
1925
1926    }
1927    void makeEditWindow(ThrottleSetting ts, Command cmd) {
1928        JDialog dialog = new EditDialog(this, ts, cmd);
1929        dialog.setLocation(cellPt);
1930        dialog.pack();
1931        dialog.setVisible(true);
1932        if (log.isDebugEnabled()) {
1933            log.debug("makeEditWindow: pt at ({}, {})", cellPt.x, cellPt.y);
1934        }
1935    }
1936
1937    static java.text.DecimalFormat twoDigit = new java.text.DecimalFormat("0.00");
1938
1939    /************************* Throttle Table ******************************/
1940    class ThrottleTableModel extends AbstractTableModel {
1941
1942        public static final int ROW_NUM = 0;
1943        public static final int TIME_COLUMN = 1;
1944        public static final int COMMAND_COLUMN = 2;
1945        public static final int VALUE_COLUMN = 3;
1946        public static final int BLOCK_COLUMN = 4;
1947        public static final int SPEED_COLUMN = 5;
1948        public static final int NUMCOLS = 6;
1949
1950        JComboBox<Integer> keyNums = new JComboBox<>();
1951
1952        public ThrottleTableModel() {
1953            super();
1954            for (int i=0; i<29; i++) {
1955                keyNums.addItem(i);
1956            }
1957        }
1958
1959        @Override
1960        public int getColumnCount() {
1961            return NUMCOLS;
1962        }
1963
1964        @Override
1965        public int getRowCount() {
1966            return _throttleCommands.size();
1967        }
1968
1969        @Override
1970        public String getColumnName(int col) {
1971            switch (col) {
1972                case ROW_NUM:
1973                    return "#";
1974                case TIME_COLUMN:
1975                    return Bundle.getMessage("TimeCol");
1976                case COMMAND_COLUMN:
1977                    return Bundle.getMessage("CommandCol");
1978                case VALUE_COLUMN:
1979                    return Bundle.getMessage("ValueCol");
1980                case BLOCK_COLUMN:
1981                    return Bundle.getMessage("BlockCol");
1982                case SPEED_COLUMN:
1983                    return Bundle.getMessage("trackSpeed");
1984                default:
1985                    // fall through
1986                    break;
1987            }
1988            return "";
1989        }
1990
1991        @Override
1992        public boolean isCellEditable(int row, int col) {
1993            if (col == ROW_NUM || col == SPEED_COLUMN) {
1994                return false;
1995            }
1996            return true;
1997        }
1998
1999        @Override
2000        public Class<?> getColumnClass(int col) {
2001            if (col == COMMAND_COLUMN) {
2002                return JComboBox.class;
2003            }
2004            return String.class;
2005        }
2006
2007        public int getPreferredWidth(int col) {
2008            switch (col) {
2009                case ROW_NUM:
2010                    return new JTextField(3).getPreferredSize().width;
2011                case TIME_COLUMN:
2012                    return new JTextField(8).getPreferredSize().width;
2013                case COMMAND_COLUMN:
2014                case VALUE_COLUMN:
2015                    return new JTextField(18).getPreferredSize().width;
2016                case BLOCK_COLUMN:
2017                    return new JTextField(45).getPreferredSize().width;
2018                case SPEED_COLUMN:
2019                    return new JTextField(10).getPreferredSize().width;
2020                default:
2021                    return new JTextField(12).getPreferredSize().width;
2022            }
2023        }
2024
2025        @Override
2026        public Object getValueAt(int row, int col) {
2027            // some error checking
2028            if (row >= _throttleCommands.size()) {
2029                if (log.isDebugEnabled()) log.debug("row {} is greater than throttle command size {}",
2030                        row, _throttleCommands.size());
2031                return "";
2032            }
2033            ThrottleSetting ts = _throttleCommands.get(row);
2034            if (ts == null) {
2035                if (log.isDebugEnabled()) log.debug("Throttle setting is null!");
2036                return "";
2037            }
2038            switch (col) {
2039                case ROW_NUM:
2040                    return Integer.valueOf(row + 1);
2041                case TIME_COLUMN:
2042                    return ts.getTime();
2043                case COMMAND_COLUMN:
2044                    return ts.getCommand();
2045                case VALUE_COLUMN:
2046                    CommandValue cmdVal = ts.getValue();
2047                    if (cmdVal == null) {
2048                        return "";
2049                    }
2050                    return cmdVal.showValue();
2051                case BLOCK_COLUMN:
2052                    return ts.getBeanDisplayName();
2053                case SPEED_COLUMN:
2054                    return twoDigit.format(ts.getTrackSpeed() * _speedConversion);
2055                default:
2056                    return "";
2057            }
2058        }
2059
2060        @Override
2061        @SuppressFBWarnings(value="DB_DUPLICATE_SWITCH_CLAUSES", justification="put least likely cases last for efficiency")
2062        public void setValueAt(Object value, int row, int col) {
2063            if (row >= _throttleCommands.size()) {
2064                return;
2065            }
2066            ThrottleSetting ts = _throttleCommands.get(row);
2067            String msg = null;
2068            switch (col) {
2069                case TIME_COLUMN:
2070                    long time = 0;
2071                    try {
2072                        time = Long.parseLong((String) value);
2073                        if (time < 0) {
2074                            msg = Bundle.getMessage("InvalidTime", (String) value);
2075                        } else {
2076                            ts.setTime(time);
2077                        }
2078                    } catch (NumberFormatException nfe) {
2079                        msg = Bundle.getMessage("InvalidTime", (String) value);
2080                    }
2081                    break;
2082                case COMMAND_COLUMN:
2083                    Command cmd = ((Command) value);
2084                    if (cmd == null) {
2085                        break;
2086                    }
2087                    Command prCmd = ts.getCommand();
2088                    if (prCmd != null && !cmd.hasBlockName() && prCmd.hasBlockName()) {
2089                        ts.setNamedBeanHandle(null);
2090                    }
2091                    switch (cmd) {
2092                        case SPEED:
2093                        case FORWARD:
2094                            ts.setCommand(cmd);
2095                            break;
2096                        case FKEY:
2097                        case LATCHF:
2098                            class CellMaker implements Runnable {
2099                                ThrottleSetting ts;
2100                                Command cmd;
2101                                CellMaker(ThrottleSetting t, Command c) {
2102                                    ts = t;
2103                                    cmd = c;
2104                                }
2105
2106                                @Override
2107                                public void run() {
2108                                    makeEditWindow(ts, cmd);
2109                                }
2110                            }
2111                            CellMaker t = new CellMaker(ts, cmd);
2112                            javax.swing.SwingUtilities.invokeLater(t);
2113                            break;
2114                        case NOOP:
2115                            msg = Bundle.getMessage("cannotEnterNoop", cmd.toString());
2116                            break;
2117                        case SET_SENSOR:
2118                        case WAIT_SENSOR:
2119                        case RUN_WARRANT:
2120                        case SPEEDSTEP:
2121                            ts.setCommand(cmd);
2122                            break;
2123                        default:
2124                            msg = Bundle.getMessage("badCommand", cmd.toString());
2125                    }
2126                    break;
2127                case VALUE_COLUMN:
2128                    if (value == null || ((String) value).length() == 0) {
2129                        break;
2130                    }
2131                    if (ts==null || ts.getCommand()==null ) {
2132                        msg = Bundle.getMessage("nullValue", Bundle.getMessage("CommandCol"));
2133                        break;
2134                    }
2135                    ts.setValue((String) value);
2136                    Command command = ts.getCommand();
2137                    if (command != null) {
2138                        if (command.hasBlockName()) {
2139                            NamedBeanHandle<?> bh = getPreviousBlockHandle(row);
2140                            ts.setNamedBeanHandle(bh);
2141                        }
2142                    }
2143                    break;
2144                case BLOCK_COLUMN:
2145                    if (ts==null || ts.getCommand()==null ) {
2146                        msg = Bundle.getMessage("nullValue", Bundle.getMessage("CommandCol"));
2147                        break;
2148                    }
2149                    command = ts.getCommand();
2150                    if (command == null) {
2151                        break;
2152                    }
2153                    if (!command.hasBlockName()) {
2154                        msg = ts.setNamedBean(command, (String)value);
2155                    } else  if (command.equals(Command.NOOP)) {
2156                        msg = Bundle.getMessage("cannotChangeBlock", (String) value);
2157                    } else {
2158                        NamedBeanHandle<?> bh = getPreviousBlockHandle(row);
2159                        if (bh != null) {
2160                            String name = bh.getBean().getDisplayName();
2161                            if (!name.equals(value)) {
2162                                msg = Bundle.getMessage("commandInBlock", name);
2163                                ts.setNamedBeanHandle(bh);
2164                            }
2165                        }
2166                    }
2167                    break;
2168                case SPEED_COLUMN:
2169                    break;
2170                default:
2171            }
2172            if (msg != null) {
2173                showWarning(msg);
2174            } else {
2175                fireTableRowsUpdated(row, row);
2176                _dirty = true;
2177            }
2178        }
2179
2180        private NamedBeanHandle <? extends NamedBean> getPreviousBlockHandle(int row) {
2181            for (int i = row; i > 0; i--) {
2182                NamedBeanHandle <? extends NamedBean> bh = _throttleCommands.get(i - 1).getNamedBeanHandle();
2183                if (bh != null && (bh.getBean() instanceof OBlock)) {
2184                    return bh;
2185                }
2186            }
2187            return null;
2188        }
2189
2190    }
2191    private final static Logger log = LoggerFactory.getLogger(WarrantFrame.class);
2192}