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