001package jmri.jmrit.dispatcher;
002
003import java.awt.BorderLayout;
004import java.awt.Container;
005import java.awt.FlowLayout;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.util.ArrayList;
009import java.util.Calendar;
010import java.util.List;
011
012import javax.swing.BoxLayout;
013import javax.swing.JButton;
014import javax.swing.JCheckBox;
015import javax.swing.JCheckBoxMenuItem;
016import javax.swing.JComboBox;
017import javax.swing.JLabel;
018import javax.swing.JMenuBar;
019import javax.swing.JPanel;
020import javax.swing.JPopupMenu;
021import javax.swing.JScrollPane;
022import javax.swing.JSeparator;
023import javax.swing.JTable;
024import javax.swing.JTextField;
025import javax.swing.table.TableColumn;
026
027import jmri.Block;
028import jmri.EntryPoint;
029import jmri.InstanceManager;
030import jmri.InstanceManagerAutoDefault;
031import jmri.Scale;
032import jmri.ScaleManager;
033import jmri.Section;
034import jmri.Sensor;
035import jmri.SignalMast;
036import jmri.Timebase;
037import jmri.Transit;
038import jmri.TransitManager;
039import jmri.TransitSection;
040import jmri.jmrit.dispatcher.TaskAllocateRelease.TaskAction;
041import jmri.jmrit.dispatcher.ActiveTrain.TrainDetection;
042import jmri.jmrit.display.EditorManager;
043import jmri.jmrit.display.layoutEditor.LayoutEditor;
044import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState;
045import jmri.jmrit.display.layoutEditor.LayoutTurnout;
046import jmri.jmrit.display.layoutEditor.LevelXing;
047import jmri.jmrit.roster.Roster;
048import jmri.jmrit.roster.RosterEntry;
049import jmri.swing.JTablePersistenceManager;
050import jmri.util.JmriJFrame;
051import jmri.util.swing.JmriJOptionPane;
052import jmri.util.swing.JmriMouseAdapter;
053import jmri.util.swing.JmriMouseEvent;
054import jmri.util.swing.JmriMouseListener;
055import jmri.util.swing.XTableColumnModel;
056import jmri.util.table.ButtonEditor;
057import jmri.util.table.ButtonRenderer;
058
059/**
060 * Dispatcher functionality, working with Sections, Transits and ActiveTrain.
061 * <p>
062 * Dispatcher serves as the manager for ActiveTrains. All allocation of Sections
063 * to ActiveTrains is performed here.
064 * <p>
065 * Programming Note: Use the managed instance returned by
066 * {@link jmri.InstanceManager#getDefault(java.lang.Class)} to access the
067 * running Dispatcher.
068 * <p>
069 * Dispatcher listens to fast clock minutes to handle all ActiveTrain items tied
070 * to fast clock time.
071 * <p>
072 * Delayed start of manual and automatic trains is enforced by not allocating
073 * Sections for trains until the fast clock reaches the departure time.
074 * <p>
075 * This file is part of JMRI.
076 * <p>
077 * JMRI is open source software; you can redistribute it and/or modify it under
078 * the terms of version 2 of the GNU General Public License as published by the
079 * Free Software Foundation. See the "COPYING" file for a copy of this license.
080 * <p>
081 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
082 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
083 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
084 *
085 * @author Dave Duchamp Copyright (C) 2008-2011
086 */
087public class DispatcherFrame extends jmri.util.JmriJFrame implements InstanceManagerAutoDefault {
088
089    public DispatcherFrame() {
090        super(true, true); // remember size a position.
091        editorManager = InstanceManager.getDefault(EditorManager.class);
092        initializeOptions();
093        openDispatcherWindow();
094        autoTurnouts = new AutoTurnouts(this);
095        InstanceManager.getDefault(jmri.SectionManager.class).initializeBlockingSensors();
096        getActiveTrainFrame();
097
098        if (fastClock == null) {
099            log.error("Failed to instantiate a fast clock when constructing Dispatcher");
100        } else {
101            minuteChangeListener = new java.beans.PropertyChangeListener() {
102                @Override
103                public void propertyChange(java.beans.PropertyChangeEvent e) {
104                    //process change to new minute
105                    newFastClockMinute();
106                }
107            };
108            fastClock.addMinuteChangeListener(minuteChangeListener);
109        }
110        jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).register(new DispatcherShutDownTask("Dispatch Shutdown"));
111    }
112
113    /***
114     *  reads thru all the traininfo files found in the dispatcher directory
115     *  and loads the ones flagged as "loadAtStartup"
116     */
117    public void loadAtStartup() {
118        log.debug("Loading saved trains flagged as LoadAtStartup");
119        TrainInfoFile tif = new TrainInfoFile();
120        String[] names = tif.getTrainInfoFileNames();
121        log.debug("initializing block paths early"); //TODO: figure out how to prevent the "regular" init
122        InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class)
123                .initializeLayoutBlockPaths();
124        if (names.length > 0) {
125            for (int i = 0; i < names.length; i++) {
126                TrainInfo info = null;
127                try {
128                    info = tif.readTrainInfo(names[i]);
129                } catch (java.io.IOException ioe) {
130                    log.error("IO Exception when reading train info file {}", names[i], ioe);
131                    continue;
132                } catch (org.jdom2.JDOMException jde) {
133                    log.error("JDOM Exception when reading train info file {}", names[i], jde);
134                    continue;
135                }
136                if (info != null && info.getLoadAtStartup()) {
137                    if (loadTrainFromTrainInfo(info) != 0) {
138                        /*
139                         * Error loading occurred The error will have already
140                         * been sent to the log and to screen
141                         */
142                    } else {
143                        /* give time to set up throttles etc */
144                        try {
145                            Thread.sleep(500);
146                        } catch (InterruptedException e) {
147                            log.warn("Sleep Interrupted in loading trains, likely being stopped", e);
148                        }
149                    }
150                }
151            }
152        }
153    }
154    
155    @Override
156    public void dispose( ) {
157        super.dispose();
158        if (autoAllocate != null) {
159            autoAllocate.setAbort();
160        }
161    }
162
163    /**
164     * Constants for the override type
165     */
166    public static final String OVERRIDETYPE_NONE = "NONE";
167    public static final String OVERRIDETYPE_USER = "USER";
168    public static final String OVERRIDETYPE_DCCADDRESS = "DCCADDRESS";
169    public static final String OVERRIDETYPE_OPERATIONS = "OPERATIONS";
170    public static final String OVERRIDETYPE_ROSTER = "ROSTER";
171
172    /**
173     * Loads a train into the Dispatcher from a traininfo file
174     *
175     * @param traininfoFileName  the file name of a traininfo file.
176     * @return 0 good, -1 create failure, -2 -3 file errors, -9 bother.
177     */
178    public int loadTrainFromTrainInfo(String traininfoFileName) {
179        return loadTrainFromTrainInfo(traininfoFileName, "NONE", "");
180    }
181
182    /**
183     * Loads a train into the Dispatcher from a traininfo file, overriding
184     * dccaddress
185     *
186     * @param traininfoFileName  the file name of a traininfo file.
187     * @param overRideType  "NONE", "USER", "ROSTER" or "OPERATIONS"
188     * @param overRideValue  "" , dccAddress, RosterEntryName or Operations
189     *            trainname.
190     * @return 0 good, -1 create failure, -2 -3 file errors, -9 bother.
191     */
192    public int loadTrainFromTrainInfo(String traininfoFileName, String overRideType, String overRideValue) {
193        //read xml data from selected filename and move it into trainfo
194        try {
195            // maybe called from jthon protect our selves
196            TrainInfoFile tif = new TrainInfoFile();
197            TrainInfo info = null;
198            try {
199                info = tif.readTrainInfo(traininfoFileName);
200            } catch (java.io.FileNotFoundException fnfe) {
201                log.error("Train info file not found {}", traininfoFileName);
202                return -2;
203            } catch (java.io.IOException ioe) {
204                log.error("IO Exception when reading train info file {}", traininfoFileName, ioe);
205                return -2;
206            } catch (org.jdom2.JDOMException jde) {
207                log.error("JDOM Exception when reading train info file {}", traininfoFileName, jde);
208                return -3;
209            }
210            return loadTrainFromTrainInfo(info, overRideType, overRideValue);
211        } catch (RuntimeException ex) {
212            log.error("Unexpected, uncaught exception loading traininfofile [{}]", traininfoFileName, ex);
213            return -9;
214        }
215    }
216
217    /**
218     * Loads a train into the Dispatcher
219     *
220     * @param info  a completed TrainInfo class.
221     * @return 0 good, -1 failure
222     */
223    public int loadTrainFromTrainInfo(TrainInfo info) {
224        return loadTrainFromTrainInfo(info, "NONE", "");
225    }
226
227    /**
228     * Loads a train into the Dispatcher
229     * returns an integer. Messages written to log.
230     * @param info  a completed TrainInfo class.
231     * @param overRideType  "NONE", "USER", "ROSTER" or "OPERATIONS"
232     * @param overRideValue  "" , dccAddress, RosterEntryName or Operations
233     *            trainName.
234     * @return 0 good, -1 failure
235     */
236    public int loadTrainFromTrainInfo(TrainInfo info, String overRideType, String overRideValue) {
237        try {
238            loadTrainFromTrainInfoThrowsException( info, overRideType, overRideValue);
239            return 0;
240        } catch (IllegalArgumentException ex) {
241            return -1;
242        }
243    }
244
245    /**
246     * Loads a train into the Dispatcher
247     * throws IllegalArgumentException on errors
248     * @param info  a completed TrainInfo class.
249     * @param overRideType  "NONE", "USER", "ROSTER" or "OPERATIONS"
250     * @param overRideValue  "" , dccAddress, RosterEntryName or Operations
251     *            trainName.
252     * @throws IllegalArgumentException validation errors.
253     */
254    public void loadTrainFromTrainInfoThrowsException(TrainInfo info, String overRideType, String overRideValue)
255                throws IllegalArgumentException {
256
257        log.debug("loading train:{}, startblockname:{}, destinationBlockName:{}", info.getTrainName(),
258                info.getStartBlockName(), info.getDestinationBlockName());
259        // create a new Active Train
260
261        //set updefaults from traininfo
262        int tSource = 0;
263        if (info.getTrainFromRoster()) {
264            tSource = ActiveTrain.ROSTER;
265        } else if (info.getTrainFromTrains()) {
266            tSource = ActiveTrain.OPERATIONS;
267        } else if (info.getTrainFromUser()) {
268            tSource = ActiveTrain.USER;
269        }
270        String dccAddressToUse = info.getDccAddress();
271        String trainNameToUse = info.getTrainUserName();
272        String rosterIDToUse = info.getRosterId();
273        //process override
274        switch (overRideType) {
275            case "":
276            case OVERRIDETYPE_NONE:
277                break;
278            case OVERRIDETYPE_USER:
279            case OVERRIDETYPE_DCCADDRESS:
280                tSource = ActiveTrain.USER;
281                dccAddressToUse = overRideValue;
282                if (trainNameToUse.isEmpty()) {
283                    trainNameToUse = overRideValue;
284                }
285                break;
286            case OVERRIDETYPE_OPERATIONS:
287                tSource = ActiveTrain.OPERATIONS;
288                trainNameToUse = overRideValue;
289                break;
290            case OVERRIDETYPE_ROSTER:
291                tSource = ActiveTrain.ROSTER;
292                rosterIDToUse = overRideValue;
293                if (trainNameToUse.isEmpty()) {
294                    trainNameToUse = overRideValue;
295                }
296                break;
297            default:
298                /* just leave as in traininfo */
299        }
300        if (tSource == 0) {
301            log.warn("Invalid Trains From [{}]",
302                    tSource);
303            throw new IllegalArgumentException(Bundle.getMessage("Error21"));
304        }
305        if (!isTrainFree(trainNameToUse)) {
306            log.warn("TrainName [{}] already in use",
307                    trainNameToUse);
308            throw new IllegalArgumentException(Bundle.getMessage("Error24",trainNameToUse));
309            
310        }
311        ActiveTrain at = createActiveTrain(info.getTransitId(), trainNameToUse, tSource,
312                info.getStartBlockId(), info.getStartBlockSeq(), info.getDestinationBlockId(),
313                info.getDestinationBlockSeq(),
314                info.getAutoRun(), dccAddressToUse, info.getPriority(),
315                info.getResetWhenDone(), info.getReverseAtEnd(), true, null, info.getAllocationMethod());
316        if (at != null) {
317            if (tSource == ActiveTrain.ROSTER) {
318            RosterEntry re = Roster.getDefault().getEntryForId(rosterIDToUse);
319                if (re != null) {
320                    at.setRosterEntry(re);
321                    at.setDccAddress(re.getDccAddress());
322                } else {
323                    log.warn("Roster Entry '{}' not found, could not create ActiveTrain '{}'",
324                            trainNameToUse, info.getTrainName());
325                    throw new IllegalArgumentException(Bundle.getMessage("Error40",rosterIDToUse));
326                }
327            }
328            at.setTrainDetection(info.getTrainDetection());
329            at.setAllocateMethod(info.getAllocationMethod());
330            at.setDelayedStart(info.getDelayedStart()); //this is a code: NODELAY, TIMEDDELAY, SENSORDELAY
331            at.setDepartureTimeHr(info.getDepartureTimeHr()); // hour of day (fast-clock) to start this train
332            at.setDepartureTimeMin(info.getDepartureTimeMin()); //minute of hour to start this train
333            at.setDelayedRestart(info.getDelayedRestart()); //this is a code: NODELAY, TIMEDDELAY, SENSORDELAY
334            at.setRestartDelay(info.getRestartDelayMin()); //this is number of minutes to delay between runs
335            at.setDelaySensor(info.getDelaySensor());
336            at.setResetStartSensor(info.getResetStartSensor());
337            if ((isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin()) &&
338                    info.getDelayedStart() != ActiveTrain.SENSORDELAY) ||
339                    info.getDelayedStart() == ActiveTrain.NODELAY) {
340                at.setStarted();
341            }
342            at.setRestartSensor(info.getRestartSensor());
343            at.setResetRestartSensor(info.getResetRestartSensor());
344            at.setReverseDelayRestart(info.getReverseDelayedRestart());
345            at.setReverseRestartDelay(info.getReverseRestartDelayMin());
346            at.setReverseDelaySensor(info.getReverseRestartSensor());
347            at.setReverseResetRestartSensor(info.getReverseResetRestartSensor());
348            at.setTrainType(info.getTrainType());
349            at.setTerminateWhenDone(info.getTerminateWhenDone());
350            at.setNextTrain(info.getNextTrain());
351            if (info.getAutoRun()) {
352                AutoActiveTrain aat = new AutoActiveTrain(at);
353                aat.setSpeedFactor(info.getSpeedFactor());
354                aat.setMaxSpeed(info.getMaxSpeed());
355                aat.setMinReliableOperatingSpeed(info.getMinReliableOperatingSpeed());
356                aat.setRampRate(AutoActiveTrain.getRampRateFromName(info.getRampRate()));
357                aat.setRunInReverse(info.getRunInReverse());
358                aat.setSoundDecoder(info.getSoundDecoder());
359                aat.setMaxTrainLength(info.getMaxTrainLength());
360                aat.setStopBySpeedProfile(info.getStopBySpeedProfile());
361                aat.setStopBySpeedProfileAdjust(info.getStopBySpeedProfileAdjust());
362                aat.setUseSpeedProfile(info.getUseSpeedProfile());
363                getAutoTrainsFrame().addAutoActiveTrain(aat);
364                if (!aat.initialize()) {
365                    log.error("ERROR initializing autorunning for train {}", at.getTrainName());
366                    throw new IllegalArgumentException(Bundle.getMessage("Error27",at.getTrainName()));
367                }
368            }
369            allocateNewActiveTrain(at);
370            newTrainDone(at);
371
372        } else {
373            log.warn("failed to create Active Train '{}'", info.getTrainName());
374            throw new IllegalArgumentException(Bundle.getMessage("Error48",info.getTrainName()));
375        }
376    }
377
378    protected enum TrainsFrom {
379        TRAINSFROMROSTER,
380        TRAINSFROMOPS,
381        TRAINSFROMUSER,
382        TRAINSFROMSETLATER
383    }
384
385    // Dispatcher options (saved to disk if user requests, and restored if present)
386    private LayoutEditor _LE = null;
387    public static final int SIGNALHEAD = 0x00;
388    public static final int SIGNALMAST = 0x01;
389    public static final int SECTIONSALLOCATED = 2;
390    private int _SignalType = SIGNALHEAD;
391    private String _StoppingSpeedName = "RestrictedSlow";
392    private boolean _UseConnectivity = false;
393    private boolean _HasOccupancyDetection = false; // "true" if blocks have occupancy detection
394    private boolean _SetSSLDirectionalSensors = true;
395    private TrainsFrom _TrainsFrom = TrainsFrom.TRAINSFROMROSTER;
396    private boolean _AutoAllocate = false;
397    private boolean _AutoRelease = false;
398    private boolean _AutoTurnouts = false;
399    private boolean _TrustKnownTurnouts = false;
400    private boolean _ShortActiveTrainNames = false;
401    private boolean _ShortNameInBlock = true;
402    private boolean _RosterEntryInBlock = false;
403    private boolean _ExtraColorForAllocated = true;
404    private boolean _NameInAllocatedBlock = false;
405    private boolean _UseScaleMeters = false;  // "true" if scale meters, "false" for scale feet
406    private Scale _LayoutScale = ScaleManager.getScale("HO");
407    private boolean _SupportVSDecoder = false;
408    private int _MinThrottleInterval = 100; //default time (in ms) between consecutive throttle commands
409    private int _FullRampTime = 10000; //default time (in ms) for RAMP_FAST to go from 0% to 100%
410    private float maximumLineSpeed = 0.0f;
411
412    // operational instance variables
413    private Thread autoAllocateThread ;
414    private static final jmri.NamedBean.DisplayOptions USERSYS = jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME;
415    private final List<ActiveTrain> activeTrainsList = new ArrayList<>();  // list of ActiveTrain objects
416    private final List<java.beans.PropertyChangeListener> _atListeners
417            = new ArrayList<>();
418    private final List<ActiveTrain> delayedTrains = new ArrayList<>();  // list of delayed Active Trains
419    private final List<ActiveTrain> restartingTrainsList = new ArrayList<>();  // list of Active Trains with restart requests
420    private final TransitManager transitManager = InstanceManager.getDefault(jmri.TransitManager.class);
421    private final List<AllocationRequest> allocationRequests = new ArrayList<>();  // List of AllocatedRequest objects
422    protected final List<AllocatedSection> allocatedSections = new ArrayList<>();  // List of AllocatedSection objects
423    private boolean optionsRead = false;
424    private AutoTurnouts autoTurnouts = null;
425    private AutoAllocate autoAllocate = null;
426    private OptionsMenu optionsMenu = null;
427    private ActivateTrainFrame atFrame = null;
428    private EditorManager editorManager = null;
429
430    public ActivateTrainFrame getActiveTrainFrame() {
431        if (atFrame == null) {
432            atFrame = new ActivateTrainFrame(this);
433        }
434        return atFrame;
435    }
436    private boolean newTrainActive = false;
437
438    public boolean getNewTrainActive() {
439        return newTrainActive;
440    }
441
442    public void setNewTrainActive(boolean boo) {
443        newTrainActive = boo;
444    }
445    private AutoTrainsFrame _autoTrainsFrame = null;
446    private final Timebase fastClock = InstanceManager.getNullableDefault(jmri.Timebase.class);
447    private final Sensor fastClockSensor = InstanceManager.sensorManagerInstance().provideSensor("ISCLOCKRUNNING");
448    private transient java.beans.PropertyChangeListener minuteChangeListener = null;
449
450    // dispatcher window variables
451    protected JmriJFrame dispatcherFrame = null;
452    private Container contentPane = null;
453    private ActiveTrainsTableModel activeTrainsTableModel = null;
454    private JButton addTrainButton = null;
455    private JButton terminateTrainButton = null;
456    private JButton cancelRestartButton = null;
457    private JButton allocateExtraButton = null;
458    private JCheckBox autoReleaseBox = null;
459    private JCheckBox autoAllocateBox = null;
460    private AllocationRequestTableModel allocationRequestTableModel = null;
461    private AllocatedSectionTableModel allocatedSectionTableModel = null;
462
463    void initializeOptions() {
464        if (optionsRead) {
465            return;
466        }
467        optionsRead = true;
468        try {
469            InstanceManager.getDefault(OptionsFile.class).readDispatcherOptions(this);
470        } catch (org.jdom2.JDOMException jde) {
471            log.error("JDOM Exception when retrieving dispatcher options", jde);
472        } catch (java.io.IOException ioe) {
473            log.error("I/O Exception when retrieving dispatcher options", ioe);
474        }
475    }
476
477    void openDispatcherWindow() {
478        if (dispatcherFrame == null) {
479            if (editorManager.getAll(LayoutEditor.class).size() > 0 && autoAllocate == null) {
480                autoAllocate = new AutoAllocate(this, allocationRequests);
481                autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator ");
482                autoAllocateThread.start();
483            }
484            dispatcherFrame = this;
485            dispatcherFrame.setTitle(Bundle.getMessage("TitleDispatcher"));
486            JMenuBar menuBar = new JMenuBar();
487            optionsMenu = new OptionsMenu(this);
488            menuBar.add(optionsMenu);
489            setJMenuBar(menuBar);
490            dispatcherFrame.addHelpMenu("package.jmri.jmrit.dispatcher.Dispatcher", true);
491            contentPane = dispatcherFrame.getContentPane();
492            contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
493
494            // set up active trains table
495            JPanel p11 = new JPanel();
496            p11.setLayout(new FlowLayout());
497            p11.add(new JLabel(Bundle.getMessage("ActiveTrainTableTitle")));
498            contentPane.add(p11);
499            JPanel p12 = new JPanel();
500            p12.setLayout(new BorderLayout());
501             activeTrainsTableModel = new ActiveTrainsTableModel();
502            JTable activeTrainsTable = new JTable(activeTrainsTableModel);
503            activeTrainsTable.setName(this.getClass().getName().concat(":activeTrainsTableModel"));
504            activeTrainsTable.setRowSelectionAllowed(false);
505            activeTrainsTable.setPreferredScrollableViewportSize(new java.awt.Dimension(950, 160));
506            activeTrainsTable.setColumnModel(new XTableColumnModel());
507            activeTrainsTable.createDefaultColumnsFromModel();
508            XTableColumnModel activeTrainsColumnModel = (XTableColumnModel)activeTrainsTable.getColumnModel();
509            // Button Columns
510            TableColumn allocateButtonColumn = activeTrainsColumnModel.getColumn(ActiveTrainsTableModel.ALLOCATEBUTTON_COLUMN);
511            allocateButtonColumn.setCellEditor(new ButtonEditor(new JButton()));
512            allocateButtonColumn.setResizable(true);
513            ButtonRenderer buttonRenderer = new ButtonRenderer();
514            activeTrainsTable.setDefaultRenderer(JButton.class, buttonRenderer);
515            JButton sampleButton = new JButton("WWW..."); //by default 3 letters and elipse
516            activeTrainsTable.setRowHeight(sampleButton.getPreferredSize().height);
517            allocateButtonColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
518            TableColumn terminateTrainButtonColumn = activeTrainsColumnModel.getColumn(ActiveTrainsTableModel.TERMINATEBUTTON_COLUMN);
519            terminateTrainButtonColumn.setCellEditor(new ButtonEditor(new JButton()));
520            terminateTrainButtonColumn.setResizable(true);
521            buttonRenderer = new ButtonRenderer();
522            activeTrainsTable.setDefaultRenderer(JButton.class, buttonRenderer);
523            sampleButton = new JButton("WWW...");
524            activeTrainsTable.setRowHeight(sampleButton.getPreferredSize().height);
525            terminateTrainButtonColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
526
527            addMouseListenerToHeader(activeTrainsTable);
528
529            activeTrainsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
530            JScrollPane activeTrainsTableScrollPane = new JScrollPane(activeTrainsTable);
531            p12.add(activeTrainsTableScrollPane, BorderLayout.CENTER);
532            contentPane.add(p12);
533
534            JPanel p13 = new JPanel();
535            p13.setLayout(new FlowLayout());
536            p13.add(addTrainButton = new JButton(Bundle.getMessage("InitiateTrain") + "..."));
537            addTrainButton.addActionListener(new ActionListener() {
538                @Override
539                public void actionPerformed(ActionEvent e) {
540                    if (!newTrainActive) {
541                        getActiveTrainFrame().initiateTrain(e);
542                        newTrainActive = true;
543                    } else {
544                        getActiveTrainFrame().showActivateFrame();
545                    }
546                }
547            });
548            addTrainButton.setToolTipText(Bundle.getMessage("InitiateTrainButtonHint"));
549            p13.add(new JLabel("   "));
550            p13.add(new JLabel("   "));
551            p13.add(allocateExtraButton = new JButton(Bundle.getMessage("AllocateExtra") + "..."));
552            allocateExtraButton.addActionListener(new ActionListener() {
553                @Override
554                public void actionPerformed(ActionEvent e) {
555                    allocateExtraSection(e);
556                }
557            });
558            allocateExtraButton.setToolTipText(Bundle.getMessage("AllocateExtraButtonHint"));
559            p13.add(new JLabel("   "));
560            p13.add(cancelRestartButton = new JButton(Bundle.getMessage("CancelRestart") + "..."));
561            cancelRestartButton.addActionListener(new ActionListener() {
562                @Override
563                public void actionPerformed(ActionEvent e) {
564                    if (!newTrainActive) {
565                        cancelRestart(e);
566                    } else if (restartingTrainsList.size() > 0) {
567                        getActiveTrainFrame().showActivateFrame();
568                        JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Message2"),
569                                Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
570                    } else {
571                        getActiveTrainFrame().showActivateFrame();
572                    }
573                }
574            });
575            cancelRestartButton.setToolTipText(Bundle.getMessage("CancelRestartButtonHint"));
576            p13.add(new JLabel("   "));
577            p13.add(terminateTrainButton = new JButton(Bundle.getMessage("TerminateTrain"))); // immediate if there is only one train
578            terminateTrainButton.addActionListener(new ActionListener() {
579                @Override
580                public void actionPerformed(ActionEvent e) {
581                    if (!newTrainActive) {
582                        terminateTrain(e);
583                    } else if (activeTrainsList.size() > 0) {
584                        getActiveTrainFrame().showActivateFrame();
585                        JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Message1"),
586                                Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
587                    } else {
588                        getActiveTrainFrame().showActivateFrame();
589                    }
590                }
591            });
592            terminateTrainButton.setToolTipText(Bundle.getMessage("TerminateTrainButtonHint"));
593            contentPane.add(p13);
594
595            // Reset and then persist the table's ui state
596            JTablePersistenceManager tpm = InstanceManager.getNullableDefault(JTablePersistenceManager.class);
597            if (tpm != null) {
598                tpm.resetState(activeTrainsTable);
599                tpm.persist(activeTrainsTable);
600            }
601
602            // set up pending allocations table
603            contentPane.add(new JSeparator());
604            JPanel p21 = new JPanel();
605            p21.setLayout(new FlowLayout());
606            p21.add(new JLabel(Bundle.getMessage("RequestedAllocationsTableTitle")));
607            contentPane.add(p21);
608            JPanel p22 = new JPanel();
609            p22.setLayout(new BorderLayout());
610            allocationRequestTableModel = new AllocationRequestTableModel();
611            JTable allocationRequestTable = new JTable(allocationRequestTableModel);
612            allocationRequestTable.setName(this.getClass().getName().concat(":allocationRequestTable"));
613            allocationRequestTable.setRowSelectionAllowed(false);
614            allocationRequestTable.setPreferredScrollableViewportSize(new java.awt.Dimension(950, 100));
615            allocationRequestTable.setColumnModel(new XTableColumnModel());
616            allocationRequestTable.createDefaultColumnsFromModel();
617            XTableColumnModel allocationRequestColumnModel = (XTableColumnModel)allocationRequestTable.getColumnModel();
618            // Button Columns
619            TableColumn allocateColumn = allocationRequestColumnModel.getColumn(AllocationRequestTableModel.ALLOCATEBUTTON_COLUMN);
620            allocateColumn.setCellEditor(new ButtonEditor(new JButton()));
621            allocateColumn.setResizable(true);
622            buttonRenderer = new ButtonRenderer();
623            allocationRequestTable.setDefaultRenderer(JButton.class, buttonRenderer);
624            sampleButton = new JButton(Bundle.getMessage("AllocateButton"));
625            allocationRequestTable.setRowHeight(sampleButton.getPreferredSize().height);
626            allocateColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
627            TableColumn cancelButtonColumn = allocationRequestColumnModel.getColumn(AllocationRequestTableModel.CANCELBUTTON_COLUMN);
628            cancelButtonColumn.setCellEditor(new ButtonEditor(new JButton()));
629            cancelButtonColumn.setResizable(true);
630            cancelButtonColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
631            // add listener
632            addMouseListenerToHeader(allocationRequestTable);
633            allocationRequestTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
634            JScrollPane allocationRequestTableScrollPane = new JScrollPane(allocationRequestTable);
635            p22.add(allocationRequestTableScrollPane, BorderLayout.CENTER);
636            contentPane.add(p22);
637            if (tpm != null) {
638                tpm.resetState(allocationRequestTable);
639                tpm.persist(allocationRequestTable);
640            }
641
642            // set up allocated sections table
643            contentPane.add(new JSeparator());
644            JPanel p30 = new JPanel();
645            p30.setLayout(new FlowLayout());
646            p30.add(new JLabel(Bundle.getMessage("AllocatedSectionsTitle") + "    "));
647            autoAllocateBox = new JCheckBox(Bundle.getMessage("AutoDispatchItem"));
648            p30.add(autoAllocateBox);
649            autoAllocateBox.setToolTipText(Bundle.getMessage("AutoAllocateBoxHint"));
650            autoAllocateBox.addActionListener(new ActionListener() {
651                @Override
652                public void actionPerformed(ActionEvent e) {
653                    handleAutoAllocateChanged(e);
654                }
655            });
656            autoAllocateBox.setSelected(_AutoAllocate);
657            autoReleaseBox = new JCheckBox(Bundle.getMessage("AutoReleaseBoxLabel"));
658            p30.add(autoReleaseBox);
659            autoReleaseBox.setToolTipText(Bundle.getMessage("AutoReleaseBoxHint"));
660            autoReleaseBox.addActionListener(new ActionListener() {
661                @Override
662                public void actionPerformed(ActionEvent e) {
663                    handleAutoReleaseChanged(e);
664                }
665            });
666            autoReleaseBox.setSelected(_AutoAllocate); // initialize autoRelease to match autoAllocate
667            _AutoRelease = _AutoAllocate;
668            contentPane.add(p30);
669            JPanel p31 = new JPanel();
670            p31.setLayout(new BorderLayout());
671            allocatedSectionTableModel = new AllocatedSectionTableModel();
672            JTable allocatedSectionTable = new JTable(allocatedSectionTableModel);
673            allocatedSectionTable.setName(this.getClass().getName().concat(":allocatedSectionTable"));
674            allocatedSectionTable.setRowSelectionAllowed(false);
675            allocatedSectionTable.setPreferredScrollableViewportSize(new java.awt.Dimension(730, 200));
676            allocatedSectionTable.setColumnModel(new XTableColumnModel());
677            allocatedSectionTable.createDefaultColumnsFromModel();
678            XTableColumnModel allocatedSectionColumnModel = (XTableColumnModel)allocatedSectionTable.getColumnModel();
679            // Button columns
680            TableColumn releaseColumn = allocatedSectionColumnModel.getColumn(AllocatedSectionTableModel.RELEASEBUTTON_COLUMN);
681            releaseColumn.setCellEditor(new ButtonEditor(new JButton()));
682            releaseColumn.setResizable(true);
683            allocatedSectionTable.setDefaultRenderer(JButton.class, buttonRenderer);
684            JButton sampleAButton = new JButton(Bundle.getMessage("ReleaseButton"));
685            allocatedSectionTable.setRowHeight(sampleAButton.getPreferredSize().height);
686            releaseColumn.setPreferredWidth((sampleAButton.getPreferredSize().width) + 2);
687            JScrollPane allocatedSectionTableScrollPane = new JScrollPane(allocatedSectionTable);
688            p31.add(allocatedSectionTableScrollPane, BorderLayout.CENTER);
689            // add listener
690            addMouseListenerToHeader(allocatedSectionTable);
691            allocatedSectionTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
692            contentPane.add(p31);
693            if (tpm != null) {
694                tpm.resetState(allocatedSectionTable);
695                tpm.persist(allocatedSectionTable);
696            }
697        }
698        dispatcherFrame.pack();
699        dispatcherFrame.setVisible(true);
700    }
701
702    void releaseAllocatedSectionFromTable(int index) {
703        AllocatedSection as = allocatedSections.get(index);
704        releaseAllocatedSection(as, false);
705    }
706
707    // allocate extra window variables
708    private JmriJFrame extraFrame = null;
709    private Container extraPane = null;
710    private final JComboBox<String> atSelectBox = new JComboBox<>();
711    private final JComboBox<String> extraBox = new JComboBox<>();
712    private final List<Section> extraBoxList = new ArrayList<>();
713    private int atSelectedIndex = -1;
714
715    public void allocateExtraSection(ActionEvent e, ActiveTrain at) {
716        allocateExtraSection(e);
717        if (_ShortActiveTrainNames) {
718            atSelectBox.setSelectedItem(at.getTrainName());
719        } else {
720            atSelectBox.setSelectedItem(at.getActiveTrainName());
721        }
722    }
723
724    // allocate an extra Section to an Active Train
725    private void allocateExtraSection(ActionEvent e) {
726        if (extraFrame == null) {
727            extraFrame = new JmriJFrame(Bundle.getMessage("ExtraTitle"));
728            extraFrame.addHelpMenu("package.jmri.jmrit.dispatcher.AllocateExtra", true);
729            extraPane = extraFrame.getContentPane();
730            extraPane.setLayout(new BoxLayout(extraFrame.getContentPane(), BoxLayout.Y_AXIS));
731            JPanel p1 = new JPanel();
732            p1.setLayout(new FlowLayout());
733            p1.add(new JLabel(Bundle.getMessage("ActiveColumnTitle") + ":"));
734            p1.add(atSelectBox);
735            atSelectBox.addActionListener(new ActionListener() {
736                @Override
737                public void actionPerformed(ActionEvent e) {
738                    handleATSelectionChanged(e);
739                }
740            });
741            atSelectBox.setToolTipText(Bundle.getMessage("ATBoxHint"));
742            extraPane.add(p1);
743            JPanel p2 = new JPanel();
744            p2.setLayout(new FlowLayout());
745            p2.add(new JLabel(Bundle.getMessage("ExtraBoxLabel") + ":"));
746            p2.add(extraBox);
747            extraBox.setToolTipText(Bundle.getMessage("ExtraBoxHint"));
748            extraPane.add(p2);
749            JPanel p7 = new JPanel();
750            p7.setLayout(new FlowLayout());
751            JButton cancelButton = null;
752            p7.add(cancelButton = new JButton(Bundle.getMessage("ButtonCancel")));
753            cancelButton.addActionListener(new ActionListener() {
754                @Override
755                public void actionPerformed(ActionEvent e) {
756                    cancelExtraRequested(e);
757                }
758            });
759            cancelButton.setToolTipText(Bundle.getMessage("CancelExtraHint"));
760            p7.add(new JLabel("    "));
761            JButton aExtraButton = null;
762            p7.add(aExtraButton = new JButton(Bundle.getMessage("AllocateButton")));
763            aExtraButton.addActionListener(new ActionListener() {
764                @Override
765                public void actionPerformed(ActionEvent e) {
766                    addExtraRequested(e);
767                }
768            });
769            aExtraButton.setToolTipText(Bundle.getMessage("AllocateButtonHint"));
770            extraPane.add(p7);
771        }
772        initializeATComboBox();
773        initializeExtraComboBox();
774        extraFrame.pack();
775        extraFrame.setVisible(true);
776    }
777
778    private void handleAutoAllocateChanged(ActionEvent e) {
779        setAutoAllocate(autoAllocateBox.isSelected());
780        stopStartAutoAllocateRelease();
781        if (autoAllocateBox != null) {
782            autoAllocateBox.setSelected(_AutoAllocate);
783        }
784
785        if (optionsMenu != null) {
786            optionsMenu.initializeMenu();
787        }
788        if (_AutoAllocate ) {
789            queueScanOfAllocationRequests();
790        }
791    }
792
793    /*
794     * Queue a scan
795     */
796    protected void queueScanOfAllocationRequests() {
797        if (_AutoAllocate) {
798            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.SCAN_REQUESTS));
799        }
800    }
801
802    /*
803     * Queue a release all reserved sections for a train.
804     */
805    protected void queueReleaseOfReservedSections(String trainName) {
806        if (_AutoRelease || _AutoAllocate) {
807            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_RESERVED, trainName));
808        }
809    }
810
811    /*
812     * Queue a release all reserved sections for a train.
813     */
814    protected void queueAllocate(AllocationRequest aRequest) {
815        if (_AutoRelease || _AutoAllocate) {
816            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.ALLOCATE_IMMEDIATE, aRequest));
817        }
818    }
819
820    /*
821     * Wait for the queue to empty
822     */
823    protected void queueWaitForEmpty() {
824        if (_AutoAllocate) {
825            while (!autoAllocate.allRequestsDone()) {
826                try {
827                    Thread.sleep(10);
828                } catch (InterruptedException iex) {
829                    // we closing do done
830                    return;
831                }
832            }
833        }
834        return;
835    }
836
837    /*
838     * Queue a general release of completed sections
839     */
840    protected void queueReleaseOfCompletedAllocations() {
841        if (_AutoRelease) {
842            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.AUTO_RELEASE));
843        }
844    }
845
846    /*
847     * autorelease option has been changed
848     */
849    private void handleAutoReleaseChanged(ActionEvent e) {
850        _AutoRelease = autoReleaseBox.isSelected();
851        stopStartAutoAllocateRelease();
852        if (autoReleaseBox != null) {
853            autoReleaseBox.setSelected(_AutoRelease);
854        }
855        if (_AutoRelease) {
856            queueReleaseOfCompletedAllocations();
857        }
858    }
859
860    /* Check trainName not in use */
861    protected boolean isTrainFree(String rName) {
862        for (int j = 0; j < getActiveTrainsList().size(); j++) {
863            ActiveTrain at = getActiveTrainsList().get(j);
864            if (rName.equals(at.getTrainName())) {
865                return false;
866            }
867        }
868        return true;
869    }
870
871    /**
872     * Check DCC not already in use
873     * @param addr DCC address.
874     * @return true / false
875     */
876    public boolean isAddressFree(int addr) {
877        for (int j = 0; j < activeTrainsList.size(); j++) {
878            ActiveTrain at = activeTrainsList.get(j);
879            if (addr == Integer.parseInt(at.getDccAddress())) {
880                return false;
881            }
882        }
883        return true;
884    }
885
886    private void handleATSelectionChanged(ActionEvent e) {
887        atSelectedIndex = atSelectBox.getSelectedIndex();
888        initializeExtraComboBox();
889        extraFrame.pack();
890        extraFrame.setVisible(true);
891    }
892
893    private void initializeATComboBox() {
894        atSelectedIndex = -1;
895        atSelectBox.removeAllItems();
896        for (int i = 0; i < activeTrainsList.size(); i++) {
897            ActiveTrain at = activeTrainsList.get(i);
898            if (_ShortActiveTrainNames) {
899                atSelectBox.addItem(at.getTrainName());
900            } else {
901                atSelectBox.addItem(at.getActiveTrainName());
902            }
903        }
904        if (activeTrainsList.size() > 0) {
905            atSelectBox.setSelectedIndex(0);
906            atSelectedIndex = 0;
907        }
908    }
909
910    private void initializeExtraComboBox() {
911        extraBox.removeAllItems();
912        extraBoxList.clear();
913        if (atSelectedIndex < 0) {
914            return;
915        }
916        ActiveTrain at = activeTrainsList.get(atSelectedIndex);
917        //Transit t = at.getTransit();
918        List<AllocatedSection> allocatedSectionList = at.getAllocatedSectionList();
919        for (Section s : InstanceManager.getDefault(jmri.SectionManager.class).getNamedBeanSet()) {
920            if (s.getState() == Section.FREE) {
921                // not already allocated, check connectivity to this train's allocated sections
922                boolean connected = false;
923                for (int k = 0; k < allocatedSectionList.size(); k++) {
924                    if (connected(s, allocatedSectionList.get(k).getSection())) {
925                        connected = true;
926                    }
927                }
928                if (connected) {
929                    // add to the combo box, not allocated and connected to allocated
930                    extraBoxList.add(s);
931                    extraBox.addItem(getSectionName(s));
932                }
933            }
934        }
935        if (extraBoxList.size() > 0) {
936            extraBox.setSelectedIndex(0);
937        }
938    }
939
940    private boolean connected(Section s1, Section s2) {
941        if ((s1 != null) && (s2 != null)) {
942            List<EntryPoint> s1Entries = s1.getEntryPointList();
943            List<EntryPoint> s2Entries = s2.getEntryPointList();
944            for (int i = 0; i < s1Entries.size(); i++) {
945                Block b = s1Entries.get(i).getFromBlock();
946                for (int j = 0; j < s2Entries.size(); j++) {
947                    if (b == s2Entries.get(j).getBlock()) {
948                        return true;
949                    }
950                }
951            }
952        }
953        return false;
954    }
955
956    public String getSectionName(Section sec) {
957        String s = sec.getDisplayName();
958        return s;
959    }
960
961    private void cancelExtraRequested(ActionEvent e) {
962        extraFrame.setVisible(false);
963        extraFrame.dispose();   // prevent listing in the Window menu.
964        extraFrame = null;
965    }
966
967    private void addExtraRequested(ActionEvent e) {
968        int index = extraBox.getSelectedIndex();
969        if ((atSelectedIndex < 0) || (index < 0)) {
970            cancelExtraRequested(e);
971            return;
972        }
973        ActiveTrain at = activeTrainsList.get(atSelectedIndex);
974        Transit t = at.getTransit();
975        Section s = extraBoxList.get(index);
976        //Section ns = null;
977        AllocationRequest ar = null;
978        boolean requested = false;
979        if (t.containsSection(s)) {
980            if (s == at.getNextSectionToAllocate()) {
981                // this is a request that the next section in the transit be allocated
982                allocateNextRequested(atSelectedIndex);
983                return;
984            } else {
985                // requesting allocation of a section in the Transit, but not the next Section
986                int seq = -99;
987                List<Integer> seqList = t.getSeqListBySection(s);
988                if (seqList.size() > 0) {
989                    seq = seqList.get(0);
990                }
991                if (seqList.size() > 1) {
992                    // this section is in the Transit multiple times
993                    int test = at.getNextSectionSeqNumber() - 1;
994                    int diff = java.lang.Math.abs(seq - test);
995                    for (int i = 1; i < seqList.size(); i++) {
996                        if (diff > java.lang.Math.abs(test - seqList.get(i))) {
997                            seq = seqList.get(i);
998                            diff = java.lang.Math.abs(seq - test);
999                        }
1000                    }
1001                }
1002                requested = requestAllocation(at, s, at.getAllocationDirectionFromSectionAndSeq(s, seq),
1003                        seq, true, extraFrame);
1004                ar = findAllocationRequestInQueue(s, seq,
1005                        at.getAllocationDirectionFromSectionAndSeq(s, seq), at);
1006            }
1007        } else {
1008            // requesting allocation of a section outside of the Transit, direction set arbitrary
1009            requested = requestAllocation(at, s, Section.FORWARD, -99, true, extraFrame);
1010            ar = findAllocationRequestInQueue(s, -99, Section.FORWARD, at);
1011        }
1012        // if allocation request is OK, allocate the Section, if not already allocated
1013        if (requested && (ar != null)) {
1014            allocateSection(ar, null);
1015        }
1016        if (extraFrame != null) {
1017            extraFrame.setVisible(false);
1018            extraFrame.dispose();   // prevent listing in the Window menu.
1019            extraFrame = null;
1020        }
1021    }
1022
1023    /**
1024     * Extend the allocation of a section to a active train. Allows a dispatcher
1025     * to manually route a train to its final destination.
1026     *
1027     * @param s      the section to allocate
1028     * @param at     the associated train
1029     * @param jFrame the window to update
1030     * @return true if section was allocated; false otherwise
1031     */
1032    public boolean extendActiveTrainsPath(Section s, ActiveTrain at, JmriJFrame jFrame) {
1033        if (s.getEntryPointFromSection(at.getEndBlockSection(), Section.FORWARD) != null
1034                && at.getNextSectionToAllocate() == null) {
1035
1036            int seq = at.getEndBlockSectionSequenceNumber() + 1;
1037            if (!at.addEndSection(s, seq)) {
1038                return false;
1039            }
1040            jmri.TransitSection ts = new jmri.TransitSection(s, seq, Section.FORWARD);
1041            ts.setTemporary(true);
1042            at.getTransit().addTransitSection(ts);
1043
1044            // requesting allocation of a section outside of the Transit, direction set arbitrary
1045            boolean requested = requestAllocation(at, s, Section.FORWARD, seq, true, jFrame);
1046
1047            AllocationRequest ar = findAllocationRequestInQueue(s, seq, Section.FORWARD, at);
1048            // if allocation request is OK, force an allocation the Section so that the dispatcher can then allocate futher paths through
1049            if (requested && (ar != null)) {
1050                allocateSection(ar, null);
1051                return true;
1052            }
1053        }
1054        return false;
1055    }
1056
1057    public boolean removeFromActiveTrainPath(Section s, ActiveTrain at, JmriJFrame jFrame) {
1058        if (s == null || at == null) {
1059            return false;
1060        }
1061        if (at.getEndBlockSection() != s) {
1062            log.error("Active trains end section {} is not the same as the requested section to remove {}", at.getEndBlockSection().getDisplayName(USERSYS), s.getDisplayName(USERSYS));
1063            return false;
1064        }
1065        if (!at.getTransit().removeLastTemporarySection(s)) {
1066            return false;
1067        }
1068
1069        //Need to find allocation and remove from list.
1070        for (int k = allocatedSections.size(); k > 0; k--) {
1071            if (at == allocatedSections.get(k - 1).getActiveTrain()
1072                    && allocatedSections.get(k - 1).getSection() == s) {
1073                releaseAllocatedSection(allocatedSections.get(k - 1), true);
1074            }
1075        }
1076        at.removeLastAllocatedSection();
1077        return true;
1078    }
1079
1080    // cancel the automatic restart request of an Active Train from the button in the Dispatcher window
1081    void cancelRestart(ActionEvent e) {
1082        ActiveTrain at = null;
1083        if (restartingTrainsList.size() == 1) {
1084            at = restartingTrainsList.get(0);
1085        } else if (restartingTrainsList.size() > 1) {
1086            Object choices[] = new Object[restartingTrainsList.size()];
1087            for (int i = 0; i < restartingTrainsList.size(); i++) {
1088                if (_ShortActiveTrainNames) {
1089                    choices[i] = restartingTrainsList.get(i).getTrainName();
1090                } else {
1091                    choices[i] = restartingTrainsList.get(i).getActiveTrainName();
1092                }
1093            }
1094            Object selName = JmriJOptionPane.showInputDialog(dispatcherFrame,
1095                    Bundle.getMessage("CancelRestartChoice"),
1096                    Bundle.getMessage("CancelRestartTitle"), JmriJOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
1097            if (selName == null) {
1098                return;
1099            }
1100            for (int j = 0; j < restartingTrainsList.size(); j++) {
1101                if (selName.equals(choices[j])) {
1102                    at = restartingTrainsList.get(j);
1103                }
1104            }
1105        }
1106        if (at != null) {
1107            at.setResetWhenDone(false);
1108            for (int j = restartingTrainsList.size(); j > 0; j--) {
1109                if (restartingTrainsList.get(j - 1) == at) {
1110                    restartingTrainsList.remove(j - 1);
1111                    return;
1112                }
1113            }
1114        }
1115    }
1116
1117    // terminate an Active Train from the button in the Dispatcher window
1118    void terminateTrain(ActionEvent e) {
1119        ActiveTrain at = null;
1120        if (activeTrainsList.size() == 1) {
1121            at = activeTrainsList.get(0);
1122        } else if (activeTrainsList.size() > 1) {
1123            Object choices[] = new Object[activeTrainsList.size()];
1124            for (int i = 0; i < activeTrainsList.size(); i++) {
1125                if (_ShortActiveTrainNames) {
1126                    choices[i] = activeTrainsList.get(i).getTrainName();
1127                } else {
1128                    choices[i] = activeTrainsList.get(i).getActiveTrainName();
1129                }
1130            }
1131            Object selName = JmriJOptionPane.showInputDialog(dispatcherFrame,
1132                    Bundle.getMessage("TerminateTrainChoice"),
1133                    Bundle.getMessage("TerminateTrainTitle"), JmriJOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
1134            if (selName == null) {
1135                return;
1136            }
1137            for (int j = 0; j < activeTrainsList.size(); j++) {
1138                if (selName.equals(choices[j])) {
1139                    at = activeTrainsList.get(j);
1140                }
1141            }
1142        }
1143        if (at != null) {
1144            terminateActiveTrain(at,true,false);
1145        }
1146    }
1147
1148    /**
1149     * Checks that exit Signal Heads are in place for all Sections in this
1150     * Transit and for Block boundaries at turnouts or level crossings within
1151     * Sections of the Transit for the direction defined in this Transit. Signal
1152     * Heads are not required at anchor point block boundaries where both blocks
1153     * are within the same Section, and for turnouts with two or more
1154     * connections in the same Section.
1155     *
1156     * <p>
1157     * Moved from Transit in JMRI 4.19.7
1158     *
1159     * @param t The transit being checked.
1160     * @return 0 if all Sections have all required signals or the number of
1161     *         Sections missing required signals; -1 if the panel is null
1162     */
1163    private int checkSignals(Transit t) {
1164        int numErrors = 0;
1165        for (TransitSection ts : t.getTransitSectionList() ) {
1166            numErrors = numErrors + ts.getSection().placeDirectionSensors();
1167        }
1168        return numErrors;
1169    }
1170
1171    /**
1172     * Validates connectivity through a Transit. Returns the number of errors
1173     * found. Sends log messages detailing the errors if break in connectivity
1174     * is detected. Checks all Sections before quitting.
1175     *
1176     * <p>
1177     * Moved from Transit in JMRI 4.19.7
1178     *
1179     * To support multiple panel dispatching, this version uses a null panel reference to bypass
1180     * the Section layout block connectivity checks. The assumption is that the existing block / path
1181     * relationships are valid.  When a section does not span panels, the layout block process can
1182     * result in valid block paths being removed.
1183     *
1184     * @return number of invalid sections
1185     */
1186    private int validateConnectivity(Transit t) {
1187        int numErrors = 0;
1188        for (int i = 0; i < t.getTransitSectionList().size(); i++) {
1189            String s = t.getTransitSectionList().get(i).getSection().validate();
1190            if (!s.isEmpty()) {
1191                log.error(s);
1192                numErrors++;
1193            }
1194        }
1195        return numErrors;
1196    }
1197
1198    // allocate the next section for an ActiveTrain at dispatcher's request
1199    void allocateNextRequested(int index) {
1200        // set up an Allocation Request
1201        ActiveTrain at = activeTrainsList.get(index);
1202        allocateNextRequestedForTrain(at);
1203    }
1204
1205    // allocate the next section for an ActiveTrain 
1206    protected void allocateNextRequestedForTrain(ActiveTrain at) {
1207        // set up an Allocation Request
1208        Section next = at.getNextSectionToAllocate();
1209        if (next == null) {
1210            return;
1211        }
1212        int seqNext = at.getNextSectionSeqNumber();
1213        int dirNext = at.getAllocationDirectionFromSectionAndSeq(next, seqNext);
1214        if (requestAllocation(at, next, dirNext, seqNext, true, dispatcherFrame)) {
1215            AllocationRequest ar = findAllocationRequestInQueue(next, seqNext, dirNext, at);
1216            if (ar == null) {
1217                return;
1218            }
1219            // attempt to allocate
1220            allocateSection(ar, null);
1221        }
1222    }
1223
1224    /**
1225     * Creates a new ActiveTrain, and registers it with Dispatcher.
1226     *
1227     * @param transitID                       system or user name of a Transit
1228     *                                        in the Transit Table
1229     * @param trainID                         any text that identifies the train
1230     * @param tSource                         either ROSTER, OPERATIONS, or USER
1231     *                                        (see ActiveTrain.java)
1232     * @param startBlockName                  system or user name of Block where
1233     *                                        train currently resides
1234     * @param startBlockSectionSequenceNumber sequence number in the Transit of
1235     *                                        the Section containing the
1236     *                                        startBlock (if the startBlock is
1237     *                                        within the Transit), or of the
1238     *                                        Section the train will enter from
1239     *                                        the startBlock (if the startBlock
1240     *                                        is outside the Transit)
1241     * @param endBlockName                    system or user name of Block where
1242     *                                        train will end up after its
1243     *                                        transit
1244     * @param endBlockSectionSequenceNumber   sequence number in the Transit of
1245     *                                        the Section containing the
1246     *                                        endBlock.
1247     * @param autoRun                         set to "true" if computer is to
1248     *                                        run the train automatically,
1249     *                                        otherwise "false"
1250     * @param dccAddress                      required if "autoRun" is "true",
1251     *                                        set to null otherwise
1252     * @param priority                        any integer, higher number is
1253     *                                        higher priority. Used to arbitrate
1254     *                                        allocation request conflicts
1255     * @param resetWhenDone                   set to "true" if the Active Train
1256     *                                        is capable of continuous running
1257     *                                        and the user has requested that it
1258     *                                        be automatically reset for another
1259     *                                        run thru its Transit each time it
1260     *                                        completes running through its
1261     *                                        Transit.
1262     * @param reverseAtEnd                    true if train should automatically
1263     *                                        reverse at end of transit; false
1264     *                                        otherwise
1265     * @param showErrorMessages               "true" if error message dialogs
1266     *                                        are to be displayed for detected
1267     *                                        errors Set to "false" to suppress
1268     *                                        error message dialogs from this
1269     *                                        method.
1270     * @param frame                           window request is from, or "null"
1271     *                                        if not from a window
1272     * @param allocateMethod                  How allocations will be performed.
1273     *                                        999 - Allocate as many section from start to finish as it can
1274     *                                        0 - Allocate to the next "Safe" section. If it cannot allocate all the way to
1275     *                                        the next "safe" section it does not allocate any sections. It will
1276     *                                        not allocate beyond the next safe section until it arrives there. This
1277     *                                        is useful for bidirectional single track running.
1278     *                                        Any other positive number (in reality thats 1-150 as the create transit
1279     *                                        allows a max of 150 sections) allocate the specified number of sections a head.
1280     * @return a new ActiveTrain or null on failure
1281     */
1282    public ActiveTrain createActiveTrain(String transitID, String trainID, int tSource, String startBlockName,
1283            int startBlockSectionSequenceNumber, String endBlockName, int endBlockSectionSequenceNumber,
1284            boolean autoRun, String dccAddress, int priority, boolean resetWhenDone, boolean reverseAtEnd,
1285            boolean showErrorMessages, JmriJFrame frame, int allocateMethod) {
1286        log.debug("trainID:{}, tSource:{}, startBlockName:{}, startBlockSectionSequenceNumber:{}, endBlockName:{}, endBlockSectionSequenceNumber:{}",
1287                trainID,tSource,startBlockName,startBlockSectionSequenceNumber,endBlockName,endBlockSectionSequenceNumber);
1288        // validate input
1289        Transit t = transitManager.getTransit(transitID);
1290        if (t == null) {
1291            if (showErrorMessages) {
1292                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1293                        "Error1"), new Object[]{transitID}), Bundle.getMessage("ErrorTitle"),
1294                        JmriJOptionPane.ERROR_MESSAGE);
1295            }
1296            log.error("Bad Transit name '{}' when attempting to create an Active Train", transitID);
1297            return null;
1298        }
1299        if (t.getState() != Transit.IDLE) {
1300            if (showErrorMessages) {
1301                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1302                        "Error2"), new Object[]{transitID}), Bundle.getMessage("ErrorTitle"),
1303                        JmriJOptionPane.ERROR_MESSAGE);
1304            }
1305            log.error("Transit '{}' not IDLE, cannot create an Active Train", transitID);
1306            return null;
1307        }
1308        if ((trainID == null) || trainID.equals("")) {
1309            if (showErrorMessages) {
1310                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error3"),
1311                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1312            }
1313            log.error("TrainID string not provided, cannot create an Active Train");
1314            return null;
1315        }
1316        if ((tSource != ActiveTrain.ROSTER) && (tSource != ActiveTrain.OPERATIONS)
1317                && (tSource != ActiveTrain.USER)) {
1318            if (showErrorMessages) {
1319                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error21"),
1320                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1321            }
1322            log.error("Train source is invalid - {} - cannot create an Active Train", tSource);
1323            return null;
1324        }
1325        Block startBlock = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(startBlockName);
1326        if (startBlock == null) {
1327            if (showErrorMessages) {
1328                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1329                        "Error4"), new Object[]{startBlockName}), Bundle.getMessage("ErrorTitle"),
1330                        JmriJOptionPane.ERROR_MESSAGE);
1331            }
1332            log.error("Bad startBlockName '{}' when attempting to create an Active Train", startBlockName);
1333            return null;
1334        }
1335        if (isInAllocatedSection(startBlock)) {
1336            if (showErrorMessages) {
1337                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1338                        "Error5"), new Object[]{startBlock.getDisplayName()}), Bundle.getMessage("ErrorTitle"),
1339                        JmriJOptionPane.ERROR_MESSAGE);
1340            }
1341            log.error("Start block '{}' in allocated Section, cannot create an Active Train", startBlock.getDisplayName(USERSYS));
1342            return null;
1343        }
1344        if (_HasOccupancyDetection && (!(startBlock.getState() == Block.OCCUPIED))) {
1345            if (showErrorMessages) {
1346                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1347                        "Error6"), new Object[]{startBlock.getDisplayName()}), Bundle.getMessage("ErrorTitle"),
1348                        JmriJOptionPane.ERROR_MESSAGE);
1349            }
1350            log.error("No train in start block '{}', cannot create an Active Train", startBlock.getDisplayName(USERSYS));
1351            return null;
1352        }
1353        if (startBlockSectionSequenceNumber <= 0) {
1354            if (showErrorMessages) {
1355                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error12"),
1356                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1357            }
1358        } else if (startBlockSectionSequenceNumber > t.getMaxSequence()) {
1359            if (showErrorMessages) {
1360                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1361                        "Error13"), new Object[]{"" + startBlockSectionSequenceNumber}),
1362                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1363            }
1364            log.error("Invalid sequence number '{}' when attempting to create an Active Train", startBlockSectionSequenceNumber);
1365            return null;
1366        }
1367        Block endBlock = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(endBlockName);
1368        if ((endBlock == null) || (!t.containsBlock(endBlock))) {
1369            if (showErrorMessages) {
1370                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1371                        "Error7"), new Object[]{endBlockName}), Bundle.getMessage("ErrorTitle"),
1372                        JmriJOptionPane.ERROR_MESSAGE);
1373            }
1374            log.error("Bad endBlockName '{}' when attempting to create an Active Train", endBlockName);
1375            return null;
1376        }
1377        if ((endBlockSectionSequenceNumber <= 0) && (t.getBlockCount(endBlock) > 1)) {
1378            JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error8"),
1379                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1380        } else if (endBlockSectionSequenceNumber > t.getMaxSequence()) {
1381            if (showErrorMessages) {
1382                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1383                        "Error9"), new Object[]{"" + endBlockSectionSequenceNumber}),
1384                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1385            }
1386            log.error("Invalid sequence number '{}' when attempting to create an Active Train", endBlockSectionSequenceNumber);
1387            return null;
1388        }
1389        if ((!reverseAtEnd) && resetWhenDone && (!t.canBeResetWhenDone())) {
1390            if (showErrorMessages) {
1391                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1392                        "Error26"), new Object[]{(t.getDisplayName())}),
1393                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1394            }
1395            log.error("Incompatible Transit set up and request to Reset When Done when attempting to create an Active Train");
1396            return null;
1397        }
1398        if (autoRun && ((dccAddress == null) || dccAddress.equals(""))) {
1399            if (showErrorMessages) {
1400                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error10"),
1401                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1402            }
1403            log.error("AutoRun requested without a dccAddress when attempting to create an Active Train");
1404            return null;
1405        }
1406        if (autoRun) {
1407            if (_autoTrainsFrame == null) {
1408                // This is the first automatic active train--check if all required options are present
1409                //   for automatic running.  First check for layout editor panel
1410                if (!_UseConnectivity || (editorManager.getAll(LayoutEditor.class).size() == 0)) {
1411                    if (showErrorMessages) {
1412                        JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error33"),
1413                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1414                        log.error("AutoRun requested without a LayoutEditor panel for connectivity.");
1415                        return null;
1416                    }
1417                }
1418                if (!_HasOccupancyDetection) {
1419                    if (showErrorMessages) {
1420                        JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error35"),
1421                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1422                        log.error("AutoRun requested without occupancy detection.");
1423                        return null;
1424                    }
1425                }
1426                // get Maximum line speed once. We need to use this when the current signal mast is null.
1427                for (var panel : editorManager.getAll(LayoutEditor.class)) {
1428                    for (int iSM = 0; iSM < panel.getSignalMastList().size();  iSM++ )  {
1429                        float msl = panel.getSignalMastList().get(iSM).getSignalMast().getSignalSystem().getMaximumLineSpeed();
1430                        if ( msl > maximumLineSpeed ) {
1431                            maximumLineSpeed = msl;
1432                        }
1433                    }
1434                }
1435            }
1436            // check/set Transit specific items for automatic running
1437            // validate connectivity for all Sections in this transit
1438            int numErrors = validateConnectivity(t);
1439
1440            if (numErrors != 0) {
1441                if (showErrorMessages) {
1442                    JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1443                            "Error34"), new Object[]{("" + numErrors)}),
1444                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1445                }
1446                return null;
1447            }
1448            // check/set direction sensors in signal logic for all Sections in this Transit.
1449            if (getSignalType() == SIGNALHEAD && getSetSSLDirectionalSensors()) {
1450                numErrors = checkSignals(t);
1451                if (numErrors == 0) {
1452                    t.initializeBlockingSensors();
1453                }
1454                if (numErrors != 0) {
1455                    if (showErrorMessages) {
1456                        JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1457                                "Error36"), new Object[]{("" + numErrors)}),
1458                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1459                    }
1460                    return null;
1461                }
1462            }
1463            // TODO: Need to check signalMasts as well
1464            // this train is OK, activate the AutoTrains window, if needed
1465            if (_autoTrainsFrame == null) {
1466                _autoTrainsFrame = new AutoTrainsFrame(this);
1467            } else {
1468                _autoTrainsFrame.setVisible(true);
1469            }
1470        } else if (_UseConnectivity && (editorManager.getAll(LayoutEditor.class).size() > 0)) {
1471            // not auto run, set up direction sensors in signals since use connectivity was requested
1472            if (getSignalType() == SIGNALHEAD) {
1473                int numErrors = checkSignals(t);
1474                if (numErrors == 0) {
1475                    t.initializeBlockingSensors();
1476                }
1477                if (numErrors != 0) {
1478                    if (showErrorMessages) {
1479                        JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1480                                "Error36"), new Object[]{("" + numErrors)}),
1481                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1482                    }
1483                    return null;
1484                }
1485            }
1486        }
1487        // all information checks out - create
1488        ActiveTrain at = new ActiveTrain(t, trainID, tSource);
1489        //if (at==null) {
1490        // if (showErrorMessages) {
1491        //JmriJOptionPaneane.showMessageDialog(frame,java.text.MessageFormat.format(Bundle.getMessage(
1492        //    "Error11"),new Object[] { transitID, trainID }), Bundle.getMessage("ErrorTitle"),
1493        //     JmriJOptionPane.ERROR_MESSAGE);
1494        // }
1495        // log.error("Creating Active Train failed, Transit - "+transitID+", train - "+trainID);
1496        // return null;
1497        //}
1498        activeTrainsList.add(at);
1499        java.beans.PropertyChangeListener listener = null;
1500        at.addPropertyChangeListener(listener = new java.beans.PropertyChangeListener() {
1501            @Override
1502            public void propertyChange(java.beans.PropertyChangeEvent e) {
1503                handleActiveTrainChange(e);
1504            }
1505        });
1506        _atListeners.add(listener);
1507        t.setState(Transit.ASSIGNED);
1508        at.setStartBlock(startBlock);
1509        at.setStartBlockSectionSequenceNumber(startBlockSectionSequenceNumber);
1510        at.setEndBlock(endBlock);
1511        at.setEndBlockSection(t.getSectionFromBlockAndSeq(endBlock, endBlockSectionSequenceNumber));
1512        at.setEndBlockSectionSequenceNumber(endBlockSectionSequenceNumber);
1513        at.setResetWhenDone(resetWhenDone);
1514        if (resetWhenDone) {
1515            restartingTrainsList.add(at);
1516        }
1517        at.setReverseAtEnd(reverseAtEnd);
1518        at.setAllocateMethod(allocateMethod);
1519        at.setPriority(priority);
1520        at.setDccAddress(dccAddress);
1521        at.setAutoRun(autoRun);
1522        return at;
1523    }
1524
1525    public void allocateNewActiveTrain(ActiveTrain at) {
1526        if (at.getDelayedStart() == ActiveTrain.SENSORDELAY && at.getDelaySensor() != null) {
1527            if (at.getDelaySensor().getState() != jmri.Sensor.ACTIVE) {
1528                at.initializeDelaySensor();
1529            }
1530        }
1531        AllocationRequest ar = at.initializeFirstAllocation();
1532        if (ar == null) {
1533            log.debug("First allocation returned null, normal for auotallocate");
1534        }
1535        // removed. initializeFirstAllocation already does this.
1536        /* if (ar != null) {
1537            if ((ar.getSection()).containsBlock(at.getStartBlock())) {
1538                // Active Train is in the first Section, go ahead and allocate it
1539                AllocatedSection als = allocateSection(ar, null);
1540                if (als == null) {
1541                    log.error("Problem allocating the first Section of the Active Train - {}", at.getActiveTrainName());
1542                }
1543            }
1544        } */
1545        activeTrainsTableModel.fireTableDataChanged();
1546        if (allocatedSectionTableModel != null) {
1547            allocatedSectionTableModel.fireTableDataChanged();
1548        }
1549    }
1550
1551    private void handleActiveTrainChange(java.beans.PropertyChangeEvent e) {
1552        activeTrainsTableModel.fireTableDataChanged();
1553    }
1554
1555    private boolean isInAllocatedSection(jmri.Block b) {
1556        for (int i = 0; i < allocatedSections.size(); i++) {
1557            Section s = allocatedSections.get(i).getSection();
1558            if (s.containsBlock(b)) {
1559                return true;
1560            }
1561        }
1562        return false;
1563    }
1564
1565    /**
1566     * Terminate an Active Train and remove it from the Dispatcher. The
1567     * ActiveTrain object should not be used again after this method is called.
1568     *
1569     * @param at the train to terminate
1570     */
1571    @Deprecated
1572    public void terminateActiveTrain(ActiveTrain at) {
1573        terminateActiveTrain(at,true,false);
1574    }
1575
1576    /**
1577     * Terminate an Active Train and remove it from the Dispatcher. The
1578     * ActiveTrain object should not be used again after this method is called.
1579     *
1580     * @param at the train to terminate
1581     * @param terminateNow TRue if doing a full terminate, not just an end of transit.
1582     * @param runNextTrain if false the next traininfo is not run.
1583     */
1584    public void terminateActiveTrain(ActiveTrain at, boolean terminateNow, boolean runNextTrain) {
1585        // ensure there is a train to terminate
1586        if (at == null) {
1587            log.error("Null ActiveTrain pointer when attempting to terminate an ActiveTrain");
1588            return;
1589        }
1590        // terminate the train - remove any allocation requests
1591        for (int k = allocationRequests.size(); k > 0; k--) {
1592            if (at == allocationRequests.get(k - 1).getActiveTrain()) {
1593                allocationRequests.get(k - 1).dispose();
1594                allocationRequests.remove(k - 1);
1595            }
1596        }
1597        // remove any allocated sections
1598        // except occupied if not a full termination
1599        for (int k = allocatedSections.size(); k > 0; k--) {
1600            try {
1601                if (at == allocatedSections.get(k - 1).getActiveTrain()) {
1602                    if ( !terminateNow ) {
1603                        if (allocatedSections.get(k - 1).getSection().getOccupancy()!=Section.OCCUPIED) {
1604                            releaseAllocatedSection(allocatedSections.get(k - 1), terminateNow);
1605                        } else {
1606                            // allocatedSections.get(k - 1).getSection().setState(Section.FREE);
1607                            log.debug("Section[{}] State [{}]",allocatedSections.get(k - 1).getSection().getUserName(),
1608                                    allocatedSections.get(k - 1).getSection().getState());
1609                        }
1610                    } else {
1611                        releaseAllocatedSection(allocatedSections.get(k - 1), terminateNow);
1612                    }
1613                }
1614            } catch (RuntimeException e) {
1615                log.warn("releaseAllocatedSection failed - maybe the AllocatedSection was removed due to a terminating train?? {}", e.getMessage());
1616            }
1617        }
1618        // remove from restarting trains list, if present
1619        for (int j = restartingTrainsList.size(); j > 0; j--) {
1620            if (at == restartingTrainsList.get(j - 1)) {
1621                restartingTrainsList.remove(j - 1);
1622            }
1623        }
1624        if (autoAllocate != null) {
1625            queueReleaseOfReservedSections(at.getTrainName());
1626        }
1627        // terminate the train
1628        if (terminateNow) {
1629            for (int m = activeTrainsList.size(); m > 0; m--) {
1630                if (at == activeTrainsList.get(m - 1)) {
1631                    activeTrainsList.remove(m - 1);
1632                    at.removePropertyChangeListener(_atListeners.get(m - 1));
1633                    _atListeners.remove(m - 1);
1634                }
1635            }
1636            if (at.getAutoRun()) {
1637                AutoActiveTrain aat = at.getAutoActiveTrain();
1638                aat.terminate();
1639                aat.dispose();
1640            }
1641            removeHeldMast(null, at);
1642            at.terminate();
1643            if (runNextTrain && !at.getNextTrain().isEmpty() && !at.getNextTrain().equals("None")) {
1644                log.debug("Loading Next Train[{}]", at.getNextTrain());
1645                // must wait at least 2 secs to allow dispose to fully complete.
1646                if (at.getRosterEntry() != null) {
1647                    jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
1648                        loadTrainFromTrainInfo(at.getNextTrain(),"ROSTER",at.getRosterEntry().getId());},2000);
1649                } else {
1650                    jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
1651                        loadTrainFromTrainInfo(at.getNextTrain(),"USER",at.getDccAddress());},2000);
1652                }
1653            }
1654            at.dispose();
1655        }
1656        activeTrainsTableModel.fireTableDataChanged();
1657        if (allocatedSectionTableModel != null) {
1658            allocatedSectionTableModel.fireTableDataChanged();
1659        }
1660        allocationRequestTableModel.fireTableDataChanged();
1661    }
1662
1663    /**
1664     * Creates an Allocation Request, and registers it with Dispatcher
1665     * <p>
1666     * Required input entries:
1667     *
1668     * @param activeTrain       ActiveTrain requesting the allocation
1669     * @param section           Section to be allocated
1670     * @param direction         direction of travel in the allocated Section
1671     * @param seqNumber         sequence number of the Section in the Transit of
1672     *                          the ActiveTrain. If the requested Section is not
1673     *                          in the Transit, a sequence number of -99 should
1674     *                          be entered.
1675     * @param showErrorMessages "true" if error message dialogs are to be
1676     *                          displayed for detected errors Set to "false" to
1677     *                          suppress error message dialogs from this method.
1678     * @param frame             window request is from, or "null" if not from a
1679     *                          window
1680     * @param firstAllocation           True if first allocation
1681     * @return true if successful; false otherwise
1682     */
1683    protected boolean requestAllocation(ActiveTrain activeTrain, Section section, int direction,
1684            int seqNumber, boolean showErrorMessages, JmriJFrame frame,boolean firstAllocation) {
1685        // check input entries
1686        if (activeTrain == null) {
1687            if (showErrorMessages) {
1688                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error16"),
1689                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1690            }
1691            log.error("Missing ActiveTrain specification");
1692            return false;
1693        }
1694        if (section == null) {
1695            if (showErrorMessages) {
1696                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1697                        "Error17"), new Object[]{activeTrain.getActiveTrainName()}), Bundle.getMessage("ErrorTitle"),
1698                        JmriJOptionPane.ERROR_MESSAGE);
1699            }
1700            log.error("Missing Section specification in allocation request from {}", activeTrain.getActiveTrainName());
1701            return false;
1702        }
1703        if (((seqNumber <= 0) || (seqNumber > (activeTrain.getTransit().getMaxSequence()))) && (seqNumber != -99)) {
1704            if (showErrorMessages) {
1705                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1706                        "Error19"), new Object[]{"" + seqNumber, activeTrain.getActiveTrainName()}), Bundle.getMessage("ErrorTitle"),
1707                        JmriJOptionPane.ERROR_MESSAGE);
1708            }
1709            log.error("Out-of-range sequence number *{}* in allocation request", seqNumber);
1710            return false;
1711        }
1712        if ((direction != Section.FORWARD) && (direction != Section.REVERSE)) {
1713            if (showErrorMessages) {
1714                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1715                        "Error18"), new Object[]{"" + direction, activeTrain.getActiveTrainName()}), Bundle.getMessage("ErrorTitle"),
1716                        JmriJOptionPane.ERROR_MESSAGE);
1717            }
1718            log.error("Invalid direction '{}' specification in allocation request", direction);
1719            return false;
1720        }
1721        // check if this allocation has already been requested
1722        AllocationRequest ar = findAllocationRequestInQueue(section, seqNumber, direction, activeTrain);
1723        if (ar == null) {
1724            ar = new AllocationRequest(section, seqNumber, direction, activeTrain);
1725            if (!firstAllocation && _AutoAllocate) {
1726                allocationRequests.add(ar);
1727                if (_AutoAllocate) {
1728                    queueScanOfAllocationRequests();
1729                }
1730            } else if (_AutoAllocate) {  // It is auto allocate and First section
1731                queueAllocate(ar);
1732            } else {
1733                // manual
1734                allocationRequests.add(ar);
1735            }
1736        }
1737        activeTrainsTableModel.fireTableDataChanged();
1738        allocationRequestTableModel.fireTableDataChanged();
1739        return true;
1740    }
1741
1742    protected boolean requestAllocation(ActiveTrain activeTrain, Section section, int direction,
1743            int seqNumber, boolean showErrorMessages, JmriJFrame frame) {
1744        return requestAllocation( activeTrain,  section,  direction,
1745                 seqNumber,  showErrorMessages,  frame, false);
1746    }
1747
1748    // ensures there will not be any duplicate allocation requests
1749    protected AllocationRequest findAllocationRequestInQueue(Section s, int seq, int dir, ActiveTrain at) {
1750        for (int i = 0; i < allocationRequests.size(); i++) {
1751            AllocationRequest ar = allocationRequests.get(i);
1752            if ((ar.getActiveTrain() == at) && (ar.getSection() == s) && (ar.getSectionSeqNumber() == seq)
1753                    && (ar.getSectionDirection() == dir)) {
1754                return ar;
1755            }
1756        }
1757        return null;
1758    }
1759
1760    private void cancelAllocationRequest(int index) {
1761        AllocationRequest ar = allocationRequests.get(index);
1762        allocationRequests.remove(index);
1763        ar.dispose();
1764        allocationRequestTableModel.fireTableDataChanged();
1765    }
1766
1767    private void allocateRequested(int index) {
1768        AllocationRequest ar = allocationRequests.get(index);
1769        allocateSection(ar, null);
1770    }
1771
1772    protected void addDelayedTrain(ActiveTrain at, int restartType, Sensor delaySensor, boolean resetSensor) {
1773        if (restartType == ActiveTrain.TIMEDDELAY) {
1774            if (!delayedTrains.contains(at)) {
1775                delayedTrains.add(at);
1776            }
1777        } else if (restartType == ActiveTrain.SENSORDELAY) {
1778            if (delaySensor != null) {
1779                at.initializeRestartSensor(delaySensor, resetSensor);
1780            }
1781        }
1782        activeTrainsTableModel.fireTableDataChanged();
1783    }
1784
1785    /**
1786     * Allocates a Section to an Active Train according to the information in an
1787     * AllocationRequest.
1788     * <p>
1789     * If successful, returns an AllocatedSection and removes the
1790     * AllocationRequest from the queue. If not successful, returns null and
1791     * leaves the AllocationRequest in the queue.
1792     * <p>
1793     * To be allocatable, a Section must be FREE and UNOCCUPIED. If a Section is
1794     * OCCUPIED, the allocation is rejected unless the dispatcher chooses to
1795     * override this restriction. To be allocatable, the Active Train must not
1796     * be waiting for its start time. If the start time has not been reached,
1797     * the allocation is rejected, unless the dispatcher chooses to override the
1798     * start time.
1799     *
1800     * @param ar the request containing the section to allocate
1801     * @param ns the next section; use null to allow the next section to be
1802     *           automatically determined, if the next section is the last
1803     *           section, of if an extra section is being allocated
1804     * @return the allocated section or null if not successful
1805     */
1806    public AllocatedSection allocateSection(AllocationRequest ar, Section ns) {
1807        log.trace("{}: Checking Section [{}]", ar.getActiveTrain().getTrainName(), (ns != null ? ns.getDisplayName(USERSYS) : "auto"));
1808        AllocatedSection as = null;
1809        Section nextSection = null;
1810        int nextSectionSeqNo = 0;
1811        ActiveTrain at = ar.getActiveTrain();
1812        Section s = ar.getSection();
1813        if (at.reachedRestartPoint()) {
1814            log.debug("{}: waiting for restart, [{}] not allocated", at.getTrainName(), s.getDisplayName(USERSYS));
1815            return null;
1816        }
1817        if (at.holdAllocation()) {
1818            log.debug("{}: allocation is held, [{}] not allocated", at.getTrainName(), s.getDisplayName(USERSYS));
1819            return null;
1820        }
1821        if (s.getState() != Section.FREE) {
1822            log.debug("{}: section [{}] is not free", at.getTrainName(), s.getDisplayName(USERSYS));
1823            return null;
1824        }
1825        // skip occupancy check if this is the first allocation and the train is occupying the Section
1826        boolean checkOccupancy = true;
1827        if ((at.getLastAllocatedSection() == null) && (s.containsBlock(at.getStartBlock()))) {
1828            checkOccupancy = false;
1829        }
1830        // check if section is occupied
1831        if (checkOccupancy && (s.getOccupancy() == Section.OCCUPIED)) {
1832            if (_AutoAllocate) {
1833                return null;  // autoAllocate never overrides occupancy
1834            }
1835            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
1836                    Bundle.getMessage("Question1"), Bundle.getMessage("WarningTitle"),
1837                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
1838                    new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")},
1839                    Bundle.getMessage("ButtonNo"));
1840            if (selectedValue != 0 ) { // array position 0, override not pressed
1841                return null;   // return without allocating if "No" or "Cancel" response
1842            }
1843        }
1844        // check if train has reached its start time if delayed start
1845        if (checkOccupancy && (!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) {
1846            if (_AutoAllocate) {
1847                return null;  // autoAllocate never overrides start time
1848            }
1849            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
1850                    Bundle.getMessage("Question4"), Bundle.getMessage("WarningTitle"),
1851                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
1852                    new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")},
1853                    Bundle.getMessage("ButtonNo"));
1854            if (selectedValue != 0 ) { // array position 0, override not pressed
1855                return null;
1856            } else {
1857                at.setStarted();
1858                for (int i = delayedTrains.size() - 1; i >= 0; i--) {
1859                    if (delayedTrains.get(i) == at) {
1860                        delayedTrains.remove(i);
1861                    }
1862                }
1863            }
1864        }
1865        //check here to see if block is already assigned to an allocated section;
1866        if (checkBlocksNotInAllocatedSection(s, ar) != null) {
1867            return null;
1868        }
1869        // Programming
1870        // Note: if ns is not null, the program will not check for end Block, but will use ns.
1871        // Calling code must do all validity checks on a non-null ns.
1872        if (ns != null) {
1873            nextSection = ns;
1874        } else if ((ar.getSectionSeqNumber() != -99) && (at.getNextSectionSeqNumber() == ar.getSectionSeqNumber())
1875                && (!((s == at.getEndBlockSection()) && (ar.getSectionSeqNumber() == at.getEndBlockSectionSequenceNumber())))
1876                && (!(at.isAllocationReversed() && (ar.getSectionSeqNumber() == 1)))) {
1877            // not at either end - determine the next section
1878            int seqNum = ar.getSectionSeqNumber();
1879            if (at.isAllocationReversed()) {
1880                seqNum -= 1;
1881            } else {
1882                seqNum += 1;
1883            }
1884            List<Section> secList = at.getTransit().getSectionListBySeq(seqNum);
1885            if (secList.size() == 1) {
1886                nextSection = secList.get(0);
1887
1888            } else if (secList.size() > 1) {
1889                if (_AutoAllocate) {
1890                    nextSection = autoChoice(secList, ar, seqNum);
1891                } else {
1892                    nextSection = dispatcherChoice(secList, ar);
1893                }
1894            }
1895            nextSectionSeqNo = seqNum;
1896        } else if (at.getReverseAtEnd() && (!at.isAllocationReversed()) && (s == at.getEndBlockSection())
1897                && (ar.getSectionSeqNumber() == at.getEndBlockSectionSequenceNumber())) {
1898            // need to reverse Transit direction when train is in the last Section, set next section.
1899            at.holdAllocation(true);
1900            nextSectionSeqNo = at.getEndBlockSectionSequenceNumber() - 1;
1901            at.setAllocationReversed(true);
1902            List<Section> secList = at.getTransit().getSectionListBySeq(nextSectionSeqNo);
1903            if (secList.size() == 1) {
1904                nextSection = secList.get(0);
1905            } else if (secList.size() > 1) {
1906                if (_AutoAllocate) {
1907                    nextSection = autoChoice(secList, ar, nextSectionSeqNo);
1908                } else {
1909                    nextSection = dispatcherChoice(secList, ar);
1910                }
1911            }
1912        } else if (((!at.isAllocationReversed()) && (s == at.getEndBlockSection())
1913                && (ar.getSectionSeqNumber() == at.getEndBlockSectionSequenceNumber()))
1914                || (at.isAllocationReversed() && (ar.getSectionSeqNumber() == 1))) {
1915            // request to allocate the last block in the Transit, or the Transit is reversed and
1916            //      has reached the beginning of the Transit--check for automatic restart
1917            if (at.getResetWhenDone()) {
1918                if (at.getDelayedRestart() != ActiveTrain.NODELAY) {
1919                    log.debug("{}: setting allocation to held", at.getTrainName());
1920                    at.holdAllocation(true);
1921                }
1922                nextSection = at.getSecondAllocatedSection();
1923                nextSectionSeqNo = 2;
1924                at.setAllocationReversed(false);
1925            }
1926        }
1927
1928        //This might be the location to check to see if we have an intermediate section that we then need to perform extra checks on.
1929        //Working on the basis that if the nextsection is not null, then we are not at the end of the transit.
1930        List<Section> intermediateSections = new ArrayList<>();
1931        Section mastHeldAtSection = null;
1932        Object imSecProperty = ar.getSection().getProperty("intermediateSection");
1933        if (nextSection != null
1934            && imSecProperty != null
1935                && ((Boolean) imSecProperty)) {
1936
1937            String property = "forwardMast";
1938            if (at.isAllocationReversed()) {
1939                property = "reverseMast";
1940            }
1941
1942            Object sectionDirProp = ar.getSection().getProperty(property);
1943            if ( sectionDirProp != null) {
1944                SignalMast endMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(sectionDirProp.toString());
1945                if (endMast != null) {
1946                    if (endMast.getHeld()) {
1947                        mastHeldAtSection = ar.getSection();
1948                    }
1949                }
1950            }
1951            List<TransitSection> tsList = ar.getActiveTrain().getTransit().getTransitSectionList();
1952            boolean found = false;
1953            if (at.isAllocationReversed()) {
1954                for (int i = tsList.size() - 1; i > 0; i--) {
1955                    TransitSection ts = tsList.get(i);
1956                    if (ts.getSection() == ar.getSection() && ts.getSequenceNumber() == ar.getSectionSeqNumber()) {
1957                        found = true;
1958                    } else if (found) {
1959                        Object imSecProp = ts.getSection().getProperty("intermediateSection");
1960                        if ( imSecProp != null) {
1961                            if ((Boolean) imSecProp) {
1962                                intermediateSections.add(ts.getSection());
1963                            } else {
1964                                //we add the section after the last intermediate in, so that the last allocation request can be built correctly
1965                                intermediateSections.add(ts.getSection());
1966                                break;
1967                            }
1968                        }
1969                    }
1970                }
1971            } else {
1972                for (int i = 0; i <= tsList.size() - 1; i++) {
1973                    TransitSection ts = tsList.get(i);
1974                    if (ts.getSection() == ar.getSection() && ts.getSequenceNumber() == ar.getSectionSeqNumber()) {
1975                        found = true;
1976                    } else if (found) {
1977                        Object imSecProp = ts.getSection().getProperty("intermediateSection");
1978                        if ( imSecProp != null ){
1979                            if ((Boolean) imSecProp) {
1980                                intermediateSections.add(ts.getSection());
1981                            } else {
1982                                //we add the section after the last intermediate in, so that the last allocation request can be built correctly
1983                                intermediateSections.add(ts.getSection());
1984                                break;
1985                            }
1986                        }
1987                    }
1988                }
1989            }
1990            boolean intermediatesOccupied = false;
1991
1992            for (int i = 0; i < intermediateSections.size() - 1; i++) {  // ie do not check last section which is not an intermediate section
1993                Section se = intermediateSections.get(i);
1994                if (se.getState() == Section.FREE  && se.getOccupancy() == Section.UNOCCUPIED) {
1995                    //If the section state is free, we need to look to see if any of the blocks are used else where
1996                    Section conflict = checkBlocksNotInAllocatedSection(se, null);
1997                    if (conflict != null) {
1998                        //We have a conflicting path
1999                        //We might need to find out if the section which the block is allocated to is one in our transit, and if so is it running in the same direction.
2000                        return null;
2001                    } else {
2002                        if (mastHeldAtSection == null) {
2003                            Object heldProp = se.getProperty(property);
2004                            if (heldProp != null) {
2005                                SignalMast endMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(heldProp.toString());
2006                                if (endMast != null && endMast.getHeld()) {
2007                                    mastHeldAtSection = se;
2008                                }
2009                            }
2010                        }
2011                    }
2012                } else if (se.getState() != Section.FREE
2013                                && at.getLastAllocatedSection() != null
2014                                && se.getState() != at.getLastAllocatedSection().getState())  {
2015                    // train coming other way...
2016                    return null;
2017                } else {
2018                    intermediatesOccupied = true;
2019                    break;
2020                }
2021            }
2022            //If the intermediate sections are already occupied or allocated then we clear the intermediate list and only allocate the original request.
2023            if (intermediatesOccupied) {
2024                intermediateSections = new ArrayList<>();
2025            }
2026        }
2027
2028        // check/set turnouts if requested or if autorun
2029        // Note: If "Use Connectivity..." is specified in the Options window, turnouts are checked. If
2030        //   turnouts are not set correctly, allocation will not proceed without dispatcher override.
2031        //   If in addition Auto setting of turnouts is requested, the turnouts are set automatically
2032        //   if not in the correct position.
2033        // Note: Turnout checking and/or setting is not performed when allocating an extra section.
2034        List<LayoutTrackExpectedState<LayoutTurnout>> expectedTurnOutStates = null;
2035        if ((_UseConnectivity) && (ar.getSectionSeqNumber() != -99)) {
2036            expectedTurnOutStates = checkTurnoutStates(s, ar.getSectionSeqNumber(), nextSection, at, at.getLastAllocatedSection());
2037            if (expectedTurnOutStates == null) {
2038                return null;
2039            }
2040            Section preSec = s;
2041            Section tmpcur = nextSection;
2042            int tmpSeqNo = nextSectionSeqNo;
2043            //The first section in the list will be the same as the nextSection, so we skip that.
2044            for (int i = 1; i < intermediateSections.size(); i++) {
2045                Section se = intermediateSections.get(i);
2046                if (preSec == mastHeldAtSection) {
2047                    log.debug("Section is beyond held mast do not set turnouts {}", (tmpcur != null ? tmpcur.getDisplayName(USERSYS) : "null"));
2048                    break;
2049                }
2050                if (checkTurnoutStates(tmpcur, tmpSeqNo, se, at, preSec) == null) {
2051                    return null;
2052                }
2053                preSec = tmpcur;
2054                tmpcur = se;
2055                if (at.isAllocationReversed()) {
2056                    tmpSeqNo -= 1;
2057                } else {
2058                    tmpSeqNo += 1;
2059                }
2060            }
2061        }
2062
2063        as = allocateSection(at, s, ar.getSectionSeqNumber(), nextSection, nextSectionSeqNo, ar.getSectionDirection());
2064        if (as != null) {
2065            as.setAutoTurnoutsResponse(expectedTurnOutStates);
2066        }
2067
2068        if (intermediateSections.size() > 1 && mastHeldAtSection != s) {
2069            Section tmpcur = nextSection;
2070            int tmpSeqNo = nextSectionSeqNo;
2071            int tmpNxtSeqNo = tmpSeqNo;
2072            if (at.isAllocationReversed()) {
2073                tmpNxtSeqNo -= 1;
2074            } else {
2075                tmpNxtSeqNo += 1;
2076            }
2077            //The first section in the list will be the same as the nextSection, so we skip that.
2078            for (int i = 1; i < intermediateSections.size(); i++) {
2079                if (tmpcur == mastHeldAtSection) {
2080                    log.debug("Section is beyond held mast do not allocate any more sections {}", (tmpcur != null ? tmpcur.getDisplayName(USERSYS) : "null"));
2081                    break;
2082                }
2083                Section se = intermediateSections.get(i);
2084                as = allocateSection(at, tmpcur, tmpSeqNo, se, tmpNxtSeqNo, ar.getSectionDirection());
2085                tmpcur = se;
2086                if (at.isAllocationReversed()) {
2087                    tmpSeqNo -= 1;
2088                    tmpNxtSeqNo -= 1;
2089                } else {
2090                    tmpSeqNo += 1;
2091                    tmpNxtSeqNo += 1;
2092                }
2093            }
2094        }
2095        int ix = -1;
2096        for (int i = 0; i < allocationRequests.size(); i++) {
2097            if (ar == allocationRequests.get(i)) {
2098                ix = i;
2099            }
2100        }
2101        if (ix != -1) {
2102            allocationRequests.remove(ix);
2103        }
2104        ar.dispose();
2105        allocationRequestTableModel.fireTableDataChanged();
2106        activeTrainsTableModel.fireTableDataChanged();
2107        if (allocatedSectionTableModel != null) {
2108            allocatedSectionTableModel.fireTableDataChanged();
2109        }
2110        if (extraFrame != null) {
2111            cancelExtraRequested(null);
2112        }
2113        if (_AutoAllocate) {
2114            requestNextAllocation(at);
2115            queueScanOfAllocationRequests();
2116        }
2117        return as;
2118    }
2119
2120    private AllocatedSection allocateSection(ActiveTrain at, Section s, int seqNum, Section nextSection, int nextSectionSeqNo, int direction) {
2121        AllocatedSection as = null;
2122        // allocate the section
2123        as = new AllocatedSection(s, at, seqNum, nextSection, nextSectionSeqNo);
2124        if (_SupportVSDecoder) {
2125            as.addPropertyChangeListener(InstanceManager.getDefault(jmri.jmrit.vsdecoder.VSDecoderManager.class));
2126        }
2127
2128        s.setState(direction/*ar.getSectionDirection()*/);
2129        if (getSignalType() == SIGNALMAST) {
2130            String property = "forwardMast";
2131            if (s.getState() == Section.REVERSE) {
2132                property = "reverseMast";
2133            }
2134            Object smProperty = s.getProperty(property);
2135            if (smProperty != null) {
2136                SignalMast toHold = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(smProperty.toString());
2137                if (toHold != null) {
2138                    if (!toHold.getHeld()) {
2139                        heldMasts.add(new HeldMastDetails(toHold, at));
2140                        toHold.setHeld(true);
2141                    }
2142                }
2143
2144            }
2145
2146            Section lastOccSec = at.getLastAllocatedSection();
2147            if (lastOccSec != null) {
2148                smProperty = lastOccSec.getProperty(property);
2149                if ( smProperty != null) {
2150                    SignalMast toRelease = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(smProperty.toString());
2151                    if (toRelease != null && isMastHeldByDispatcher(toRelease, at)) {
2152                        removeHeldMast(toRelease, at);
2153                        //heldMasts.remove(toRelease);
2154                        toRelease.setHeld(false);
2155                    }
2156                }
2157            }
2158        }
2159        at.addAllocatedSection(as);
2160        allocatedSections.add(as);
2161        log.debug("{}: Allocated section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS));
2162        return as;
2163    }
2164
2165    /**
2166     * Check an active train has an occupied section
2167     * @param at  ActiveTRain object
2168     * @return true / false
2169     */
2170    protected boolean hasTrainAnOccupiedSection(ActiveTrain at) {
2171        for (AllocatedSection asItem : at.getAllocatedSectionList()) {
2172            if (asItem.getSection().getOccupancy() == Section.OCCUPIED) {
2173                return true;
2174            }
2175        }
2176        return false;
2177    }
2178
2179    /**
2180     *
2181     * @param s Section to check
2182     * @param sSeqNum Sequence number of section
2183     * @param nextSection section after
2184     * @param at the active train
2185     * @param prevSection the section before
2186     * @return null if error else a list of the turnouts and their expected states.
2187     */
2188    List<LayoutTrackExpectedState<LayoutTurnout>> checkTurnoutStates(Section s, int sSeqNum, Section nextSection, ActiveTrain at, Section prevSection) {
2189        List<LayoutTrackExpectedState<LayoutTurnout>> turnoutsOK;
2190        if (_AutoTurnouts || at.getAutoRun()) {
2191            // automatically set the turnouts for this section before allocation
2192            turnoutsOK = autoTurnouts.setTurnoutsInSection(s, sSeqNum, nextSection,
2193                    at, _TrustKnownTurnouts, prevSection);
2194        } else {
2195            // check that turnouts are correctly set before allowing allocation to proceed
2196            turnoutsOK = autoTurnouts.checkTurnoutsInSection(s, sSeqNum, nextSection,
2197                    at, prevSection);
2198        }
2199        if (turnoutsOK == null) {
2200            if (_AutoAllocate) {
2201                return turnoutsOK;
2202            } else {
2203                // give the manual dispatcher a chance to override turnouts not OK
2204                int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
2205                        Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"),
2206                        JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2207                        new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")},
2208                        Bundle.getMessage("ButtonNo"));
2209                if (selectedValue != 0 ) { // array position 0, override not pressed
2210                    return null;
2211                }
2212                // return empty list
2213                turnoutsOK = new ArrayList<>();
2214            }
2215        }
2216        return turnoutsOK;
2217    }
2218
2219    List<HeldMastDetails> heldMasts = new ArrayList<>();
2220
2221    static class HeldMastDetails {
2222
2223        SignalMast mast = null;
2224        ActiveTrain at = null;
2225
2226        HeldMastDetails(SignalMast sm, ActiveTrain a) {
2227            mast = sm;
2228            at = a;
2229        }
2230
2231        ActiveTrain getActiveTrain() {
2232            return at;
2233        }
2234
2235        SignalMast getMast() {
2236            return mast;
2237        }
2238    }
2239
2240    public boolean isMastHeldByDispatcher(SignalMast sm, ActiveTrain at) {
2241        for (HeldMastDetails hmd : heldMasts) {
2242            if (hmd.getMast() == sm && hmd.getActiveTrain() == at) {
2243                return true;
2244            }
2245        }
2246        return false;
2247    }
2248
2249    private void removeHeldMast(SignalMast sm, ActiveTrain at) {
2250        List<HeldMastDetails> toRemove = new ArrayList<>();
2251        for (HeldMastDetails hmd : heldMasts) {
2252            if (hmd.getActiveTrain() == at) {
2253                if (sm == null) {
2254                    toRemove.add(hmd);
2255                } else if (sm == hmd.getMast()) {
2256                    toRemove.add(hmd);
2257                }
2258            }
2259        }
2260        for (HeldMastDetails hmd : toRemove) {
2261            hmd.getMast().setHeld(false);
2262            heldMasts.remove(hmd);
2263        }
2264    }
2265
2266    /*
2267     * returns a list of level crossings (0 to n) in a section.
2268     */
2269    private List<LevelXing> containedLevelXing(Section s) {
2270        List<LevelXing> _levelXingList = new ArrayList<>();
2271        if (s == null) {
2272            log.error("null argument to 'containsLevelCrossing'");
2273            return _levelXingList;
2274        }
2275
2276        for (var panel : editorManager.getAll(LayoutEditor.class)) {
2277            for (Block blk: s.getBlockList()) {
2278                for (LevelXing temLevelXing: panel.getConnectivityUtil().getLevelCrossingsThisBlock(blk)) {
2279                    // it is returned if the block is in the crossing or connected to the crossing
2280                    // we only need it if it is in the crossing
2281                    if (temLevelXing.getLayoutBlockAC().getBlock() == blk || temLevelXing.getLayoutBlockBD().getBlock() == blk ) {
2282                        _levelXingList.add(temLevelXing);
2283                    }
2284                }
2285            }
2286        }
2287        return _levelXingList;
2288    }
2289
2290    /**
2291     * Checks for a block in allocated section, except one
2292     * @param b - The Block
2293     * @param ignoreSection - ignore this section, can be null
2294     * @return true is The Block is being used in a section.
2295     */
2296    protected boolean checkForBlockInAllocatedSection ( Block b, Section ignoreSection ) {
2297        for ( AllocatedSection as : allocatedSections) {
2298            if (ignoreSection == null || as.getSection() != ignoreSection) {
2299                if (as.getSection().getBlockList().contains(b)) {
2300                    return true;
2301                }
2302            }
2303        }
2304        return false;
2305    }
2306
2307    /*
2308     * This is used to determine if the blocks in a section we want to allocate are already allocated to a section, or if they are now free.
2309     */
2310    protected Section checkBlocksNotInAllocatedSection(Section s, AllocationRequest ar) {
2311        for (AllocatedSection as : allocatedSections) {
2312            if (as.getSection() != s) {
2313                List<Block> blas = as.getSection().getBlockList();
2314                //
2315                // When allocating the initial section for an Active Train,
2316                // we need not be concerned with any blocks in the initial section
2317                // which are unoccupied and to the rear of any occupied blocks in
2318                // the section as the train is not expected to enter those blocks.
2319                // When sections include the OS section these blocks prevented
2320                // allocation.
2321                //
2322                // The procedure is to remove those blocks (for the moment) from
2323                // the blocklist for the section during the initial allocation.
2324                //
2325
2326                List<Block> bls = new ArrayList<>();
2327                if (ar != null && ar.getActiveTrain().getAllocatedSectionList().size() == 0) {
2328                    int j;
2329                    if (ar.getSectionDirection() == Section.FORWARD) {
2330                        j = 0;
2331                        for (int i = 0; i < s.getBlockList().size(); i++) {
2332                            if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) {
2333                                j = 1;
2334                            }
2335                            if (j == 1) {
2336                                bls.add(s.getBlockList().get(i));
2337                            }
2338                        }
2339                    } else {
2340                        j = 0;
2341                        for (int i = s.getBlockList().size() - 1; i >= 0; i--) {
2342                            if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) {
2343                                j = 1;
2344                            }
2345                            if (j == 1) {
2346                                bls.add(s.getBlockList().get(i));
2347                            }
2348                        }
2349                    }
2350                } else {
2351                    bls = s.getBlockList();
2352                    // Add Blocks in any XCrossing, dont add ones already in the list
2353                    for ( LevelXing lx: containedLevelXing(s)) {
2354                        Block bAC = lx.getLayoutBlockAC().getBlock();
2355                        Block bBD = lx.getLayoutBlockBD().getBlock();
2356                        if (!bls.contains(bAC)) {
2357                            bls.add(bAC);
2358                        }
2359                        if (!bls.contains(bBD)) {
2360                            bls.add(bBD);
2361                        }
2362                    }
2363                }
2364
2365                for (Block b : bls) {
2366                    if (blas.contains(b)) {
2367                        if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) {
2368                            // no clue where the tail is some must assume this block still in use.
2369                            return as.getSection();
2370                        }
2371                        if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADANDTAIL) {
2372                            // if this is in the oldest section then we treat as whole train..
2373                            // if there is a section that exited but occupied the tail is there
2374                            for (AllocatedSection tas : allocatedSections) {
2375                                if (tas.getActiveTrain() == as.getActiveTrain() && tas.getExited() && tas.getSection().getOccupancy() == Section.OCCUPIED ) {
2376                                    return as.getSection();
2377                                }
2378                            }
2379                        } else if (as.getActiveTrain().getTrainDetection() != TrainDetection.TRAINDETECTION_WHOLETRAIN) {
2380                            return as.getSection();
2381                        }
2382                        if (as.getSection().getOccupancy() == Block.OCCUPIED) {
2383                            //The next check looks to see if the block has already been passed or not and therefore ready for allocation.
2384                            if (as.getSection().getState() == Section.FORWARD) {
2385                                for (int i = 0; i < blas.size(); i++) {
2386                                    //The block we get to is occupied therefore the subsequent blocks have not been entered
2387                                    if (blas.get(i).getState() == Block.OCCUPIED) {
2388                                        if (ar != null) {
2389                                            ar.setWaitingOnBlock(b);
2390                                        }
2391                                        return as.getSection();
2392                                    } else if (blas.get(i) == b) {
2393                                        break;
2394                                    }
2395                                }
2396                            } else {
2397                                for (int i = blas.size() - 1; i >= 0; i--) {
2398                                    //The block we get to is occupied therefore the subsequent blocks have not been entered
2399                                    if (blas.get(i).getState() == Block.OCCUPIED) {
2400                                        if (ar != null) {
2401                                            ar.setWaitingOnBlock(b);
2402                                        }
2403                                        return as.getSection();
2404                                    } else if (blas.get(i) == b) {
2405                                        break;
2406                                    }
2407                                }
2408                            }
2409                        } else if (as.getSection().getOccupancy() != Section.FREE) {
2410                            if (ar != null) {
2411                                ar.setWaitingOnBlock(b);
2412                            }
2413                            return as.getSection();
2414                        }
2415                    }
2416                }
2417            }
2418        }
2419        return null;
2420    }
2421
2422    // automatically make a choice of next section
2423    private Section autoChoice(List<Section> sList, AllocationRequest ar, int sectionSeqNo) {
2424        Section tSection = autoAllocate.autoNextSectionChoice(sList, ar, sectionSeqNo);
2425        if (tSection != null) {
2426            return tSection;
2427        }
2428        // if automatic choice failed, ask the dispatcher
2429        return dispatcherChoice(sList, ar);
2430    }
2431
2432    // manually make a choice of next section
2433    private Section dispatcherChoice(List<Section> sList, AllocationRequest ar) {
2434        Object choices[] = new Object[sList.size()];
2435        for (int i = 0; i < sList.size(); i++) {
2436            Section s = sList.get(i);
2437            String txt = s.getDisplayName();
2438            choices[i] = txt;
2439        }
2440        Object secName = JmriJOptionPane.showInputDialog(dispatcherFrame,
2441                Bundle.getMessage("ExplainChoice", ar.getSectionName()),
2442                Bundle.getMessage("ChoiceFrameTitle"), JmriJOptionPane
2443                        .QUESTION_MESSAGE, null, choices, choices[0]);
2444        if (secName == null) {
2445            JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("WarnCancel"));
2446            return sList.get(0);
2447        }
2448        for (int j = 0; j < sList.size(); j++) {
2449            if (secName.equals(choices[j])) {
2450                return sList.get(j);
2451            }
2452        }
2453        return sList.get(0);
2454    }
2455
2456    // submit an AllocationRequest for the next Section of an ActiveTrain
2457    private void requestNextAllocation(ActiveTrain at) {
2458        // set up an Allocation Request
2459        Section next = at.getNextSectionToAllocate();
2460        if (next == null) {
2461            return;
2462        }
2463        int seqNext = at.getNextSectionSeqNumber();
2464        int dirNext = at.getAllocationDirectionFromSectionAndSeq(next, seqNext);
2465        requestAllocation(at, next, dirNext, seqNext, true, dispatcherFrame);
2466    }
2467
2468    /**
2469     * Check if any allocation requests need to be allocated, or if any
2470     * allocated sections need to be released
2471     */
2472    protected void checkAutoRelease() {
2473        if (_AutoRelease) {
2474            // Auto release of exited sections has been requested - because of possible noise in block detection
2475            //    hardware, allocated sections are automatically released in the order they were allocated only
2476            // Only unoccupied sections that have been exited are tested.
2477            // The next allocated section must be assigned to the same train, and it must have been entered for
2478            //    the exited Section to be released.
2479            // Extra allocated sections are not automatically released (allocation number = -1).
2480            boolean foundOne = true;
2481            while ((allocatedSections.size() > 0) && foundOne) {
2482                try {
2483                    foundOne = false;
2484                    AllocatedSection as = null;
2485                    for (int i = 0; (i < allocatedSections.size()) && !foundOne; i++) {
2486                        as = allocatedSections.get(i);
2487                        if (as.getExited() && (as.getSection().getOccupancy() != Section.OCCUPIED)
2488                                && (as.getAllocationNumber() != -1)) {
2489                            // possible candidate for deallocation - check order
2490                            foundOne = true;
2491                            for (int j = 0; (j < allocatedSections.size()) && foundOne; j++) {
2492                                if (j != i) {
2493                                    AllocatedSection asx = allocatedSections.get(j);
2494                                    if ((asx.getActiveTrain() == as.getActiveTrain())
2495                                            && (asx.getAllocationNumber() != -1)
2496                                            && (asx.getAllocationNumber() < as.getAllocationNumber())) {
2497                                        foundOne = false;
2498                                    }
2499                                }
2500                            }
2501
2502                            // The train must have one occupied section.
2503                            // The train may be sitting in one of its allocated section undetected.
2504                            if ( foundOne && !hasTrainAnOccupiedSection(as.getActiveTrain())) {
2505                                log.warn("[{}]:CheckAutoRelease release section [{}] failed, train has no occupied section",
2506                                        as.getActiveTrain().getActiveTrainName(),as.getSectionName());
2507                                foundOne = false;
2508                            }
2509
2510                            if (foundOne) {
2511                                // check its not the last allocated section
2512                                int allocatedCount = 0;
2513                                for (int j = 0; (j < allocatedSections.size()); j++) {
2514                                    AllocatedSection asx = allocatedSections.get(j);
2515                                    if (asx.getActiveTrain() == as.getActiveTrain()) {
2516                                            allocatedCount++ ;
2517                                    }
2518                                }
2519                                if (allocatedCount == 1) {
2520                                    foundOne = false;
2521                                }
2522                            }
2523                            if (foundOne) {
2524                                // check if the next section is allocated to the same train and has been entered
2525                                ActiveTrain at = as.getActiveTrain();
2526                                Section ns = as.getNextSection();
2527                                AllocatedSection nas = null;
2528                                for (int k = 0; (k < allocatedSections.size()) && (nas == null); k++) {
2529                                    if (allocatedSections.get(k).getSection() == ns) {
2530                                        nas = allocatedSections.get(k);
2531                                    }
2532                                }
2533                                if ((nas == null) || (at.getStatus() == ActiveTrain.WORKING)
2534                                        || (at.getStatus() == ActiveTrain.STOPPED)
2535                                        || (at.getStatus() == ActiveTrain.READY)
2536                                        || (at.getMode() == ActiveTrain.MANUAL)) {
2537                                    // do not autorelease allocated sections from an Active Train that is
2538                                    //    STOPPED, READY, or WORKING, or is in MANUAL mode.
2539                                    foundOne = false;
2540                                    //But do so if the active train has reached its restart point
2541                                    if (nas != null && at.reachedRestartPoint()) {
2542                                        foundOne = true;
2543                                    }
2544                                } else {
2545                                    if ((nas.getActiveTrain() != as.getActiveTrain()) || (!nas.getEntered())) {
2546                                        foundOne = false;
2547                                    }
2548                                }
2549                                foundOne = sectionNotRequiredByHeadOnly(foundOne,at,as);
2550                                if (foundOne) {
2551                                    log.debug("{}: releasing section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS));
2552                                    doReleaseAllocatedSection(as, false);
2553                                }
2554                            }
2555                        }
2556                    }
2557                } catch (RuntimeException e) {
2558                    log.warn("checkAutoRelease failed  - maybe the AllocatedSection was removed due to a terminating train? {}", e.toString());
2559                    continue;
2560                }
2561            }
2562        }
2563        if (_AutoAllocate) {
2564            queueScanOfAllocationRequests();
2565        }
2566    }
2567
2568    /*
2569     * Check whether the section is in use by a "Head Only" train and can be released.
2570     * calculate the length of exited sections, subtract the length of section
2571     * being released. If the train is moving do not include the length of the occupied section,
2572     * if the train is stationary and was stopped by sensor or speed profile include the length
2573     * of the occupied section. This is done as we dont know where the train is in the section block.
2574     */
2575    private boolean sectionNotRequiredByHeadOnly(boolean foundOne, ActiveTrain at, AllocatedSection as) {
2576        if (at.getAutoActiveTrain() != null && at.getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) {
2577            long allocatedLengthMM = 0;
2578            for (AllocatedSection tas : at.getAllocatedSectionList()) {
2579                if (tas.getSection().getOccupancy() == Section.OCCUPIED) {
2580                    if (at.getAutoActiveTrain().getAutoEngineer().isStopped() &&
2581                            (at.getAutoActiveTrain().getStopBySpeedProfile() ||
2582                                    tas.getSection().getForwardStoppingSensor() != null ||
2583                                    tas.getSection().getReverseStoppingSensor() != null)) {
2584                        allocatedLengthMM += tas.getSection().getActualLength();
2585                        log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] including in length.",
2586                                at.getTrainName(),tas.getSection().getDisplayName());
2587                        break;
2588                    } else {
2589                        log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] excluding from length.",
2590                                at.getTrainName(),tas.getSection().getDisplayName());
2591                        break;
2592                    }
2593                }
2594                if (tas.getExited()) {
2595                    allocatedLengthMM += tas.getSection().getActualLength();
2596                }
2597            }
2598            long trainLengthMM = at.getAutoActiveTrain().getMaxTrainLengthMM();
2599            long releaseLengthMM = as.getSection().getActualLength();
2600            log.debug("[{}]:Release Section [{}] by Length allocated [{}] release [{}] train [{}]",
2601                    at.getTrainName(), as.getSectionName(), allocatedLengthMM, releaseLengthMM, trainLengthMM);
2602            if ((allocatedLengthMM - releaseLengthMM) < trainLengthMM) {
2603                return (false);
2604            }
2605        }
2606        return (true);
2607    }
2608
2609    /**
2610     * Releases an allocated Section, and removes it from the Dispatcher Input.
2611     *
2612     * @param as               the section to release
2613     * @param terminatingTrain true if the associated train is being terminated;
2614     *                         false otherwise
2615     */
2616    public void releaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2617        // Unless the train is termination it must have one occupied section.
2618        // The train may be sitting in an allocated section undetected.
2619        if ( !terminatingTrain && !hasTrainAnOccupiedSection(as.getActiveTrain())) {
2620                log.warn("[{}]: releaseAllocatedSection release section [{}] failed train has no occupied section",as.getActiveTrain().getActiveTrainName(),as.getSectionName());
2621            return;
2622        }
2623        if (_AutoAllocate ) {
2624            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_ONE,as,terminatingTrain));
2625        } else {
2626            doReleaseAllocatedSection( as,  terminatingTrain);
2627        }
2628    }
2629    protected void doReleaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2630        // check that section is not occupied if not terminating train
2631        if (!terminatingTrain && (as.getSection().getOccupancy() == Section.OCCUPIED)) {
2632            // warn the manual dispatcher that Allocated Section is occupied
2633            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, java.text.MessageFormat.format(
2634                    Bundle.getMessage("Question5"), new Object[]{as.getSectionName()}), Bundle.getMessage("WarningTitle"),
2635                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2636                    new Object[]{Bundle.getMessage("ButtonRelease"), Bundle.getMessage("ButtonNo")},
2637                    Bundle.getMessage("ButtonNo"));
2638            if (selectedValue != 0 ) { // array position 0, release not pressed
2639                return;   // return without releasing if "No" or "Cancel" response
2640            }
2641        }
2642        // release the Allocated Section
2643        for (int i = allocatedSections.size(); i > 0; i--) {
2644            if (as == allocatedSections.get(i - 1)) {
2645                allocatedSections.remove(i - 1);
2646            }
2647        }
2648        as.getSection().setState(Section.FREE);
2649        as.getActiveTrain().removeAllocatedSection(as);
2650        as.dispose();
2651        if (allocatedSectionTableModel != null) {
2652            allocatedSectionTableModel.fireTableDataChanged();
2653        }
2654        allocationRequestTableModel.fireTableDataChanged();
2655        activeTrainsTableModel.fireTableDataChanged();
2656        if (_AutoAllocate) {
2657            queueScanOfAllocationRequests();
2658        }
2659    }
2660
2661    /**
2662     * Updates display when occupancy of an allocated section changes Also
2663     * drives auto release if it is selected
2664     */
2665    public void sectionOccupancyChanged() {
2666        queueReleaseOfCompletedAllocations();
2667        if (allocatedSectionTableModel != null) {
2668            allocatedSectionTableModel.fireTableDataChanged();
2669        }
2670        allocationRequestTableModel.fireTableDataChanged();
2671    }
2672
2673    /**
2674     * Handle activity that is triggered by the fast clock
2675     */
2676    protected void newFastClockMinute() {
2677        for (int i = delayedTrains.size() - 1; i >= 0; i--) {
2678            ActiveTrain at = delayedTrains.get(i);
2679            // check if this Active Train is waiting to start
2680            if ((!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) {
2681                // is it time to start?
2682                if (at.getDelayedStart() == ActiveTrain.TIMEDDELAY) {
2683                    if (isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin())) {
2684                        // allow this train to start
2685                        at.setStarted();
2686                        delayedTrains.remove(i);
2687                    }
2688                }
2689            } else if (at.getStarted() && at.getStatus() == ActiveTrain.READY && at.reachedRestartPoint()) {
2690                if (isFastClockTimeGE(at.getRestartDepartHr(), at.getRestartDepartMin())) {
2691                    at.restart();
2692                    delayedTrains.remove(i);
2693                }
2694            }
2695        }
2696        if (_AutoAllocate) {
2697            queueScanOfAllocationRequests();
2698        }
2699    }
2700
2701    /**
2702     * This method tests time
2703     *
2704     * @param hr  the hour to test against (0-23)
2705     * @param min the minute to test against (0-59)
2706     * @return true if fast clock time and tested time are the same
2707     */
2708    public boolean isFastClockTimeGE(int hr, int min) {
2709        Calendar now = Calendar.getInstance();
2710        now.setTime(fastClock.getTime());
2711        int nowHours = now.get(Calendar.HOUR_OF_DAY);
2712        int nowMinutes = now.get(Calendar.MINUTE);
2713        return ((nowHours * 60) + nowMinutes) == ((hr * 60) + min);
2714    }
2715
2716    // option access methods
2717    protected LayoutEditor getLayoutEditor() {
2718        return _LE;
2719    }
2720
2721    protected void setLayoutEditor(LayoutEditor editor) {
2722        _LE = editor;
2723    }
2724
2725    protected boolean getUseConnectivity() {
2726        return _UseConnectivity;
2727    }
2728
2729    protected void setUseConnectivity(boolean set) {
2730        _UseConnectivity = set;
2731    }
2732
2733    protected void setSignalType(int type) {
2734        _SignalType = type;
2735    }
2736
2737    protected int getSignalType() {
2738        return _SignalType;
2739    }
2740
2741    protected String getSignalTypeString() {
2742        switch (_SignalType) {
2743            case SIGNALHEAD:
2744                return Bundle.getMessage("SignalType1");
2745            case SIGNALMAST:
2746                return Bundle.getMessage("SignalType2");
2747            case SECTIONSALLOCATED:
2748                return Bundle.getMessage("SignalType3");
2749            default:
2750                return "Unknown";
2751        }
2752    }
2753
2754    protected void setStoppingSpeedName(String speedName) {
2755        _StoppingSpeedName = speedName;
2756    }
2757
2758    protected String getStoppingSpeedName() {
2759        return _StoppingSpeedName;
2760    }
2761
2762    protected float getMaximumLineSpeed() {
2763        return maximumLineSpeed;
2764    }
2765
2766    protected void setTrainsFrom(TrainsFrom value ) {
2767        _TrainsFrom = value;
2768    }
2769
2770    protected TrainsFrom getTrainsFrom() {
2771        return _TrainsFrom;
2772    }
2773
2774    protected boolean getAutoAllocate() {
2775        return _AutoAllocate;
2776    }
2777
2778    protected boolean getAutoRelease() {
2779        return _AutoRelease;
2780    }
2781
2782    protected void stopStartAutoAllocateRelease() {
2783        if (_AutoAllocate || _AutoRelease) {
2784            if (editorManager.getAll(LayoutEditor.class).size() > 0) {
2785                if (autoAllocate == null) {
2786                    autoAllocate = new AutoAllocate(this,allocationRequests);
2787                    autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator ");
2788                    autoAllocateThread.start();
2789                }
2790            } else {
2791                JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Error39"),
2792                        Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
2793                _AutoAllocate = false;
2794                if (autoAllocateBox != null) {
2795                    autoAllocateBox.setSelected(_AutoAllocate);
2796                }
2797                return;
2798            }
2799        } else {
2800            //no need for autoallocateRelease
2801            if (autoAllocate != null) {
2802                autoAllocate.setAbort();
2803                autoAllocate = null;
2804            }
2805        }
2806
2807    }
2808    protected void setAutoAllocate(boolean set) {
2809        _AutoAllocate = set;
2810        stopStartAutoAllocateRelease();
2811        if (autoAllocateBox != null) {
2812            autoAllocateBox.setSelected(_AutoAllocate);
2813        }
2814    }
2815
2816    protected void setAutoRelease(boolean set) {
2817        _AutoRelease = set;
2818        stopStartAutoAllocateRelease();
2819        if (autoReleaseBox != null) {
2820            autoReleaseBox.setSelected(_AutoAllocate);
2821        }
2822    }
2823
2824    protected AutoTurnouts getAutoTurnoutsHelper () {
2825        return autoTurnouts;
2826    }
2827
2828    protected boolean getAutoTurnouts() {
2829        return _AutoTurnouts;
2830    }
2831
2832    protected void setAutoTurnouts(boolean set) {
2833        _AutoTurnouts = set;
2834    }
2835
2836    protected boolean getTrustKnownTurnouts() {
2837        return _TrustKnownTurnouts;
2838    }
2839
2840    protected void setTrustKnownTurnouts(boolean set) {
2841        _TrustKnownTurnouts = set;
2842    }
2843
2844    protected int getMinThrottleInterval() {
2845        return _MinThrottleInterval;
2846    }
2847
2848    protected void setMinThrottleInterval(int set) {
2849        _MinThrottleInterval = set;
2850    }
2851
2852    protected int getFullRampTime() {
2853        return _FullRampTime;
2854    }
2855
2856    protected void setFullRampTime(int set) {
2857        _FullRampTime = set;
2858    }
2859
2860    protected boolean getHasOccupancyDetection() {
2861        return _HasOccupancyDetection;
2862    }
2863
2864    protected void setHasOccupancyDetection(boolean set) {
2865        _HasOccupancyDetection = set;
2866    }
2867
2868    protected boolean getSetSSLDirectionalSensors() {
2869        return _SetSSLDirectionalSensors;
2870    }
2871
2872    protected void setSetSSLDirectionalSensors(boolean set) {
2873        _SetSSLDirectionalSensors = set;
2874    }
2875
2876    protected boolean getUseScaleMeters() {
2877        return _UseScaleMeters;
2878    }
2879
2880    protected void setUseScaleMeters(boolean set) {
2881        _UseScaleMeters = set;
2882    }
2883
2884    protected boolean getShortActiveTrainNames() {
2885        return _ShortActiveTrainNames;
2886    }
2887
2888    protected void setShortActiveTrainNames(boolean set) {
2889        _ShortActiveTrainNames = set;
2890        if (allocatedSectionTableModel != null) {
2891            allocatedSectionTableModel.fireTableDataChanged();
2892        }
2893        if (allocationRequestTableModel != null) {
2894            allocationRequestTableModel.fireTableDataChanged();
2895        }
2896    }
2897
2898    protected boolean getShortNameInBlock() {
2899        return _ShortNameInBlock;
2900    }
2901
2902    protected void setShortNameInBlock(boolean set) {
2903        _ShortNameInBlock = set;
2904    }
2905
2906    protected boolean getRosterEntryInBlock() {
2907        return _RosterEntryInBlock;
2908    }
2909
2910    protected void setRosterEntryInBlock(boolean set) {
2911        _RosterEntryInBlock = set;
2912    }
2913
2914    protected boolean getExtraColorForAllocated() {
2915        return _ExtraColorForAllocated;
2916    }
2917
2918    protected void setExtraColorForAllocated(boolean set) {
2919        _ExtraColorForAllocated = set;
2920    }
2921
2922    protected boolean getNameInAllocatedBlock() {
2923        return _NameInAllocatedBlock;
2924    }
2925
2926    protected void setNameInAllocatedBlock(boolean set) {
2927        _NameInAllocatedBlock = set;
2928    }
2929
2930    protected Scale getScale() {
2931        return _LayoutScale;
2932    }
2933
2934    protected void setScale(Scale sc) {
2935        _LayoutScale = sc;
2936    }
2937
2938    public List<ActiveTrain> getActiveTrainsList() {
2939        return activeTrainsList;
2940    }
2941
2942    protected List<AllocatedSection> getAllocatedSectionsList() {
2943        return allocatedSections;
2944    }
2945
2946    public ActiveTrain getActiveTrainForRoster(RosterEntry re) {
2947        if ( _TrainsFrom != TrainsFrom.TRAINSFROMROSTER) {
2948            return null;
2949        }
2950        for (ActiveTrain at : activeTrainsList) {
2951            if (at.getRosterEntry().equals(re)) {
2952                return at;
2953            }
2954        }
2955        return null;
2956
2957    }
2958
2959    protected boolean getSupportVSDecoder() {
2960        return _SupportVSDecoder;
2961    }
2962
2963    protected void setSupportVSDecoder(boolean set) {
2964        _SupportVSDecoder = set;
2965    }
2966
2967    // called by ActivateTrainFrame after a new train is all set up
2968    //      Dispatcher side of activating a new train should be completed here
2969    // Jay Janzen protection changed to public for access via scripting
2970    public void newTrainDone(ActiveTrain at) {
2971        if (at != null) {
2972            // a new active train was created, check for delayed start
2973            if (at.getDelayedStart() != ActiveTrain.NODELAY && (!at.getStarted())) {
2974                delayedTrains.add(at);
2975                fastClockWarn(true);
2976            } // djd needs work here
2977            // check for delayed restart
2978            else if (at.getDelayedRestart() == ActiveTrain.TIMEDDELAY) {
2979                fastClockWarn(false);
2980            }
2981        }
2982        if (atFrame != null) {
2983            atFrame.setVisible(false);
2984            atFrame.dispose();
2985            atFrame = null;
2986        }
2987        newTrainActive = false;
2988    }
2989
2990    protected void removeDelayedTrain(ActiveTrain at) {
2991        delayedTrains.remove(at);
2992    }
2993
2994    private void fastClockWarn(boolean wMess) {
2995        if (fastClockSensor.getState() == Sensor.ACTIVE) {
2996            return;
2997        }
2998        // warn that the fast clock is not running
2999        String mess = "";
3000        if (wMess) {
3001            mess = Bundle.getMessage("FastClockWarn");
3002        } else {
3003            mess = Bundle.getMessage("FastClockWarn2");
3004        }
3005        int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
3006                mess, Bundle.getMessage("WarningTitle"),
3007                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
3008                new Object[]{Bundle.getMessage("ButtonYesStart"), Bundle.getMessage("ButtonNo")},
3009                Bundle.getMessage("ButtonNo"));
3010        if (selectedValue == 0) {
3011            try {
3012                fastClockSensor.setState(Sensor.ACTIVE);
3013            } catch (jmri.JmriException reason) {
3014                log.error("Exception when setting fast clock sensor");
3015            }
3016        }
3017    }
3018
3019    // Jay Janzen
3020    // Protection changed to public to allow access via scripting
3021    public AutoTrainsFrame getAutoTrainsFrame() {
3022        return _autoTrainsFrame;
3023    }
3024
3025    /**
3026     * Table model for Active Trains Table in Dispatcher window
3027     */
3028    public class ActiveTrainsTableModel extends javax.swing.table.AbstractTableModel implements
3029            java.beans.PropertyChangeListener {
3030
3031        public static final int TRANSIT_COLUMN = 0;
3032        public static final int TRANSIT_COLUMN_U = 1;
3033        public static final int TRAIN_COLUMN = 2;
3034        public static final int TYPE_COLUMN = 3;
3035        public static final int STATUS_COLUMN = 4;
3036        public static final int MODE_COLUMN = 5;
3037        public static final int ALLOCATED_COLUMN = 6;
3038        public static final int ALLOCATED_COLUMN_U = 7;
3039        public static final int NEXTSECTION_COLUMN = 8;
3040        public static final int NEXTSECTION_COLUMN_U = 9;
3041        public static final int ALLOCATEBUTTON_COLUMN = 10;
3042        public static final int TERMINATEBUTTON_COLUMN = 11;
3043        public static final int RESTARTCHECKBOX_COLUMN = 12;
3044        public static final int ISAUTO_COLUMN = 13;
3045        public static final int CURRENTSIGNAL_COLUMN = 14;
3046        public static final int CURRENTSIGNAL_COLUMN_U = 15;
3047        public static final int DCC_ADDRESS = 16;
3048        public static final int MAX_COLUMN = 16;
3049        public ActiveTrainsTableModel() {
3050            super();
3051        }
3052
3053        @Override
3054        public void propertyChange(java.beans.PropertyChangeEvent e) {
3055            if (e.getPropertyName().equals("length")) {
3056                fireTableDataChanged();
3057            }
3058        }
3059
3060        @Override
3061        public Class<?> getColumnClass(int col) {
3062            switch (col) {
3063                case ALLOCATEBUTTON_COLUMN:
3064                case TERMINATEBUTTON_COLUMN:
3065                    return JButton.class;
3066                case RESTARTCHECKBOX_COLUMN:
3067                case ISAUTO_COLUMN:
3068                    return Boolean.class;
3069                default:
3070                    return String.class;
3071            }
3072        }
3073
3074        @Override
3075        public int getColumnCount() {
3076            return MAX_COLUMN + 1;
3077        }
3078
3079        @Override
3080        public int getRowCount() {
3081            return (activeTrainsList.size());
3082        }
3083
3084        @Override
3085        public boolean isCellEditable(int row, int col) {
3086            switch (col) {
3087                case ALLOCATEBUTTON_COLUMN:
3088                case TERMINATEBUTTON_COLUMN:
3089                case RESTARTCHECKBOX_COLUMN:
3090                    return (true);
3091                default:
3092                    return (false);
3093            }
3094        }
3095
3096        @Override
3097        public String getColumnName(int col) {
3098            switch (col) {
3099                case TRANSIT_COLUMN:
3100                    return Bundle.getMessage("TransitColumnSysTitle");
3101                case TRANSIT_COLUMN_U:
3102                    return Bundle.getMessage("TransitColumnTitle");
3103                case TRAIN_COLUMN:
3104                    return Bundle.getMessage("TrainColumnTitle");
3105                case TYPE_COLUMN:
3106                    return Bundle.getMessage("TrainTypeColumnTitle");
3107                case STATUS_COLUMN:
3108                    return Bundle.getMessage("TrainStatusColumnTitle");
3109                case MODE_COLUMN:
3110                    return Bundle.getMessage("TrainModeColumnTitle");
3111                case ALLOCATED_COLUMN:
3112                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
3113                case ALLOCATED_COLUMN_U:
3114                    return Bundle.getMessage("AllocatedSectionColumnTitle");
3115                case NEXTSECTION_COLUMN:
3116                    return Bundle.getMessage("NextSectionColumnSysTitle");
3117                case NEXTSECTION_COLUMN_U:
3118                    return Bundle.getMessage("NextSectionColumnTitle");
3119                case RESTARTCHECKBOX_COLUMN:
3120                    return(Bundle.getMessage("AutoRestartColumnTitle"));
3121                case ALLOCATEBUTTON_COLUMN:
3122                    return(Bundle.getMessage("AllocateButton"));
3123                case TERMINATEBUTTON_COLUMN:
3124                    return(Bundle.getMessage("TerminateTrain"));
3125                case ISAUTO_COLUMN:
3126                    return(Bundle.getMessage("AutoColumnTitle"));
3127                case CURRENTSIGNAL_COLUMN:
3128                    return(Bundle.getMessage("CurrentSignalSysColumnTitle"));
3129                case CURRENTSIGNAL_COLUMN_U:
3130                    return(Bundle.getMessage("CurrentSignalColumnTitle"));
3131                case DCC_ADDRESS:
3132                    return(Bundle.getMessage("DccColumnTitleColumnTitle"));
3133                default:
3134                    return "";
3135            }
3136        }
3137
3138        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
3139                                justification="better to keep cases in column order rather than to combine")
3140        public int getPreferredWidth(int col) {
3141            switch (col) {
3142                case TRANSIT_COLUMN:
3143                case TRANSIT_COLUMN_U:
3144                case TRAIN_COLUMN:
3145                    return new JTextField(17).getPreferredSize().width;
3146                case TYPE_COLUMN:
3147                    return new JTextField(16).getPreferredSize().width;
3148                case STATUS_COLUMN:
3149                    return new JTextField(8).getPreferredSize().width;
3150                case MODE_COLUMN:
3151                    return new JTextField(11).getPreferredSize().width;
3152                case ALLOCATED_COLUMN:
3153                case ALLOCATED_COLUMN_U:
3154                    return new JTextField(17).getPreferredSize().width;
3155                case NEXTSECTION_COLUMN:
3156                case NEXTSECTION_COLUMN_U:
3157                    return new JTextField(17).getPreferredSize().width;
3158                case ALLOCATEBUTTON_COLUMN:
3159                case TERMINATEBUTTON_COLUMN:
3160                case RESTARTCHECKBOX_COLUMN:
3161                case ISAUTO_COLUMN:
3162                case CURRENTSIGNAL_COLUMN:
3163                case CURRENTSIGNAL_COLUMN_U:
3164                case DCC_ADDRESS:
3165                    return new JTextField(5).getPreferredSize().width;
3166                default:
3167                    // fall through
3168                    break;
3169            }
3170            return new JTextField(5).getPreferredSize().width;
3171        }
3172
3173        @Override
3174        public Object getValueAt(int r, int c) {
3175            int rx = r;
3176            if (rx >= activeTrainsList.size()) {
3177                return null;
3178            }
3179            ActiveTrain at = activeTrainsList.get(rx);
3180            switch (c) {
3181                case TRANSIT_COLUMN:
3182                    return (at.getTransit().getSystemName());
3183                case TRANSIT_COLUMN_U:
3184                    if (at.getTransit() != null && at.getTransit().getUserName() != null) {
3185                        return (at.getTransit().getUserName());
3186                    } else {
3187                        return "";
3188                    }
3189                case TRAIN_COLUMN:
3190                    return (at.getTrainName());
3191                case TYPE_COLUMN:
3192                    return (at.getTrainTypeText());
3193                case STATUS_COLUMN:
3194                    return (at.getStatusText());
3195                case MODE_COLUMN:
3196                    return (at.getModeText());
3197                case ALLOCATED_COLUMN:
3198                    if (at.getLastAllocatedSection() != null) {
3199                        return (at.getLastAllocatedSection().getSystemName());
3200                    } else {
3201                        return "<none>";
3202                    }
3203                case ALLOCATED_COLUMN_U:
3204                    if (at.getLastAllocatedSection() != null && at.getLastAllocatedSection().getUserName() != null) {
3205                        return (at.getLastAllocatedSection().getUserName());
3206                    } else {
3207                        return "<none>";
3208                    }
3209                case NEXTSECTION_COLUMN:
3210                    if (at.getNextSectionToAllocate() != null) {
3211                        return (at.getNextSectionToAllocate().getSystemName());
3212                    } else {
3213                        return "<none>";
3214                    }
3215                case NEXTSECTION_COLUMN_U:
3216                    if (at.getNextSectionToAllocate() != null && at.getNextSectionToAllocate().getUserName() != null) {
3217                        return (at.getNextSectionToAllocate().getUserName());
3218                    } else {
3219                        return "<none>";
3220                    }
3221                case ALLOCATEBUTTON_COLUMN:
3222                    return Bundle.getMessage("AllocateButtonName");
3223                case TERMINATEBUTTON_COLUMN:
3224                    return Bundle.getMessage("TerminateTrain");
3225                case RESTARTCHECKBOX_COLUMN:
3226                    return at.getResetWhenDone();
3227                case ISAUTO_COLUMN:
3228                    return at.getAutoRun();
3229                case CURRENTSIGNAL_COLUMN:
3230                    if (at.getAutoRun()) {
3231                        return(at.getAutoActiveTrain().getCurrentSignal());
3232                    } else {
3233                        return("NA");
3234                    }
3235                case CURRENTSIGNAL_COLUMN_U:
3236                    if (at.getAutoRun()) {
3237                        return(at.getAutoActiveTrain().getCurrentSignalUserName());
3238                    } else {
3239                        return("NA");
3240                    }
3241                case DCC_ADDRESS:
3242                    if (at.getDccAddress() != null) {
3243                        return(at.getDccAddress());
3244                    } else {
3245                        return("NA");
3246                    }
3247                default:
3248                    return (" ");
3249            }
3250        }
3251
3252        @Override
3253        public void setValueAt(Object value, int row, int col) {
3254            if (col == ALLOCATEBUTTON_COLUMN) {
3255                // open an allocate window
3256                allocateNextRequested(row);
3257            }
3258            if (col == TERMINATEBUTTON_COLUMN) {
3259                if (activeTrainsList.get(row) != null) {
3260                    terminateActiveTrain(activeTrainsList.get(row),true,false);
3261                }
3262            }
3263            if (col == RESTARTCHECKBOX_COLUMN) {
3264                ActiveTrain at = null;
3265                at = activeTrainsList.get(row);
3266                if (activeTrainsList.get(row) != null) {
3267                    if (!at.getResetWhenDone()) {
3268                        at.setResetWhenDone(true);
3269                        return;
3270                    }
3271                    at.setResetWhenDone(false);
3272                    for (int j = restartingTrainsList.size(); j > 0; j--) {
3273                        if (restartingTrainsList.get(j - 1) == at) {
3274                            restartingTrainsList.remove(j - 1);
3275                            return;
3276                        }
3277                    }
3278                }
3279            }
3280        }
3281    }
3282
3283    /**
3284     * Table model for Allocation Request Table in Dispatcher window
3285     */
3286    public class AllocationRequestTableModel extends javax.swing.table.AbstractTableModel implements
3287            java.beans.PropertyChangeListener {
3288
3289        public static final int TRANSIT_COLUMN = 0;
3290        public static final int TRANSIT_COLUMN_U = 1;
3291        public static final int TRAIN_COLUMN = 2;
3292        public static final int PRIORITY_COLUMN = 3;
3293        public static final int TRAINTYPE_COLUMN = 4;
3294        public static final int SECTION_COLUMN = 5;
3295        public static final int SECTION_COLUMN_U = 6;
3296        public static final int STATUS_COLUMN = 7;
3297        public static final int OCCUPANCY_COLUMN = 8;
3298        public static final int SECTIONLENGTH_COLUMN = 9;
3299        public static final int ALLOCATEBUTTON_COLUMN = 10;
3300        public static final int CANCELBUTTON_COLUMN = 11;
3301        public static final int MAX_COLUMN = 11;
3302
3303        public AllocationRequestTableModel() {
3304            super();
3305        }
3306
3307        @Override
3308        public void propertyChange(java.beans.PropertyChangeEvent e) {
3309            if (e.getPropertyName().equals("length")) {
3310                fireTableDataChanged();
3311            }
3312        }
3313
3314        @Override
3315        public Class<?> getColumnClass(int c) {
3316            if (c == CANCELBUTTON_COLUMN) {
3317                return JButton.class;
3318            }
3319            if (c == ALLOCATEBUTTON_COLUMN) {
3320                return JButton.class;
3321            }
3322            //if (c == CANCELRESTART_COLUMN) {
3323            //    return JButton.class;
3324            //}
3325            return String.class;
3326        }
3327
3328        @Override
3329        public int getColumnCount() {
3330            return MAX_COLUMN + 1;
3331        }
3332
3333        @Override
3334        public int getRowCount() {
3335            return (allocationRequests.size());
3336        }
3337
3338        @Override
3339        public boolean isCellEditable(int r, int c) {
3340            if (c == CANCELBUTTON_COLUMN) {
3341                return (true);
3342            }
3343            if (c == ALLOCATEBUTTON_COLUMN) {
3344                return (true);
3345            }
3346            return (false);
3347        }
3348
3349        @Override
3350        public String getColumnName(int col) {
3351            switch (col) {
3352                case TRANSIT_COLUMN:
3353                    return Bundle.getMessage("TransitColumnSysTitle");
3354                case TRANSIT_COLUMN_U:
3355                    return Bundle.getMessage("TransitColumnTitle");
3356                case TRAIN_COLUMN:
3357                    return Bundle.getMessage("TrainColumnTitle");
3358                case PRIORITY_COLUMN:
3359                    return Bundle.getMessage("PriorityLabel");
3360                case TRAINTYPE_COLUMN:
3361                    return Bundle.getMessage("TrainTypeColumnTitle");
3362                case SECTION_COLUMN:
3363                    return Bundle.getMessage("SectionColumnSysTitle");
3364                case SECTION_COLUMN_U:
3365                    return Bundle.getMessage("SectionColumnTitle");
3366                case STATUS_COLUMN:
3367                    return Bundle.getMessage("StatusColumnTitle");
3368                case OCCUPANCY_COLUMN:
3369                    return Bundle.getMessage("OccupancyColumnTitle");
3370                case SECTIONLENGTH_COLUMN:
3371                    return Bundle.getMessage("SectionLengthColumnTitle");
3372                case ALLOCATEBUTTON_COLUMN:
3373                    return Bundle.getMessage("AllocateButton");
3374                case CANCELBUTTON_COLUMN:
3375                    return Bundle.getMessage("ButtonCancel");
3376                default:
3377                    return "";
3378            }
3379        }
3380
3381        public int getPreferredWidth(int col) {
3382            switch (col) {
3383                case TRANSIT_COLUMN:
3384                case TRANSIT_COLUMN_U:
3385                case TRAIN_COLUMN:
3386                    return new JTextField(17).getPreferredSize().width;
3387                case PRIORITY_COLUMN:
3388                    return new JTextField(8).getPreferredSize().width;
3389                case TRAINTYPE_COLUMN:
3390                    return new JTextField(15).getPreferredSize().width;
3391                case SECTION_COLUMN:
3392                    return new JTextField(25).getPreferredSize().width;
3393                case STATUS_COLUMN:
3394                    return new JTextField(15).getPreferredSize().width;
3395                case OCCUPANCY_COLUMN:
3396                    return new JTextField(10).getPreferredSize().width;
3397                case SECTIONLENGTH_COLUMN:
3398                    return new JTextField(8).getPreferredSize().width;
3399                case ALLOCATEBUTTON_COLUMN:
3400                    return new JTextField(12).getPreferredSize().width;
3401                case CANCELBUTTON_COLUMN:
3402                    return new JTextField(10).getPreferredSize().width;
3403                default:
3404                    // fall through
3405                    break;
3406            }
3407            return new JTextField(5).getPreferredSize().width;
3408        }
3409
3410        @Override
3411        public Object getValueAt(int r, int c) {
3412            int rx = r;
3413            if (rx >= allocationRequests.size()) {
3414                return null;
3415            }
3416            AllocationRequest ar = allocationRequests.get(rx);
3417            switch (c) {
3418                case TRANSIT_COLUMN:
3419                    return (ar.getActiveTrain().getTransit().getSystemName());
3420                case TRANSIT_COLUMN_U:
3421                    if (ar.getActiveTrain().getTransit() != null && ar.getActiveTrain().getTransit().getUserName() != null) {
3422                        return (ar.getActiveTrain().getTransit().getUserName());
3423                    } else {
3424                        return "";
3425                    }
3426                case TRAIN_COLUMN:
3427                    return (ar.getActiveTrain().getTrainName());
3428                case PRIORITY_COLUMN:
3429                    return ("   " + ar.getActiveTrain().getPriority());
3430                case TRAINTYPE_COLUMN:
3431                    return (ar.getActiveTrain().getTrainTypeText());
3432                case SECTION_COLUMN:
3433                    if (ar.getSection() != null) {
3434                        return (ar.getSection().getSystemName());
3435                    } else {
3436                        return "<none>";
3437                    }
3438                case SECTION_COLUMN_U:
3439                    if (ar.getSection() != null && ar.getSection().getUserName() != null) {
3440                        return (ar.getSection().getUserName());
3441                    } else {
3442                        return "<none>";
3443                    }
3444                case STATUS_COLUMN:
3445                    if (ar.getSection().getState() == Section.FREE) {
3446                        return Bundle.getMessage("FREE");
3447                    }
3448                    return Bundle.getMessage("ALLOCATED");
3449                case OCCUPANCY_COLUMN:
3450                    if (!_HasOccupancyDetection) {
3451                        return Bundle.getMessage("UNKNOWN");
3452                    }
3453                    if (ar.getSection().getOccupancy() == Section.OCCUPIED) {
3454                        return Bundle.getMessage("OCCUPIED");
3455                    }
3456                    return Bundle.getMessage("UNOCCUPIED");
3457                case SECTIONLENGTH_COLUMN:
3458                    return ("  " + ar.getSection().getLengthI(_UseScaleMeters, _LayoutScale));
3459                case ALLOCATEBUTTON_COLUMN:
3460                    return Bundle.getMessage("AllocateButton");
3461                case CANCELBUTTON_COLUMN:
3462                    return Bundle.getMessage("ButtonCancel");
3463                default:
3464                    return (" ");
3465            }
3466        }
3467
3468        @Override
3469        public void setValueAt(Object value, int row, int col) {
3470            if (col == ALLOCATEBUTTON_COLUMN) {
3471                // open an allocate window
3472                allocateRequested(row);
3473            }
3474            if (col == CANCELBUTTON_COLUMN) {
3475                // open an allocate window
3476                cancelAllocationRequest(row);
3477            }
3478        }
3479    }
3480
3481    /**
3482     * Table model for Allocated Section Table
3483     */
3484    public class AllocatedSectionTableModel extends javax.swing.table.AbstractTableModel implements
3485            java.beans.PropertyChangeListener {
3486
3487        public static final int TRANSIT_COLUMN = 0;
3488        public static final int TRANSIT_COLUMN_U = 1;
3489        public static final int TRAIN_COLUMN = 2;
3490        public static final int SECTION_COLUMN = 3;
3491        public static final int SECTION_COLUMN_U = 4;
3492        public static final int OCCUPANCY_COLUMN = 5;
3493        public static final int USESTATUS_COLUMN = 6;
3494        public static final int RELEASEBUTTON_COLUMN = 7;
3495        public static final int MAX_COLUMN = 7;
3496
3497        public AllocatedSectionTableModel() {
3498            super();
3499        }
3500
3501        @Override
3502        public void propertyChange(java.beans.PropertyChangeEvent e) {
3503            if (e.getPropertyName().equals("length")) {
3504                fireTableDataChanged();
3505            }
3506        }
3507
3508        @Override
3509        public Class<?> getColumnClass(int c) {
3510            if (c == RELEASEBUTTON_COLUMN) {
3511                return JButton.class;
3512            }
3513            return String.class;
3514        }
3515
3516        @Override
3517        public int getColumnCount() {
3518            return MAX_COLUMN + 1;
3519        }
3520
3521        @Override
3522        public int getRowCount() {
3523            return (allocatedSections.size());
3524        }
3525
3526        @Override
3527        public boolean isCellEditable(int r, int c) {
3528            if (c == RELEASEBUTTON_COLUMN) {
3529                return (true);
3530            }
3531            return (false);
3532        }
3533
3534        @Override
3535        public String getColumnName(int col) {
3536            switch (col) {
3537                case TRANSIT_COLUMN:
3538                    return Bundle.getMessage("TransitColumnSysTitle");
3539                case TRANSIT_COLUMN_U:
3540                    return Bundle.getMessage("TransitColumnTitle");
3541                case TRAIN_COLUMN:
3542                    return Bundle.getMessage("TrainColumnTitle");
3543                case SECTION_COLUMN:
3544                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
3545                case SECTION_COLUMN_U:
3546                    return Bundle.getMessage("AllocatedSectionColumnTitle");
3547                case OCCUPANCY_COLUMN:
3548                    return Bundle.getMessage("OccupancyColumnTitle");
3549                case USESTATUS_COLUMN:
3550                    return Bundle.getMessage("UseStatusColumnTitle");
3551                case RELEASEBUTTON_COLUMN:
3552                    return Bundle.getMessage("ReleaseButton");
3553                default:
3554                    return "";
3555            }
3556        }
3557
3558        public int getPreferredWidth(int col) {
3559            switch (col) {
3560                case TRANSIT_COLUMN:
3561                case TRANSIT_COLUMN_U:
3562                case TRAIN_COLUMN:
3563                    return new JTextField(17).getPreferredSize().width;
3564                case SECTION_COLUMN:
3565                case SECTION_COLUMN_U:
3566                    return new JTextField(25).getPreferredSize().width;
3567                case OCCUPANCY_COLUMN:
3568                    return new JTextField(10).getPreferredSize().width;
3569                case USESTATUS_COLUMN:
3570                    return new JTextField(15).getPreferredSize().width;
3571                case RELEASEBUTTON_COLUMN:
3572                    return new JTextField(12).getPreferredSize().width;
3573                default:
3574                    // fall through
3575                    break;
3576            }
3577            return new JTextField(5).getPreferredSize().width;
3578        }
3579
3580        @Override
3581        public Object getValueAt(int r, int c) {
3582            int rx = r;
3583            if (rx >= allocatedSections.size()) {
3584                return null;
3585            }
3586            AllocatedSection as = allocatedSections.get(rx);
3587            switch (c) {
3588                case TRANSIT_COLUMN:
3589                    return (as.getActiveTrain().getTransit().getSystemName());
3590                case TRANSIT_COLUMN_U:
3591                    if (as.getActiveTrain().getTransit() != null && as.getActiveTrain().getTransit().getUserName() != null) {
3592                        return (as.getActiveTrain().getTransit().getUserName());
3593                    } else {
3594                        return "";
3595                    }
3596                case TRAIN_COLUMN:
3597                    return (as.getActiveTrain().getTrainName());
3598                case SECTION_COLUMN:
3599                    if (as.getSection() != null) {
3600                        return (as.getSection().getSystemName());
3601                    } else {
3602                        return "<none>";
3603                    }
3604                case SECTION_COLUMN_U:
3605                    if (as.getSection() != null && as.getSection().getUserName() != null) {
3606                        return (as.getSection().getUserName());
3607                    } else {
3608                        return "<none>";
3609                    }
3610                case OCCUPANCY_COLUMN:
3611                    if (!_HasOccupancyDetection) {
3612                        return Bundle.getMessage("UNKNOWN");
3613                    }
3614                    if (as.getSection().getOccupancy() == Section.OCCUPIED) {
3615                        return Bundle.getMessage("OCCUPIED");
3616                    }
3617                    return Bundle.getMessage("UNOCCUPIED");
3618                case USESTATUS_COLUMN:
3619                    if (!as.getEntered()) {
3620                        return Bundle.getMessage("NotEntered");
3621                    }
3622                    if (as.getExited()) {
3623                        return Bundle.getMessage("Exited");
3624                    }
3625                    return Bundle.getMessage("Entered");
3626                case RELEASEBUTTON_COLUMN:
3627                    return Bundle.getMessage("ReleaseButton");
3628                default:
3629                    return (" ");
3630            }
3631        }
3632
3633        @Override
3634        public void setValueAt(Object value, int row, int col) {
3635            if (col == RELEASEBUTTON_COLUMN) {
3636                releaseAllocatedSectionFromTable(row);
3637            }
3638        }
3639    }
3640
3641    /*
3642     * Mouse popup stuff
3643     */
3644
3645    /**
3646     * Process the column header click
3647     * @param e     the evnt data
3648     * @param table the JTable
3649     */
3650    protected void showTableHeaderPopup(JmriMouseEvent e, JTable table) {
3651        JPopupMenu popupMenu = new JPopupMenu();
3652        XTableColumnModel tcm = (XTableColumnModel) table.getColumnModel();
3653        for (int i = 0; i < tcm.getColumnCount(false); i++) {
3654            TableColumn tc = tcm.getColumnByModelIndex(i);
3655            String columnName = table.getModel().getColumnName(i);
3656            if (columnName != null && !columnName.equals("")) {
3657                JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(table.getModel().getColumnName(i), tcm.isColumnVisible(tc));
3658                menuItem.addActionListener(new HeaderActionListener(tc, tcm));
3659                popupMenu.add(menuItem);
3660            }
3661
3662        }
3663        popupMenu.show(e.getComponent(), e.getX(), e.getY());
3664    }
3665
3666    /**
3667     * Adds the column header pop listener to a JTable using XTableColumnModel
3668     * @param table The JTable effected.
3669     */
3670    protected void addMouseListenerToHeader(JTable table) {
3671        JmriMouseListener mouseHeaderListener = new TableHeaderListener(table);
3672        table.getTableHeader().addMouseListener(JmriMouseListener.adapt(mouseHeaderListener));
3673    }
3674
3675    static protected class HeaderActionListener implements ActionListener {
3676
3677        TableColumn tc;
3678        XTableColumnModel tcm;
3679
3680        HeaderActionListener(TableColumn tc, XTableColumnModel tcm) {
3681            this.tc = tc;
3682            this.tcm = tcm;
3683        }
3684
3685        @Override
3686        public void actionPerformed(ActionEvent e) {
3687            JCheckBoxMenuItem check = (JCheckBoxMenuItem) e.getSource();
3688            //Do not allow the last column to be hidden
3689            if (!check.isSelected() && tcm.getColumnCount(true) == 1) {
3690                return;
3691            }
3692            tcm.setColumnVisible(tc, check.isSelected());
3693        }
3694    }
3695
3696    /**
3697     * Class to support Columnheader popup menu on XTableColum model.
3698     */
3699    class TableHeaderListener extends JmriMouseAdapter {
3700
3701        JTable table;
3702
3703        TableHeaderListener(JTable tbl) {
3704            super();
3705            table = tbl;
3706        }
3707
3708        /**
3709         * {@inheritDoc}
3710         */
3711        @Override
3712        public void mousePressed(JmriMouseEvent e) {
3713            if (e.isPopupTrigger()) {
3714                showTableHeaderPopup(e, table);
3715            }
3716        }
3717
3718        /**
3719         * {@inheritDoc}
3720         */
3721        @Override
3722        public void mouseReleased(JmriMouseEvent e) {
3723            if (e.isPopupTrigger()) {
3724                showTableHeaderPopup(e, table);
3725            }
3726        }
3727
3728        /**
3729         * {@inheritDoc}
3730         */
3731        @Override
3732        public void mouseClicked(JmriMouseEvent e) {
3733            if (e.isPopupTrigger()) {
3734                showTableHeaderPopup(e, table);
3735            }
3736        }
3737    }
3738
3739    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DispatcherFrame.class);
3740
3741}