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