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