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