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