001package jmri.jmrit.logix;
002
003import java.awt.event.ActionEvent;
004import java.text.NumberFormat;
005import java.util.ArrayList;
006import java.util.List;
007import java.util.ListIterator;
008
009import javax.annotation.CheckForNull;
010import javax.annotation.Nonnull;
011
012import javax.swing.Box;
013import javax.swing.BoxLayout;
014import javax.swing.ButtonGroup;
015import javax.swing.JButton;
016import javax.swing.JCheckBox;
017import javax.swing.JLabel;
018import javax.swing.JMenuBar;
019import javax.swing.JPanel;
020import javax.swing.JRadioButton;
021import javax.swing.JSpinner;
022import javax.swing.JTextField;
023import javax.swing.SpinnerNumberModel;
024
025import jmri.JmriException;
026import jmri.SpeedStepMode;
027import jmri.jmrit.logix.ThrottleSetting.Command;
028import jmri.jmrit.logix.ThrottleSetting.ValueType;
029import jmri.util.swing.JmriJOptionPane;
030
031/**
032 * Frame for defining and launching an entry/exit warrant. An NX warrant is a
033 * warrant that can be defined on the run without a pre-recorded learn mode
034 * session using a set script for ramping startup and stop throttle settings.
035 * <p>
036 * The route can be defined in a form or by mouse clicking on the OBlock
037 * IndicatorTrack icons.
038 * <br>
039 * <hr>
040 * This file is part of JMRI.
041 * <p>
042 * JMRI is free software; you can redistribute it and/or modify it under the
043 * terms of version 2 of the GNU General Public License as published by the Free
044 * Software Foundation. See the "COPYING" file for a copy of this license.
045 * <p>
046 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
047 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
048 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
049 *
050 * @author Pete Cressman Copyright (C) 2009, 2010, 2015
051 */
052public class NXFrame extends WarrantRoute {
053
054    private float _maxThrottle = 0.0f;
055    private float _startDist;   // mm start distance to portal
056    private float _stopDist;    // mm stop distance from portal
057
058    private final JTextField _maxThrottleBox = new JTextField(6);
059    private final JTextField _maxSpeedBox = new JTextField(6);
060    private final JLabel _maxSpeedBoxLabel = new JLabel(Bundle.getMessage("scaleSpeed"));
061
062    private final JTextField _originDist = new JTextField(6);
063    private DisplayButton _originUnits;
064    private final JTextField _destDist = new JTextField(6);
065    private DisplayButton _destUnits;
066    private final JSpinner _timeIncre = new JSpinner(new SpinnerNumberModel(750, 200, 9000, 1));
067    private final JTextField _rampIncre = new JTextField(6);
068    private final JRadioButton _forward = new JRadioButton();
069    private final JRadioButton _reverse = new JRadioButton();
070    private final JCheckBox _noRamp = new JCheckBox();
071    private final JCheckBox _noSound = new JCheckBox();
072    private final JCheckBox _stageEStop = new JCheckBox();
073    private final JCheckBox _shareRouteBox = new JCheckBox();
074    private final JCheckBox _haltStartBox = new JCheckBox();
075    private final JCheckBox _addTracker = new JCheckBox();
076    private final JRadioButton _runAuto = new JRadioButton(Bundle.getMessage("RunAuto"));
077    private final JRadioButton _runManual = new JRadioButton(Bundle.getMessage("RunManual"));
078
079    private JPanel _routePanel = new JPanel();
080    private JPanel _autoRunPanel;
081    private final JPanel __trainHolder = new JPanel();
082    private JPanel _switchPanel;
083    private JPanel _trainPanel;
084
085    protected NXFrame() {
086        super();
087        init();
088    }
089
090    private void init() {
091        log.debug("newInstance");
092        makeMenus();
093
094        _routePanel = new JPanel();
095        _routePanel.setLayout(new BoxLayout(_routePanel, BoxLayout.PAGE_AXIS));
096        _routePanel.add(Box.createVerticalGlue());
097        _routePanel.add(makeBlockPanels(true));
098
099        _forward.setSelected(true);
100        _speedUtil.setIsForward(true);
101        _stageEStop.setSelected(false);
102        _haltStartBox.setSelected(false);
103        _runAuto.setSelected(true);
104
105        _autoRunPanel = makeAutoRunPanel();
106        _switchPanel = makeSwitchPanel();
107        _maxSpeedBox.setEnabled(false);
108
109        JPanel mainPanel = new JPanel();
110        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
111        mainPanel.add(_routePanel);
112        getContentPane().add(mainPanel);
113
114        if (_maxThrottle <= 0.1f) {
115            _maxThrottle = WarrantPreferences.getDefault().getThrottleScale()*100;
116        }
117        _maxThrottleBox.setText(NumberFormat.getNumberInstance().format(_maxThrottle));
118        maxThrottleEventAction();
119
120        addWindowListener(new java.awt.event.WindowAdapter() {
121            @Override
122            public void windowClosing(java.awt.event.WindowEvent e) {
123                WarrantTableAction.getDefault().closeNXFrame();
124            }
125        });
126        setAlwaysOnTop(true);
127        setVisible(true);
128        pack();
129    }
130
131    protected boolean isRouteSeaching() {
132        return _routePanel.isVisible();
133    }
134
135    private void setPanel() {
136        __trainHolder.add(_trainPanel);
137    }
138    private void setPanel(JPanel p) {
139        JPanel con = (JPanel)getContentPane().getComponent(0);
140        con.removeAll();
141        con.add(p);
142        con.add(_switchPanel);
143        pack();
144    }
145
146    private JPanel makeSwitchPanel() {
147        ButtonGroup bg = new ButtonGroup();
148        bg.add(_runAuto);
149        bg.add(_runManual);
150        _runAuto.addActionListener((ActionEvent event) -> {
151            setPanel();
152            setPanel(_autoRunPanel);
153        });
154        _runManual.addActionListener((ActionEvent event) -> {
155            setPanel(_trainPanel);
156            _stageEStop.setSelected(false);
157            _shareRouteBox.setSelected(false);
158            _haltStartBox.setSelected(false);
159            _addTracker.setSelected(false);
160        });
161        JPanel pp = new JPanel();
162        pp.setLayout(new BoxLayout(pp, BoxLayout.LINE_AXIS));
163        pp.add(Box.createHorizontalGlue());
164        pp.add(_runAuto);
165        pp.add(Box.createHorizontalStrut(STRUT_SIZE));
166        pp.add(_runManual);
167        pp.add(Box.createHorizontalGlue());
168
169        JPanel p = new JPanel();
170        p.add(Box.createGlue());
171        JButton button = new JButton(Bundle.getMessage("ButtonRoute"));
172        button.addActionListener((ActionEvent e) -> {
173            clearTempWarrant();
174            JPanel con = (JPanel)getContentPane().getComponent(0);
175            con.removeAll();
176            con.add(_routePanel);
177            pack();
178        });
179        p.add(button);
180        p.add(Box.createHorizontalStrut(2 * STRUT_SIZE));
181        button = new JButton(Bundle.getMessage("ButtonRunNX"));
182        button.addActionListener((ActionEvent e) -> {
183            clearTempWarrant();
184            makeAndRunWarrant();
185        });
186        p.add(button);
187        p.add(Box.createHorizontalStrut(2 * STRUT_SIZE));
188        button = new JButton(Bundle.getMessage("ButtonCancel"));
189        button.addActionListener((ActionEvent e) -> WarrantTableAction.getDefault().closeNXFrame());
190        p.add(button);
191        p.add(Box.createGlue());
192
193        JPanel panel = new JPanel();
194        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
195        panel.add(pp);
196        panel.add(p);
197        return panel;
198    }
199
200    @Override
201    protected void maxThrottleEventAction() {
202        NumberFormat formatter = NumberFormat.getNumberInstance();
203        float num;
204        try {
205            num =  formatter.parse(_maxThrottleBox.getText()).floatValue();
206            num = Math.min(100.0f, Math.max(num,  0.f));
207            _maxThrottleBox.setText(formatter.format(num));
208        } catch (java.text.ParseException pe) {
209            _maxThrottleBox.setText(null);
210            _maxSpeedBox.setText(null);
211            return;
212        }
213        float speed = _speedUtil.getTrackSpeed(num/100);    // returns mm/ms (meters/sec)
214        switch(_displayPref) {
215            case MPH:
216                // Convert meters/sec to scale miles/hr
217                _maxSpeedBox.setText(formatter.format(speed * _scale * 2.2369363f));
218                break;
219            case KPH:
220                // Convert meters/sec to scale kilometers/hr
221                _maxSpeedBox.setText(formatter.format(speed * _scale * 3.6f));
222                break;
223            case MMPS:
224                // Convert meters/sec to millimeters/sec
225                _maxSpeedBox.setText(formatter.format(speed * 1000));  // mm/sec
226                break;
227            case INPS:
228            default:
229                // Convert meters/sec to inchec/sec
230                _maxSpeedBox.setText(formatter.format(speed * 39.37f));  // in/sec
231        }
232    }
233
234    private void unitsEventAction(@Nonnull JButton button, JTextField field) {
235        try {
236            getDistance(_originDist, _orders.get(0));
237            getDistance(_destDist, _orders.get(_orders.size()-1));
238        } catch (JmriException je) {
239            JmriJOptionPane.showMessageDialog(this, je.getMessage(),
240                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
241            return;
242        }
243        if (button.getText().equals(Display.IN.toString())) {
244            _units = Display.CM;
245        } else {
246            _units = Display.IN;
247        }
248        setFieldText(_originUnits, _originDist);
249        setFieldText(_destUnits, _destDist);
250    }
251    // convert to units change
252    private void setFieldText(@Nonnull JButton button, @Nonnull JTextField field) {
253        NumberFormat formatter = NumberFormat.getNumberInstance();
254        formatter.setMaximumFractionDigits(2);
255        float num = 0;
256        try {
257            num =  formatter.parse(field.getText()).floatValue();
258        } catch (java.text.ParseException pe) {
259            // errors reported later
260        }
261        if (_units.equals(Display.IN)) {
262            num *= 0.393701f;
263        } else {
264            num *= 2.54f;
265        }
266        button.setText(_units.toString());
267        field.setText(formatter.format(num));
268    }
269
270    private JPanel makeAutoRunPanel() {
271        JPanel p1 = new JPanel();
272        p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
273
274        DisplayButton speedUnitsButton = new DisplayButton(_displayPref);
275        _originUnits = new DisplayButton(_units);
276        _destUnits = new DisplayButton(_units);
277
278        _maxThrottleBox.addActionListener((ActionEvent evt)-> maxThrottleEventAction());
279
280        _maxSpeedBox.addActionListener((ActionEvent evt)-> {
281            NumberFormat formatter = NumberFormat.getNumberInstance();
282            formatter.setMaximumFractionDigits(2);
283            float num;
284            try {
285                num =  formatter.parse(_maxSpeedBox.getText()).floatValue();
286            } catch (java.text.ParseException pe) {
287                _maxSpeedBox.setText("");
288                return;
289            }
290            if (num < 0) {
291                _maxSpeedBox.setText(formatter.format(0));
292                _maxThrottleBox.setText(formatter.format(0));
293                return;
294            }
295            // maxSpeed is speed at full throttle in mm/sec
296            float maxSpeed = _speedUtil.getTrackSpeed(1);   // mm/ms, i.e. m/s
297            // maximum number is maxSpeed when converted to selected units
298            float maxNum;
299            // convert to display units. Note real world speed is converted to scaled world speed
300            // display label changes "Scale speed" to "Track Speed" accordingly
301            switch (_displayPref) {
302                case MPH:
303                    maxNum = maxSpeed * 2.2369363f *_scale; // convert meters/sec to miles/hr
304                    break;
305                case KPH:
306                    maxNum = maxSpeed * 3.6f * _scale;  // convert meters/sec to to kilometers/hr
307                    break;
308                case MMPS:
309                    maxNum = maxSpeed * 1000;   // convert meters/sec to milimeters/sec
310                    break;
311                default:
312                    maxNum = maxSpeed * 39.37f; // convert meters/sec to inches/sec
313                    break;
314            }
315            if (num > maxNum) {
316                String name = _speedUtil.getRosterId();
317                if (name == null || name.charAt(0) == '$') {
318                    name = getTrainName();
319                    if (name == null || name.isEmpty()) {
320                        name = Bundle.getMessage("Unknown");
321                    }
322                }
323                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("maxSpeedLimit",
324                        name, formatter.format(maxNum), speedUnitsButton.getText()),
325                        Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
326                _maxSpeedBox.setText(formatter.format(maxNum));
327                _maxThrottleBox.setText(formatter.format(100));
328                return;
329            }
330            // convert to display num in selected units to track speed in meters/sec (mm/ms)
331            // reciprocal of above
332            switch (_displayPref) {
333                case MPH:
334                    num = num * 0.44704f / _scale;  // convert scale miles/hr to mm/msec
335                    break;
336                case KPH:
337                    num = num * 0.277778f / _scale;  // convert scale kilometers/hr to mm/msec
338                    break;
339                case MMPS:
340                    num /= 1000;  // convert mm/sec to mm/msec
341                    break;
342                default:
343                    num /= 39.37f;  // convert inches/sec to mm/msec
344                    break;
345            }
346            // get throttla setting and display as percent full throttle.
347            float throttle = _speedUtil.getThrottleSettingForSpeed(num)*100;
348            _maxThrottleBox.setText(formatter.format(throttle));
349        });
350
351        // User makes a choice for their desired units (_displayPref) to show max speed
352        speedUnitsButton.addActionListener((ActionEvent evt)-> {
353            NumberFormat formatter = NumberFormat.getNumberInstance();
354            float num;
355            try {
356                num =  formatter.parse(_maxSpeedBox.getText()).floatValue();
357            } catch (java.text.ParseException pe) {
358                _maxSpeedBox.setText(null);
359                return;
360            }
361            // display preference for units cycles through 4 choices
362            // convert old choice to new
363            switch (_displayPref) {
364                case MPH:
365                    _displayPref = Display.KPH;
366                    _maxSpeedBox.setText(formatter.format(num * 1.60934f)); // miles/hr to km/hr
367                    break;
368                case KPH:
369                    _displayPref = Display.MMPS;
370                    _maxSpeedBox.setText(formatter.format(num * 0277.778f / _scale));   // scale km/hr to mm/sec
371                    _maxSpeedBoxLabel.setText(Bundle.getMessage("trackSpeed"));
372                    break;
373                case MMPS:
374                    _displayPref = Display.INPS;
375                    _maxSpeedBox.setText(formatter.format(num * 0.03937f)); // mm/sec to in/sec
376                    break;
377                default:
378                    _displayPref = Display.MPH;
379                    _maxSpeedBox.setText(formatter.format(num * 0.056818f * _scale)); // inches/sec to scale miles/hr
380                    _maxSpeedBoxLabel.setText(Bundle.getMessage("scaleSpeed"));
381                    break;
382                }
383                // display label changes "Scale speed" to "Track Speed" accordingly
384                speedUnitsButton.setDisplayPref(_displayPref);
385            });
386
387        p1.add(makeTextAndButtonPanel(_maxThrottleBox, new JLabel(Bundle.getMessage("percent")),
388                new JLabel(Bundle.getMessage("MaxSpeed")), "ToolTipPercentThrottle"));
389        p1.add(makeTextAndButtonPanel(_maxSpeedBox, speedUnitsButton,
390                _maxSpeedBoxLabel, "ToolTipScaleSpeed"));
391
392        _originUnits.addActionListener((ActionEvent evt)->
393            unitsEventAction(_originUnits, _originDist));
394        _destUnits.addActionListener((ActionEvent evt)->
395            unitsEventAction(_destUnits, _destDist));
396
397        p1.add(makeTextAndButtonPanel(_originDist, _originUnits,
398                new JLabel(Bundle.getMessage("startDistance")), "ToolTipStartDistance"));
399        p1.add(makeTextAndButtonPanel(_destDist, _destUnits,
400                new JLabel(Bundle.getMessage("stopDistance")), "ToolTipStopDistance"));
401        p1.add(WarrantPreferencesPanel.timeIncrementPanel(false, _timeIncre));
402        p1.add(WarrantPreferencesPanel.throttleIncrementPanel(false, _rampIncre));
403        _rampIncre.addActionListener((ActionEvent e)->{
404                String text = _rampIncre.getText();
405                boolean showdialog;
406                try {
407                    float incr = NumberFormat.getNumberInstance().parse(text).floatValue();
408                    showdialog = (incr < 0.5f || incr > 25f);
409                } catch (java.text.ParseException pe) {
410                    showdialog = true;
411                }
412                if (showdialog) {
413                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("rampIncrWarning", text),
414                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
415                }
416            });
417        ButtonGroup bg = new ButtonGroup();
418        bg.add(_forward);
419        bg.add(_reverse);
420        JPanel pp = new JPanel();
421        pp.setLayout(new BoxLayout(pp, BoxLayout.LINE_AXIS));
422        pp.add(Box.createHorizontalGlue());
423        pp.add(makeTextBoxPanel(false, _forward, "forward", null));
424        pp.add(makeTextBoxPanel(false, _reverse, "reverse", null));
425        pp.add(Box.createHorizontalGlue());
426        p1.add(pp);
427
428        __trainHolder.setLayout(new BoxLayout(__trainHolder, BoxLayout.PAGE_AXIS));
429        _trainPanel = makeTrainIdPanel(null);
430        __trainHolder.add(_trainPanel);
431
432        JPanel p2 = new JPanel();
433        p2.setLayout(new BoxLayout(p2, BoxLayout.PAGE_AXIS));
434        p2.add(__trainHolder);
435        p2.add(makeTextBoxPanel(_noRamp, "NoRamping", "ToolTipNoRamping"));
436        p2.add(makeTextBoxPanel(_noSound, "NoSound", "ToolTipNoSound"));
437        p2.add(makeTextBoxPanel(_stageEStop, "StageEStop", null));
438        p2.add(makeTextBoxPanel(_haltStartBox, "HaltAtStart", null));
439        p2.add(makeTextBoxPanel(_shareRouteBox, "ShareRoute", "ToolTipShareRoute"));
440        p2.add(makeTextBoxPanel(_addTracker, "AddTracker", "ToolTipAddTracker"));
441
442
443        JPanel autoRunPanel = new JPanel();
444        autoRunPanel.setLayout(new BoxLayout(autoRunPanel, BoxLayout.PAGE_AXIS));
445        JPanel ppp = new JPanel();
446        ppp.setLayout(new BoxLayout(ppp, BoxLayout.LINE_AXIS));
447        ppp.add(Box.createHorizontalStrut(STRUT_SIZE));
448        ppp.add(p1);
449        ppp.add(Box.createHorizontalGlue());
450        ppp.add(p2);
451        ppp.add(Box.createHorizontalStrut(STRUT_SIZE));
452        autoRunPanel.add(ppp);
453
454        _forward.addActionListener((ActionEvent evt)-> maxThrottleEventAction());
455        _reverse.addActionListener((ActionEvent evt)-> maxThrottleEventAction());
456
457        return autoRunPanel;
458    }
459
460    private void updateAutoRunPanel() {
461        _startDist = getPathLength(_orders.get(0)) * 0.4f;
462        _stopDist = getPathLength(_orders.get(_orders.size()-1)) * 0.6f;
463        NumberFormat formatter = NumberFormat.getNumberInstance();
464        if (_units.equals(Display.IN)) {
465            // convert millimeters to inches
466            _originDist.setText(formatter.format(_startDist * 0.0393701));
467            _destDist.setText(formatter.format(_stopDist * 0.0393701));
468        } else {
469         // convert millimeters to centimeters
470            _originDist.setText(formatter.format(_startDist / 10));
471            _destDist.setText(formatter.format(_stopDist / 10));
472        }
473         _autoRunPanel.repaint();
474    }
475
476    private void makeMenus() {
477        setTitle(Bundle.getMessage("AutoWarrant"));
478        JMenuBar menuBar = new JMenuBar();
479        setJMenuBar(menuBar);
480        addHelpMenu("package.jmri.jmrit.logix.NXWarrant", true);
481    }
482
483    @Override
484    public void propertyChange(java.beans.PropertyChangeEvent e) {
485        String property = e.getPropertyName();
486        log.trace("propertyChange \"{}\" old= {} new= {} source= {}",property,
487            e.getOldValue(), e.getNewValue(), e.getSource().getClass().getName());
488        if (property.equals("DnDrop")) {
489            doAction(e.getSource());
490        }
491    }
492
493    /**
494     * Called by {@link jmri.jmrit.logix.RouteFinder#run()}. If all goes well,
495     * WarrantTableFrame.runTrain(warrant) will run the warrant
496     *
497     * @param orders list of block orders
498     */
499    @Override
500    protected void selectedRoute(ArrayList<BlockOrder> orders) {
501        JPanel con = (JPanel)getContentPane().getComponent(0);
502        con.removeAll();
503        if (_runAuto.isSelected()) {
504            con.add(_autoRunPanel);
505        } else {
506            con.add(_trainPanel);
507        }
508        con.add(_switchPanel);
509        updateAutoRunPanel();
510        pack();
511    }
512
513    private void makeAndRunWarrant() {
514        String msg = getBoxData();
515        if (msg == null) {
516            msg = checkLocoAddress();
517        }
518        if (msg != null) {
519            JmriJOptionPane.showMessageDialog(this, msg,
520                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
521            return;
522        }
523        // There is a dccAddress so a throttle can be acquired
524        String s = ("" + Math.random()).substring(2);
525        Warrant warrant = new Warrant("IW" + s, "NX(" + getAddress() + ")");
526        warrant.setBlockOrders(_orders);
527        warrant.setTrainName(getTrainName());
528        warrant.setNoRamp(_noRamp.isSelected());
529        _speedUtil.setIsForward(_forward.isSelected());
530        warrant.setSpeedUtil(_speedUtil);   // transfer SpeedUtil to warrant
531        log.debug("Warrant {}. Route and loco set.", warrant.getDisplayName());
532        int mode;
533        if (!_runManual.isSelected()) {
534            mode = Warrant.MODE_RUN;
535            warrant.setShareRoute(_shareRouteBox.isSelected());
536            warrant.setAddTracker(_addTracker.isSelected());
537            warrant.setHaltStart(_haltStartBox.isSelected());
538            msg = makeCommands(warrant);
539        } else {
540            mode = Warrant.MODE_MANUAL;
541        }
542        if (msg == null) {
543            WarrantTableFrame tableFrame = WarrantTableFrame.getDefault();
544            tableFrame.setVisible(true);
545            warrant.setNXWarrant(true);
546            tableFrame.getModel().addNXWarrant(warrant);   //need to catch propertyChange at start
547            log.debug("NXWarrant added to table");
548            msg = tableFrame.runTrain(warrant, mode);
549            if (msg != null) {
550                log.debug("WarrantTableFrame run warrant. msg= {} Remove warrant {}",msg,warrant.getDisplayName());
551                tableFrame.getModel().removeWarrant(warrant, false);
552            }
553        }
554        if (msg != null) {
555            JmriJOptionPane.showMessageDialog(this, msg,
556                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
557        } else {
558            WarrantTableAction.getDefault().closeNXFrame();
559        }
560    }
561
562    // for testing
563    protected void setMaxSpeed(float s) {
564        _maxThrottle = s;
565        _maxThrottleBox.setText(NumberFormat.getNumberInstance().format(s));
566    }
567
568    private String getBoxData() {
569        String text = null;
570        float maxSpeed;
571        NumberFormat formatter = NumberFormat.getNumberInstance();
572        try {
573            text = _maxThrottleBox.getText();
574            maxSpeed = formatter.parse(text).floatValue();
575        } catch (java.text.ParseException pe) {
576            if (text==null) {
577                text = "\"\"";
578            }
579            return Bundle.getMessage("badSpeed100", text);
580        }
581
582        try {
583            _startDist = getDistance(_originDist, _orders.get(0));
584            if (_startDist < 2) {
585                _startDist = 2; // leave block by at least 2 millimeters - cannot be 0
586            }
587        } catch (JmriException je) {
588            return je.getMessage();
589        }
590
591        try {
592            _stopDist = getDistance(_destDist, _orders.get(_orders.size()-1));
593            if (_stopDist < 2) {
594                _stopDist = 2; // enter block by at least 2 millimeters - cannot be 0
595            }
596        } catch (JmriException je) {
597            return je.getMessage();
598        }
599
600        if (maxSpeed > 100f || maxSpeed < 0.001f) {
601            return Bundle.getMessage("badSpeed100", maxSpeed);
602        }
603        _maxThrottle = maxSpeed / 100;
604
605        String msg = setAddress();
606        if (msg != null) {
607            return msg;
608        }
609
610        int time = (Integer)_timeIncre.getValue();
611        _speedUtil.setRampTimeIncrement(time);
612
613        try {
614            text = _rampIncre.getText();
615            float incre = NumberFormat.getNumberInstance().parse(text).floatValue();
616            if (incre < 0.5f || incre > 25f) {
617                return Bundle.getMessage("rampIncrWarning", text);
618            } else {
619                _speedUtil.setRampThrottleIncrement(incre/100);
620            }
621        } catch (java.text.ParseException pe) {
622            return Bundle.getMessage("MustBeFloat", text);
623        }
624        return null;
625    }
626
627    private float getDistance(@Nonnull JTextField field, @Nonnull BlockOrder bo) throws JmriException {
628        NumberFormat formatter = NumberFormat.getNumberInstance();
629        float distance;
630        String text = field.getText();
631        try {
632            distance = formatter.parse(text).floatValue();
633        } catch (java.text.ParseException pe) {
634            throw new JmriException(Bundle.getMessage("MustBeFloat", text));
635        }
636        float pathLen = getPathLength(bo);
637        if (pathLen <= 0) {
638            throw new JmriException(Bundle.getMessage("zeroPathLength", 
639                bo.getPathName(), bo.getBlock().getDisplayName()));
640        }
641        if (_units.equals(Display.IN)){
642            distance *= 25.4f;  // convert inches to millimeters
643            if (distance > pathLen) {
644                field.setText(formatter.format(pathLen*0.03937008f));
645                throw new JmriException(Bundle.getMessage(
646                        "BadLengthIn", bo.getPathName(), bo.getBlock().getDisplayName(), pathLen*0.03937008f, text));
647            } else if (distance < 0) {
648                field.setText("0");
649                throw new JmriException(Bundle.getMessage(
650                        "BadLengthIn", bo.getPathName(), bo.getBlock().getDisplayName(), pathLen*0.03937008f, text));
651            }
652        } else {
653            distance *= 10f;  // convert centimeters to millimeters
654            if (distance > pathLen) {
655                field.setText(formatter.format(pathLen*0.1f));
656                throw new JmriException(Bundle.getMessage(
657                        "BadLengthCm", bo.getPathName(), bo.getBlock().getDisplayName(), pathLen*0.1f, text));
658            } else if (distance < 0) {
659                field.setText("0");
660                throw new JmriException(Bundle.getMessage(
661                        "BadLengthCm", bo.getPathName(), bo.getBlock().getDisplayName(), pathLen*0.1f, text));
662            }
663        }
664        return distance;
665    }
666
667    private float getPathLength(@Nonnull BlockOrder bo) {
668        float len = bo.getPathLength();
669        if (len <= 0) {
670            len = bo.getPathLength();
671            if ( len <= 0) {
672                String sLen = JmriJOptionPane.showInputDialog(this,
673                        Bundle.getMessage("zeroPathLength", bo.getPathName(), bo.getBlock().getDisplayName())
674                        + Bundle.getMessage("getPathLength", bo.getPathName(), bo.getBlock().getDisplayName()),
675                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
676                try {
677                    len = NumberFormat.getNumberInstance().parse(sLen).floatValue();
678                } catch (java.text.ParseException | java.lang.NullPointerException pe) {
679                    len = 0.0f;
680                }
681                bo.setPathLength(len);
682            }
683        }
684       return len;
685    }
686
687    /**
688     * Return length of warrant route in mm.
689     * @throws JmriException when a Path Length is &lt; or equals 0
690     */
691    private float getTotalLength() throws JmriException {
692        List<BlockOrder> orders = getOrders();
693        float totalLen = _startDist;
694        for (int i = 1; i < orders.size() - 1; i++) {
695            BlockOrder bo = orders.get(i);
696            float pathLen = getPathLength(bo);
697            if (pathLen <= 0) {
698                throw new JmriException(Bundle.getMessage("zeroPathLength",
699                    bo.getPathName(), bo.getBlock().getDisplayName()));
700            }
701            totalLen += pathLen;
702        }
703        totalLen += _stopDist;
704        return totalLen;
705    }
706
707    @CheckForNull
708    private String makeCommands(@Nonnull Warrant w) {
709
710        int nextIdx = 0;        // block index - increment after getting a block order
711        List<BlockOrder> orders = getOrders();
712        BlockOrder bo = orders.get(nextIdx++);
713        String blockName = bo.getBlock().getDisplayName();
714
715        int cmdNum;
716        w.addThrottleCommand(new ThrottleSetting(0, Command.FKEY, 0,
717            ValueType.VAL_ON, SpeedStepMode.UNKNOWN, 0, "", blockName));
718        if (_forward.isSelected()) {
719            w.addThrottleCommand(new ThrottleSetting(100, Command.FORWARD, -1,
720                ValueType.VAL_TRUE, SpeedStepMode.UNKNOWN, 0, "",  blockName));
721            if (!_noSound.isSelected()) {
722                w.addThrottleCommand(new ThrottleSetting(1000, Command.FKEY, 2,
723                    ValueType.VAL_ON, SpeedStepMode.UNKNOWN, 0, "", blockName));
724                w.addThrottleCommand(new ThrottleSetting(2500, Command.FKEY, 2,
725                    ValueType.VAL_OFF, SpeedStepMode.UNKNOWN, 0, "", blockName));
726                w.addThrottleCommand(new ThrottleSetting(1000, Command.FKEY, 2,
727                    ValueType.VAL_ON, SpeedStepMode.UNKNOWN, 0, "", blockName));
728                w.addThrottleCommand(new ThrottleSetting(2500, Command.FKEY, 2,
729                    ValueType.VAL_OFF, SpeedStepMode.UNKNOWN, 0, "", blockName));
730                cmdNum = 7;
731            } else {
732                cmdNum = 3;
733            }
734        } else {
735            w.addThrottleCommand(new ThrottleSetting(100, Command.FORWARD, -1,
736                ValueType.VAL_FALSE, SpeedStepMode.UNKNOWN, 0, "", blockName));
737            if (!_noSound.isSelected()) {
738                w.addThrottleCommand(new ThrottleSetting(1000, Command.FKEY, 3,
739                    ValueType.VAL_ON, SpeedStepMode.UNKNOWN, 0, "",  blockName));
740                w.addThrottleCommand(new ThrottleSetting(500, Command.FKEY, 3,
741                    ValueType.VAL_OFF, SpeedStepMode.UNKNOWN, 0, "", blockName));
742                w.addThrottleCommand(new ThrottleSetting(500, Command.FKEY, 3,
743                    ValueType.VAL_ON, SpeedStepMode.UNKNOWN, 0, "", blockName));
744                w.addThrottleCommand(new ThrottleSetting(500, Command.FKEY, 3,
745                    ValueType.VAL_OFF, SpeedStepMode.UNKNOWN, 0, "", blockName));
746                cmdNum = 6;
747            } else {
748                cmdNum = 2;
749            }
750        }
751
752        float totalLen;
753        try {
754            totalLen = getTotalLength();
755        } catch (JmriException je) {
756            return je.getMessage();
757        }
758
759        RampData upRamp = _speedUtil.getRampForSpeedChange(0f, _maxThrottle);
760        RampData downRamp = _speedUtil.getRampForSpeedChange(_maxThrottle, 0f);
761        float upRampLength = upRamp.getRampLength();
762        float dnRampLength = downRamp.getRampLength();
763        int timeInterval = downRamp.getRampTimeIncrement();
764        float intervalDist = totalLen - (upRampLength + dnRampLength);
765        while (intervalDist < 0) {
766            log.debug("Route length= {}, upRampLength= {}, dnRampLength= {}, intervalDist= {}, _maxThrottle= {}",
767                    totalLen, upRampLength, dnRampLength, intervalDist, _maxThrottle);
768            ListIterator<Float> downIter = downRamp.speedIterator(false);
769            float prevSetting = downIter.previous();   // top value is _maxThrottle
770            if (downIter.hasPrevious()) { // if none, empty ramp
771                prevSetting = downIter.previous();
772                _maxThrottle = prevSetting;    // last throttle increment
773            } else {
774                _maxThrottle = _speedUtil.getThrottleSettingForSpeed(totalLen/(timeInterval*2));
775            }
776            upRamp = _speedUtil.getRampForSpeedChange(0f, _maxThrottle);
777            downRamp = _speedUtil.getRampForSpeedChange(_maxThrottle, 0f);
778            upRampLength = upRamp.getRampLength();
779            dnRampLength = downRamp.getRampLength();
780            intervalDist = totalLen - (upRampLength + dnRampLength);
781        }
782        if (upRampLength < 1) {
783            upRamp = _speedUtil.getRampForSpeedChange(0f, _speedUtil.getRampThrottleIncrement());
784        }
785        if (dnRampLength < 1) {
786            downRamp = _speedUtil.getRampForSpeedChange(0f, _speedUtil.getRampThrottleIncrement());
787        }
788        log.debug("Route length= {}, upRampLength= {}, dnRampLength= {}, intervalDist= {}, _maxThrottle= {}",
789                totalLen, upRampLength, dnRampLength, intervalDist, _maxThrottle);
790
791        float blockLen = _startDist;    // length of path in current block
792        float sumBlkLen = 0;    // sum of path lengths at NOOP
793
794        // start train
795        int speedTime = 0;      // ms time to complete speed step from last block
796        int noopTime = 0;       // ms time for entry into next block
797        ListIterator<Float> iter = upRamp.speedIterator(true);
798        float curThrottle = 0;  // throttle setting
799        float prevThrottle = 0;
800        float curDistance = 0;  // current distance traveled up to issuing next command
801        float blkDistance = 0;  // distance traveled in current block up to issuing next command
802        float dist = 0f;    // increment to accumulate curDistance and blkDistance
803
804        log.debug("Start in block \"{}\" startDist= {} stopDist= {}", blockName, _startDist, _stopDist);
805
806        while (iter.hasNext()) {       // ramp up loop
807
808            while (iter.hasNext()) {
809                // interval distance up to speed change
810                dist = _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, speedTime);
811                if (blkDistance + dist >= blockLen) {
812                    break;  // cannot finish upRamp within this block
813                }
814                blkDistance += dist;
815                curDistance += dist;
816                float nextThrottle = iter.next();
817                w.addThrottleCommand(new ThrottleSetting(speedTime, Command.SPEED, -1, ValueType.VAL_FLOAT,
818                        SpeedStepMode.UNKNOWN, nextThrottle, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
819                if (log.isDebugEnabled()) {
820                    log.debug("cmd#{}. UpRamp block \"{}\" set speed {} after {}ms " +
821                        "dist= {} from {} to {}, blkDist= {} curDist= {}",
822                        cmdNum++, blockName, nextThrottle, speedTime,
823                        dist, prevThrottle, curThrottle, blkDistance, curDistance);
824                }
825                prevThrottle = curThrottle;
826                curThrottle = nextThrottle;
827                speedTime = timeInterval;
828            }   // end of upRamp within a block
829
830            if (blkDistance >= blockLen) {
831                // Possible case where initial blkDistance can exceed the length of a block that was just entered.
832                // Skip over block and move to next block and adjust the distance times into that block
833                noopTime = _speedUtil.getTimeForDistance(curThrottle, blockLen);   // noop distance to run through block
834                speedTime = _speedUtil.getTimeForDistance(curThrottle, blkDistance - blockLen);
835            } else {
836                // typical case where next speed change broke out of above loop. (blkDistance + dist >= blockLen)
837                noopTime = _speedUtil.getTimeForDistance(curThrottle, (blockLen - blkDistance));   // time to next block
838                speedTime = timeInterval - noopTime; // time to next speed change
839            }
840
841            if (log.isDebugEnabled()) {
842                log.debug("Leave block \"{}\"  curThrottle= {}, blockLen= {} blkDist= {}, " +
843                    "noopTime= {} 'speedTime'= {}, curDist= {}",
844                    blockName, curThrottle, blockLen, blkDistance, noopTime, speedTime, curDistance);
845            }
846            if (!iter.hasNext()) {
847                break;
848            }
849
850            if (nextIdx < orders.size()) {
851                if (noopTime > timeInterval) {
852                    speedTime = 0;
853                } else {
854                    speedTime = timeInterval - noopTime;
855                }
856                dist = _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, noopTime);
857                blkDistance += dist;
858                curDistance += dist;
859                sumBlkLen += blockLen;
860                bo = orders.get(nextIdx++);
861                blockLen = getPathLength(bo);
862                if (blockLen <= 0) {
863                    return Bundle.getMessage("zeroPathLength", bo.getPathName(), bo.getBlock().getDisplayName());
864                 }
865                blockName = bo.getBlock().getDisplayName();
866                w.addThrottleCommand(new ThrottleSetting(noopTime, Command.NOOP, -1, ValueType.VAL_NOOP,
867                        SpeedStepMode.UNKNOWN, 0, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
868                if (log.isDebugEnabled()) {
869                    log.debug("cmd#{}. Enter RampUp block \"{}\" noopTime= {}"
870                        + ", dist= {} blockLen= {}, blkDist= {}, sumBlkLen= {}, curDist= {}",
871                        cmdNum++, blockName, noopTime, dist, blockLen, blkDistance, sumBlkLen, curDistance);
872                }
873                blkDistance = 0;
874                curDistance = sumBlkLen;
875           }
876        }   // end of upRamp loop
877
878        if (blkDistance < 0.01) {   // no increase of speed in this block
879            dist = _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, speedTime);
880            log.debug("No speed increase in block \"{}\" speedTime= {} dist= {}"
881                + ", blkDist= {}, curDist= {} upRampLength={}",
882                blockName, speedTime, dist, blkDistance, curDistance, upRampLength);
883            blkDistance += dist;
884            curDistance += dist;    // curDistance ought to equal upRampLength
885        }
886        log.debug("Ramp Up done in block \"{}\" speedTime= {} dist= {}"
887            + ", blkDist= {}, curDist= {} upRampLength= {} diff= {}",
888            blockName, speedTime, dist, blkDistance, curDistance, upRampLength, upRampLength - curDistance);
889        blkDistance += (upRampLength - curDistance);  // adjustment for getDistanceOfSpeedChange calculation variances
890        curDistance = upRampLength;
891        if (log.isDebugEnabled()) {
892            log.debug("Ramp Up done in block \"{}\" timeInterval= {} dist= {}"
893                + ", blkDist= {}, curDist= {} upRampLength= {}",
894                blockName, noopTime, dist, blkDistance, curDistance, upRampLength);
895        }
896        prevThrottle = curThrottle; // travel at curThrottle (max speed) for a period of time
897
898        if ( log.isDebugEnabled() && Math.abs(curThrottle - _maxThrottle) > 0.001) {
899            log.error("curThrottle = {} _maxThrottle = {} prevThrottle= {}", curThrottle, _maxThrottle, prevThrottle);
900        }
901
902        // (sumBlkLen + blockLen) is total distance traveled to end of current block
903        if (totalLen - sumBlkLen - blockLen > dnRampLength) {
904            if (!iter.hasNext()) {  // upRamp done. At maxThrottle for remainder of block
905                if (nextIdx < orders.size()) {    // not the last block
906                    dist = _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, noopTime);
907                    blkDistance += dist;
908                    curDistance += dist;
909                    sumBlkLen += blockLen;
910                    bo = orders.get(nextIdx++);
911                    blockLen = getPathLength(bo);
912                    if (blockLen <= 0) {
913                        return Bundle.getMessage("zeroPathLength", bo.getPathName(), bo.getBlock().getDisplayName());
914                     }
915                    blockName = bo.getBlock().getDisplayName();
916                    w.addThrottleCommand(new ThrottleSetting(noopTime, Command.NOOP, -1, ValueType.VAL_NOOP,
917                            SpeedStepMode.UNKNOWN, 0, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
918                    if (log.isDebugEnabled()) {
919                        log.debug("cmd#{}. Enter RampUp block \"{}\" noopTime= {}"
920                            + ", dist= {} blockLen= {}, blkDist= {}, sumBlkLen= {}, curDist= {}",
921                            cmdNum++, blockName, noopTime,
922                        dist, blockLen, blkDistance, sumBlkLen, curDistance);
923                    }
924                    curDistance = sumBlkLen;
925                    blkDistance = 0;
926                } else {
927                    if (log.isDebugEnabled()) {
928                        log.debug("Ramp Up done at last block \"{}\" curThrottle={}, blkDist={}, curDist={}",
929                                blockName, curThrottle, blkDistance, curDistance);
930                    }
931                } // left block where up ramp finished
932
933                // run through mid route at max speed
934                while (nextIdx < orders.size() && totalLen - sumBlkLen - blockLen > dnRampLength) {
935                    // constant speed, get time to next block
936                    noopTime = _speedUtil.getTimeForDistance(curThrottle, blockLen);   // time to next block
937                    sumBlkLen += blockLen;
938                    curDistance += blockLen;
939                    if (log.isDebugEnabled()) {
940                        log.debug("Leave MidRoute block \"{}\" noopTime= {} blockLen= {}, sumBlkLen= {}, curDist={}",
941                                blockName, noopTime, blockLen, sumBlkLen, curDistance);
942                    }
943                    bo = orders.get(nextIdx++);
944                    blockLen = getPathLength(bo);
945                    if (blockLen <= 0) {
946                        return Bundle.getMessage("zeroPathLength", bo.getPathName(), bo.getBlock().getDisplayName());
947                     }
948                    blockName = bo.getBlock().getDisplayName();
949                    if (nextIdx == orders.size()) {
950                        blockLen = _stopDist;
951                    }
952                    w.addThrottleCommand(new ThrottleSetting(noopTime, Command.NOOP, -1, ValueType.VAL_NOOP,
953                            SpeedStepMode.UNKNOWN, 0, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
954                    if (log.isDebugEnabled()) {
955                        log.debug("cmd#{}. Enter MidRoute block \"{}\" noopTime= {},"
956                            + "blockLen= {}, sumBlkLen= {}, curDist={}",
957                            cmdNum++, blockName, noopTime, blockLen, sumBlkLen, curDistance);
958                    }
959                }
960            }
961            blkDistance = 0;
962            dist = totalLen - sumBlkLen - dnRampLength;
963        } else {    // up ramp and down ramp in the same block
964            dist = totalLen - dnRampLength - upRampLength;
965        }
966
967        speedTime =_speedUtil.getTimeForDistance(curThrottle, dist);
968        blkDistance += dist;
969        curDistance += dist;
970
971        float diff = totalLen - dnRampLength - curDistance;
972        if (log.isDebugEnabled()) {
973            log.debug("Begin Ramp Down in block \"{}\" speedTime= {} dist= {}"
974                + ", blkDist= {}, curDist= {} dnRampLength= {} diff= {}",
975                blockName, speedTime, dist, blkDistance, curDistance, dnRampLength, diff);
976        }
977        blkDistance += diff;  // adjustment for getDistanceOfSpeedChange calculation variances
978        curDistance = totalLen - dnRampLength;
979
980        // Ramp down.
981        if (log.isDebugEnabled()) {
982            log.debug("Begin Ramp Down at block \"{}\" blockLen={},"
983                + " at blkDistance= {} curDist= {} sumBlkLen= {} curThrottle= {}",
984                blockName, blockLen, blkDistance, curDistance, sumBlkLen, curThrottle);
985        }
986
987        iter = downRamp.speedIterator(false);
988        iter.previous();   // discard, equals curThrottle
989
990        float nextThrottle = iter.previous();
991        w.addThrottleCommand(new ThrottleSetting(speedTime, Command.SPEED, -1, ValueType.VAL_FLOAT,
992                SpeedStepMode.UNKNOWN, nextThrottle, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
993        if (log.isDebugEnabled()) {
994            log.debug("cmd#{}. DownRamp block \"{}\" set speed {} after {}ms "
995                + "dist= {} from {} to {}, blkDist= {} curDist={}",
996                cmdNum++, blockName, nextThrottle, speedTime,
997                dist, prevThrottle, curThrottle, blkDistance, curDistance);
998        }
999        prevThrottle = curThrottle;
1000        curThrottle = nextThrottle;
1001        speedTime = timeInterval;
1002
1003        while (iter.hasPrevious()) {
1004            if ( nextIdx == orders.size() /* at last block */ && _stageEStop.isSelected() ) {
1005                w.addThrottleCommand(new ThrottleSetting(50, Command.SPEED, -1, ValueType.VAL_FLOAT,
1006                        SpeedStepMode.UNKNOWN, -0.5f, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
1007                log.debug("cmd#{}. At block \"{}\" EStop set speed= {}", cmdNum++, blockName, -0.5);
1008                break;
1009            }
1010
1011            nextThrottle = curThrottle;
1012            while (iter.hasPrevious()) {
1013                dist = _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, speedTime);
1014                if (blkDistance + dist >= blockLen) {
1015                    break;
1016                }
1017                nextThrottle = iter.previous();
1018
1019                if (!iter.hasPrevious() && nextIdx != orders.size()) {
1020                    // nextThrottle is last speed setting. Make sure speed 0 is set in last block
1021                    log.debug("BEFORE last block! Set speed {} in block \"{}\" after {}ms!"
1022                        + "dist= {}, blkDist= {} curDist={}, blockLen= {}",
1023                        nextThrottle, blockName, speedTime, dist, blkDistance, curDistance, blockLen);
1024                    iter.next();    // Back up.
1025                    noopTime = speedTime;
1026                    speedTime = -1;
1027                    break;
1028                }
1029                // interval distance up to speed change
1030                blkDistance += dist;
1031                curDistance += dist;
1032                w.addThrottleCommand(new ThrottleSetting(speedTime, Command.SPEED, -1, ValueType.VAL_FLOAT,
1033                        SpeedStepMode.UNKNOWN, nextThrottle, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
1034                if (log.isDebugEnabled()) {
1035                    log.debug("cmd#{}. DownRamp block \"{}\" set speed {} after {}ms"
1036                        + " dist= {} from {} to {}, blkDist= {} curDist={}",
1037                        cmdNum++, blockName, nextThrottle, speedTime,
1038                        dist, prevThrottle, curThrottle, blkDistance, curDistance);
1039                }
1040                prevThrottle = curThrottle;
1041                curThrottle = nextThrottle;
1042                speedTime = timeInterval;
1043            }
1044
1045            if (!iter.hasPrevious()) {
1046                break;
1047            }
1048
1049            if (speedTime < 0) {
1050                speedTime = 0;
1051            } else {
1052                if (blkDistance >= blockLen) {
1053                    // Possible case where blkDistance can exceed the length of a block that was just entered.
1054                    // Skip over block and move to next block and adjust the distance times into that block
1055                    noopTime = _speedUtil.getTimeForDistance(curThrottle, blockLen);   // noop distance to run through block
1056                    speedTime = _speedUtil.getTimeForDistance(curThrottle, blkDistance - blockLen);
1057                } else {
1058                    // typical case where next speed change broke out of above loop. (blkDistance + dist >= blockLen)
1059                    noopTime = _speedUtil.getTimeForDistance(curThrottle, (blockLen - blkDistance));   // time to next block
1060                    speedTime = timeInterval - noopTime;   // time to next speed change
1061                }
1062           }
1063
1064            if (log.isDebugEnabled()) {
1065                log.debug("Leave block \"{}\" curThrottle= {}, blockLen= {}"
1066                    + " BlkDist= {}, noopTime= {} 'speedTime'= {}, curDist= {}",
1067                    blockName, curThrottle, blockLen, blkDistance, noopTime, speedTime, curDistance);
1068            }
1069
1070            if (nextIdx < orders.size()) {
1071                if (noopTime > timeInterval) {
1072                    speedTime = 0;
1073                } else {
1074                    speedTime = timeInterval - noopTime;
1075                }
1076                dist = _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, noopTime);
1077                blkDistance += dist;
1078                curDistance += dist;
1079                sumBlkLen += blockLen;
1080                bo = orders.get(nextIdx++);
1081                if (nextIdx == orders.size()) {
1082                    blockLen = _stopDist;
1083                } else {
1084                    blockLen = getPathLength(bo);
1085                }
1086                if (blockLen <= 0) {
1087                    return Bundle.getMessage("zeroPathLength", bo.getPathName(), bo.getBlock().getDisplayName());
1088                }
1089                blockName = bo.getBlock().getDisplayName();
1090                w.addThrottleCommand(new ThrottleSetting(noopTime, Command.NOOP, -1, ValueType.VAL_NOOP,
1091                        SpeedStepMode.UNKNOWN, 0, "", blockName, _speedUtil.getTrackSpeed(curThrottle)));
1092                if (log.isDebugEnabled()) {
1093                    log.debug("cmd#{}. Enter RampDown block \"{}\" noopTime= {},"
1094                        + " dist= {} blockLen= {}, blkDist= {}, sumBlkLen= {}, curDist= {}",
1095                        cmdNum++, blockName, noopTime, dist, blockLen, blkDistance, sumBlkLen, curDistance);
1096                }
1097                blkDistance = 0;
1098                curDistance = sumBlkLen;
1099                if (nextIdx == orders.size()) {
1100                    dist = _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, speedTime);
1101                    if (dist > blockLen) {
1102                        speedTime = 0;
1103                    }
1104                }
1105            } else if (iter.hasPrevious()) { // Should not happen. Error in distance calculation
1106                _stageEStop.setSelected(true);
1107                log.error("cmd#{}. ERROR speed in block \"{}\" set speed {} "
1108                    + "after {}ms dist= {} from {} to {}, blkDist= {} curDist={}",
1109                    cmdNum++, blockName, nextThrottle,
1110                    speedTime, dist, prevThrottle, curThrottle, blkDistance, curDistance);
1111            }
1112        }
1113
1114        // Ramp down finished
1115        if (log.isDebugEnabled()) {
1116            sumBlkLen += _stopDist;
1117            curDistance += _speedUtil.getDistanceOfSpeedChange(prevThrottle, curThrottle, speedTime);
1118            log.debug("Ramp down done at block \"{}\", blockLen= {}, "
1119                + "BlkDist= {}, curDist= {}, sumBlkLen= {}, totalLen= {},",
1120                blockName, blockLen, blkDistance, curDistance, sumBlkLen, totalLen);
1121        }
1122        if (!_noSound.isSelected()) {
1123            w.addThrottleCommand(new ThrottleSetting(500, Command.FKEY, 1,
1124                ValueType.VAL_OFF, SpeedStepMode.UNKNOWN, 0, "", blockName));
1125            w.addThrottleCommand(new ThrottleSetting(1000, Command.FKEY, 2,
1126                ValueType.VAL_ON, SpeedStepMode.UNKNOWN, 0, "", blockName));
1127            w.addThrottleCommand(new ThrottleSetting(2000, Command.FKEY, 2,
1128                ValueType.VAL_OFF, SpeedStepMode.UNKNOWN, 0, "", blockName));
1129        }
1130        w.addThrottleCommand(new ThrottleSetting(500, Command.FKEY, 0,
1131            ValueType.VAL_OFF, SpeedStepMode.UNKNOWN, 0, "", blockName));
1132        return null;
1133    }
1134
1135    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NXFrame.class);
1136
1137}