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