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                                if (foundOne) {
2481                                    log.debug("{}: releasing section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS));
2482                                    doReleaseAllocatedSection(as, false);
2483                                }
2484                            }
2485                        }
2486                    }
2487                } catch (RuntimeException e) {
2488                    log.warn("checkAutoRelease failed  - maybe the AllocatedSection was removed due to a terminating train? {}", e.toString());
2489                    continue;
2490                }
2491            }
2492        }
2493        if (_AutoAllocate) {
2494            queueScanOfAllocationRequests();
2495        }
2496    }
2497
2498    /**
2499     * Releases an allocated Section, and removes it from the Dispatcher Input.
2500     *
2501     * @param as               the section to release
2502     * @param terminatingTrain true if the associated train is being terminated;
2503     *                         false otherwise
2504     */
2505    public void releaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2506        if (_AutoAllocate ) {
2507            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_ONE,as,terminatingTrain));
2508        } else {
2509            doReleaseAllocatedSection( as,  terminatingTrain);
2510        }
2511    }
2512    protected void doReleaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2513        // check that section is not occupied if not terminating train
2514        if (!terminatingTrain && (as.getSection().getOccupancy() == Section.OCCUPIED)) {
2515            // warn the manual dispatcher that Allocated Section is occupied
2516            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, java.text.MessageFormat.format(
2517                    Bundle.getMessage("Question5"), new Object[]{as.getSectionName()}), Bundle.getMessage("WarningTitle"),
2518                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2519                    new Object[]{Bundle.getMessage("ButtonRelease"), Bundle.getMessage("ButtonNo")},
2520                    Bundle.getMessage("ButtonNo"));
2521            if (selectedValue != 0 ) { // array position 0, release not pressed
2522                return;   // return without releasing if "No" or "Cancel" response
2523            }
2524        }
2525        // release the Allocated Section
2526        for (int i = allocatedSections.size(); i > 0; i--) {
2527            if (as == allocatedSections.get(i - 1)) {
2528                allocatedSections.remove(i - 1);
2529            }
2530        }
2531        as.getSection().setState(Section.FREE);
2532        as.getActiveTrain().removeAllocatedSection(as);
2533        as.dispose();
2534        if (allocatedSectionTableModel != null) {
2535            allocatedSectionTableModel.fireTableDataChanged();
2536        }
2537        allocationRequestTableModel.fireTableDataChanged();
2538        activeTrainsTableModel.fireTableDataChanged();
2539        if (_AutoAllocate) {
2540            queueScanOfAllocationRequests();
2541        }
2542    }
2543
2544    /**
2545     * Updates display when occupancy of an allocated section changes Also
2546     * drives auto release if it is selected
2547     */
2548    public void sectionOccupancyChanged() {
2549        queueReleaseOfCompletedAllocations();
2550        if (allocatedSectionTableModel != null) {
2551            allocatedSectionTableModel.fireTableDataChanged();
2552        }
2553        allocationRequestTableModel.fireTableDataChanged();
2554    }
2555
2556    /**
2557     * Handle activity that is triggered by the fast clock
2558     */
2559    protected void newFastClockMinute() {
2560        for (int i = delayedTrains.size() - 1; i >= 0; i--) {
2561            ActiveTrain at = delayedTrains.get(i);
2562            // check if this Active Train is waiting to start
2563            if ((!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) {
2564                // is it time to start?
2565                if (at.getDelayedStart() == ActiveTrain.TIMEDDELAY) {
2566                    if (isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin())) {
2567                        // allow this train to start
2568                        at.setStarted();
2569                        delayedTrains.remove(i);
2570                    }
2571                }
2572            } else if (at.getStarted() && at.getStatus() == ActiveTrain.READY && at.reachedRestartPoint()) {
2573                if (isFastClockTimeGE(at.getRestartDepartHr(), at.getRestartDepartMin())) {
2574                    at.restart();
2575                    delayedTrains.remove(i);
2576                }
2577            }
2578        }
2579        if (_AutoAllocate) {
2580            queueScanOfAllocationRequests();
2581        }
2582    }
2583
2584    /**
2585     * This method tests time
2586     *
2587     * @param hr  the hour to test against (0-23)
2588     * @param min the minute to test against (0-59)
2589     * @return true if fast clock time and tested time are the same
2590     */
2591    public boolean isFastClockTimeGE(int hr, int min) {
2592        Calendar now = Calendar.getInstance();
2593        now.setTime(fastClock.getTime());
2594        int nowHours = now.get(Calendar.HOUR_OF_DAY);
2595        int nowMinutes = now.get(Calendar.MINUTE);
2596        return ((nowHours * 60) + nowMinutes) == ((hr * 60) + min);
2597    }
2598
2599    // option access methods
2600    protected LayoutEditor getLayoutEditor() {
2601        return _LE;
2602    }
2603
2604    protected void setLayoutEditor(LayoutEditor editor) {
2605        _LE = editor;
2606    }
2607
2608    protected boolean getUseConnectivity() {
2609        return _UseConnectivity;
2610    }
2611
2612    protected void setUseConnectivity(boolean set) {
2613        _UseConnectivity = set;
2614    }
2615
2616    protected void setSignalType(int type) {
2617        _SignalType = type;
2618    }
2619
2620    protected int getSignalType() {
2621        return _SignalType;
2622    }
2623
2624    protected String getSignalTypeString() {
2625        switch (_SignalType) {
2626            case SIGNALHEAD:
2627                return Bundle.getMessage("SignalType1");
2628            case SIGNALMAST:
2629                return Bundle.getMessage("SignalType2");
2630            case SECTIONSALLOCATED:
2631                return Bundle.getMessage("SignalType3");
2632            default:
2633                return "Unknown";
2634        }
2635    }
2636
2637    protected void setStoppingSpeedName(String speedName) {
2638        _StoppingSpeedName = speedName;
2639    }
2640
2641    protected String getStoppingSpeedName() {
2642        return _StoppingSpeedName;
2643    }
2644
2645    protected float getMaximumLineSpeed() {
2646        return maximumLineSpeed;
2647    }
2648
2649    protected void setTrainsFrom(TrainsFrom value ) {
2650        _TrainsFrom = value;
2651    }
2652
2653    protected TrainsFrom getTrainsFrom() {
2654        return _TrainsFrom;
2655    }
2656
2657    protected boolean getAutoAllocate() {
2658        return _AutoAllocate;
2659    }
2660
2661    protected boolean getAutoRelease() {
2662        return _AutoRelease;
2663    }
2664
2665    protected void stopStartAutoAllocateRelease() {
2666        if (_AutoAllocate || _AutoRelease) {
2667            if (editorManager.getAll(LayoutEditor.class).size() > 0) {
2668                if (autoAllocate == null) {
2669                    autoAllocate = new AutoAllocate(this,allocationRequests);
2670                    autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator ");
2671                    autoAllocateThread.start();
2672                }
2673            } else {
2674                JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Error39"),
2675                        Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
2676                _AutoAllocate = false;
2677                if (autoAllocateBox != null) {
2678                    autoAllocateBox.setSelected(_AutoAllocate);
2679                }
2680                return;
2681            }
2682        } else {
2683            //no need for autoallocateRelease
2684            if (autoAllocate != null) {
2685                autoAllocate.setAbort();
2686                autoAllocate = null;
2687            }
2688        }
2689
2690    }
2691    protected void setAutoAllocate(boolean set) {
2692        _AutoAllocate = set;
2693        stopStartAutoAllocateRelease();
2694        if (autoAllocateBox != null) {
2695            autoAllocateBox.setSelected(_AutoAllocate);
2696        }
2697    }
2698
2699    protected void setAutoRelease(boolean set) {
2700        _AutoRelease = set;
2701        stopStartAutoAllocateRelease();
2702        if (autoReleaseBox != null) {
2703            autoReleaseBox.setSelected(_AutoAllocate);
2704        }
2705    }
2706
2707    protected AutoTurnouts getAutoTurnoutsHelper () {
2708        return autoTurnouts;
2709    }
2710
2711    protected boolean getAutoTurnouts() {
2712        return _AutoTurnouts;
2713    }
2714
2715    protected void setAutoTurnouts(boolean set) {
2716        _AutoTurnouts = set;
2717    }
2718
2719    protected boolean getTrustKnownTurnouts() {
2720        return _TrustKnownTurnouts;
2721    }
2722
2723    protected void setTrustKnownTurnouts(boolean set) {
2724        _TrustKnownTurnouts = set;
2725    }
2726
2727    protected int getMinThrottleInterval() {
2728        return _MinThrottleInterval;
2729    }
2730
2731    protected void setMinThrottleInterval(int set) {
2732        _MinThrottleInterval = set;
2733    }
2734
2735    protected int getFullRampTime() {
2736        return _FullRampTime;
2737    }
2738
2739    protected void setFullRampTime(int set) {
2740        _FullRampTime = set;
2741    }
2742
2743    protected boolean getHasOccupancyDetection() {
2744        return _HasOccupancyDetection;
2745    }
2746
2747    protected void setHasOccupancyDetection(boolean set) {
2748        _HasOccupancyDetection = set;
2749    }
2750
2751    protected boolean getSetSSLDirectionalSensors() {
2752        return _SetSSLDirectionalSensors;
2753    }
2754
2755    protected void setSetSSLDirectionalSensors(boolean set) {
2756        _SetSSLDirectionalSensors = set;
2757    }
2758
2759    protected boolean getUseScaleMeters() {
2760        return _UseScaleMeters;
2761    }
2762
2763    protected void setUseScaleMeters(boolean set) {
2764        _UseScaleMeters = set;
2765    }
2766
2767    protected boolean getShortActiveTrainNames() {
2768        return _ShortActiveTrainNames;
2769    }
2770
2771    protected void setShortActiveTrainNames(boolean set) {
2772        _ShortActiveTrainNames = set;
2773        if (allocatedSectionTableModel != null) {
2774            allocatedSectionTableModel.fireTableDataChanged();
2775        }
2776        if (allocationRequestTableModel != null) {
2777            allocationRequestTableModel.fireTableDataChanged();
2778        }
2779    }
2780
2781    protected boolean getShortNameInBlock() {
2782        return _ShortNameInBlock;
2783    }
2784
2785    protected void setShortNameInBlock(boolean set) {
2786        _ShortNameInBlock = set;
2787    }
2788
2789    protected boolean getRosterEntryInBlock() {
2790        return _RosterEntryInBlock;
2791    }
2792
2793    protected void setRosterEntryInBlock(boolean set) {
2794        _RosterEntryInBlock = set;
2795    }
2796
2797    protected boolean getExtraColorForAllocated() {
2798        return _ExtraColorForAllocated;
2799    }
2800
2801    protected void setExtraColorForAllocated(boolean set) {
2802        _ExtraColorForAllocated = set;
2803    }
2804
2805    protected boolean getNameInAllocatedBlock() {
2806        return _NameInAllocatedBlock;
2807    }
2808
2809    protected void setNameInAllocatedBlock(boolean set) {
2810        _NameInAllocatedBlock = set;
2811    }
2812
2813    protected Scale getScale() {
2814        return _LayoutScale;
2815    }
2816
2817    protected void setScale(Scale sc) {
2818        _LayoutScale = sc;
2819    }
2820
2821    public List<ActiveTrain> getActiveTrainsList() {
2822        return activeTrainsList;
2823    }
2824
2825    protected List<AllocatedSection> getAllocatedSectionsList() {
2826        return allocatedSections;
2827    }
2828
2829    public ActiveTrain getActiveTrainForRoster(RosterEntry re) {
2830        if ( _TrainsFrom != TrainsFrom.TRAINSFROMROSTER) {
2831            return null;
2832        }
2833        for (ActiveTrain at : activeTrainsList) {
2834            if (at.getRosterEntry().equals(re)) {
2835                return at;
2836            }
2837        }
2838        return null;
2839
2840    }
2841
2842    protected boolean getSupportVSDecoder() {
2843        return _SupportVSDecoder;
2844    }
2845
2846    protected void setSupportVSDecoder(boolean set) {
2847        _SupportVSDecoder = set;
2848    }
2849
2850    // called by ActivateTrainFrame after a new train is all set up
2851    //      Dispatcher side of activating a new train should be completed here
2852    // Jay Janzen protection changed to public for access via scripting
2853    public void newTrainDone(ActiveTrain at) {
2854        if (at != null) {
2855            // a new active train was created, check for delayed start
2856            if (at.getDelayedStart() != ActiveTrain.NODELAY && (!at.getStarted())) {
2857                delayedTrains.add(at);
2858                fastClockWarn(true);
2859            } // djd needs work here
2860            // check for delayed restart
2861            else if (at.getDelayedRestart() == ActiveTrain.TIMEDDELAY) {
2862                fastClockWarn(false);
2863            }
2864        }
2865        if (atFrame != null) {
2866            atFrame.setVisible(false);
2867            atFrame.dispose();
2868            atFrame = null;
2869        }
2870        newTrainActive = false;
2871    }
2872
2873    protected void removeDelayedTrain(ActiveTrain at) {
2874        delayedTrains.remove(at);
2875    }
2876
2877    private void fastClockWarn(boolean wMess) {
2878        if (fastClockSensor.getState() == Sensor.ACTIVE) {
2879            return;
2880        }
2881        // warn that the fast clock is not running
2882        String mess = "";
2883        if (wMess) {
2884            mess = Bundle.getMessage("FastClockWarn");
2885        } else {
2886            mess = Bundle.getMessage("FastClockWarn2");
2887        }
2888        int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
2889                mess, Bundle.getMessage("WarningTitle"),
2890                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2891                new Object[]{Bundle.getMessage("ButtonYesStart"), Bundle.getMessage("ButtonNo")},
2892                Bundle.getMessage("ButtonNo"));
2893        if (selectedValue == 0) {
2894            try {
2895                fastClockSensor.setState(Sensor.ACTIVE);
2896            } catch (jmri.JmriException reason) {
2897                log.error("Exception when setting fast clock sensor");
2898            }
2899        }
2900    }
2901
2902    // Jay Janzen
2903    // Protection changed to public to allow access via scripting
2904    public AutoTrainsFrame getAutoTrainsFrame() {
2905        return _autoTrainsFrame;
2906    }
2907
2908    /**
2909     * Table model for Active Trains Table in Dispatcher window
2910     */
2911    public class ActiveTrainsTableModel extends javax.swing.table.AbstractTableModel implements
2912            java.beans.PropertyChangeListener {
2913
2914        public static final int TRANSIT_COLUMN = 0;
2915        public static final int TRANSIT_COLUMN_U = 1;
2916        public static final int TRAIN_COLUMN = 2;
2917        public static final int TYPE_COLUMN = 3;
2918        public static final int STATUS_COLUMN = 4;
2919        public static final int MODE_COLUMN = 5;
2920        public static final int ALLOCATED_COLUMN = 6;
2921        public static final int ALLOCATED_COLUMN_U = 7;
2922        public static final int NEXTSECTION_COLUMN = 8;
2923        public static final int NEXTSECTION_COLUMN_U = 9;
2924        public static final int ALLOCATEBUTTON_COLUMN = 10;
2925        public static final int TERMINATEBUTTON_COLUMN = 11;
2926        public static final int RESTARTCHECKBOX_COLUMN = 12;
2927        public static final int ISAUTO_COLUMN = 13;
2928        public static final int CURRENTSIGNAL_COLUMN = 14;
2929        public static final int CURRENTSIGNAL_COLUMN_U = 15;
2930        public static final int DCC_ADDRESS = 16;
2931        public static final int MAX_COLUMN = 16;
2932        public ActiveTrainsTableModel() {
2933            super();
2934        }
2935
2936        @Override
2937        public void propertyChange(java.beans.PropertyChangeEvent e) {
2938            if (e.getPropertyName().equals("length")) {
2939                fireTableDataChanged();
2940            }
2941        }
2942
2943        @Override
2944        public Class<?> getColumnClass(int col) {
2945            switch (col) {
2946                case ALLOCATEBUTTON_COLUMN:
2947                case TERMINATEBUTTON_COLUMN:
2948                    return JButton.class;
2949                case RESTARTCHECKBOX_COLUMN:
2950                case ISAUTO_COLUMN:
2951                    return Boolean.class;
2952                default:
2953                    return String.class;
2954            }
2955        }
2956
2957        @Override
2958        public int getColumnCount() {
2959            return MAX_COLUMN + 1;
2960        }
2961
2962        @Override
2963        public int getRowCount() {
2964            return (activeTrainsList.size());
2965        }
2966
2967        @Override
2968        public boolean isCellEditable(int row, int col) {
2969            switch (col) {
2970                case ALLOCATEBUTTON_COLUMN:
2971                case TERMINATEBUTTON_COLUMN:
2972                case RESTARTCHECKBOX_COLUMN:
2973                    return (true);
2974                default:
2975                    return (false);
2976            }
2977        }
2978
2979        @Override
2980        public String getColumnName(int col) {
2981            switch (col) {
2982                case TRANSIT_COLUMN:
2983                    return Bundle.getMessage("TransitColumnSysTitle");
2984                case TRANSIT_COLUMN_U:
2985                    return Bundle.getMessage("TransitColumnTitle");
2986                case TRAIN_COLUMN:
2987                    return Bundle.getMessage("TrainColumnTitle");
2988                case TYPE_COLUMN:
2989                    return Bundle.getMessage("TrainTypeColumnTitle");
2990                case STATUS_COLUMN:
2991                    return Bundle.getMessage("TrainStatusColumnTitle");
2992                case MODE_COLUMN:
2993                    return Bundle.getMessage("TrainModeColumnTitle");
2994                case ALLOCATED_COLUMN:
2995                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
2996                case ALLOCATED_COLUMN_U:
2997                    return Bundle.getMessage("AllocatedSectionColumnTitle");
2998                case NEXTSECTION_COLUMN:
2999                    return Bundle.getMessage("NextSectionColumnSysTitle");
3000                case NEXTSECTION_COLUMN_U:
3001                    return Bundle.getMessage("NextSectionColumnTitle");
3002                case RESTARTCHECKBOX_COLUMN:
3003                    return(Bundle.getMessage("AutoRestartColumnTitle"));
3004                case ALLOCATEBUTTON_COLUMN:
3005                    return(Bundle.getMessage("AllocateButton"));
3006                case TERMINATEBUTTON_COLUMN:
3007                    return(Bundle.getMessage("TerminateTrain"));
3008                case ISAUTO_COLUMN:
3009                    return(Bundle.getMessage("AutoColumnTitle"));
3010                case CURRENTSIGNAL_COLUMN:
3011                    return(Bundle.getMessage("CurrentSignalSysColumnTitle"));
3012                case CURRENTSIGNAL_COLUMN_U:
3013                    return(Bundle.getMessage("CurrentSignalColumnTitle"));
3014                case DCC_ADDRESS:
3015                    return(Bundle.getMessage("DccColumnTitleColumnTitle"));
3016                default:
3017                    return "";
3018            }
3019        }
3020
3021        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
3022                                justification="better to keep cases in column order rather than to combine")
3023        public int getPreferredWidth(int col) {
3024            switch (col) {
3025                case TRANSIT_COLUMN:
3026                case TRANSIT_COLUMN_U:
3027                case TRAIN_COLUMN:
3028                    return new JTextField(17).getPreferredSize().width;
3029                case TYPE_COLUMN:
3030                    return new JTextField(16).getPreferredSize().width;
3031                case STATUS_COLUMN:
3032                    return new JTextField(8).getPreferredSize().width;
3033                case MODE_COLUMN:
3034                    return new JTextField(11).getPreferredSize().width;
3035                case ALLOCATED_COLUMN:
3036                case ALLOCATED_COLUMN_U:
3037                    return new JTextField(17).getPreferredSize().width;
3038                case NEXTSECTION_COLUMN:
3039                case NEXTSECTION_COLUMN_U:
3040                    return new JTextField(17).getPreferredSize().width;
3041                case ALLOCATEBUTTON_COLUMN:
3042                case TERMINATEBUTTON_COLUMN:
3043                case RESTARTCHECKBOX_COLUMN:
3044                case ISAUTO_COLUMN:
3045                case CURRENTSIGNAL_COLUMN:
3046                case CURRENTSIGNAL_COLUMN_U:
3047                case DCC_ADDRESS:
3048                    return new JTextField(5).getPreferredSize().width;
3049                default:
3050                    // fall through
3051                    break;
3052            }
3053            return new JTextField(5).getPreferredSize().width;
3054        }
3055
3056        @Override
3057        public Object getValueAt(int r, int c) {
3058            int rx = r;
3059            if (rx >= activeTrainsList.size()) {
3060                return null;
3061            }
3062            ActiveTrain at = activeTrainsList.get(rx);
3063            switch (c) {
3064                case TRANSIT_COLUMN:
3065                    return (at.getTransit().getSystemName());
3066                case TRANSIT_COLUMN_U:
3067                    if (at.getTransit() != null && at.getTransit().getUserName() != null) {
3068                        return (at.getTransit().getUserName());
3069                    } else {
3070                        return "";
3071                    }
3072                case TRAIN_COLUMN:
3073                    return (at.getTrainName());
3074                case TYPE_COLUMN:
3075                    return (at.getTrainTypeText());
3076                case STATUS_COLUMN:
3077                    return (at.getStatusText());
3078                case MODE_COLUMN:
3079                    return (at.getModeText());
3080                case ALLOCATED_COLUMN:
3081                    if (at.getLastAllocatedSection() != null) {
3082                        return (at.getLastAllocatedSection().getSystemName());
3083                    } else {
3084                        return "<none>";
3085                    }
3086                case ALLOCATED_COLUMN_U:
3087                    if (at.getLastAllocatedSection() != null && at.getLastAllocatedSection().getUserName() != null) {
3088                        return (at.getLastAllocatedSection().getUserName());
3089                    } else {
3090                        return "<none>";
3091                    }
3092                case NEXTSECTION_COLUMN:
3093                    if (at.getNextSectionToAllocate() != null) {
3094                        return (at.getNextSectionToAllocate().getSystemName());
3095                    } else {
3096                        return "<none>";
3097                    }
3098                case NEXTSECTION_COLUMN_U:
3099                    if (at.getNextSectionToAllocate() != null && at.getNextSectionToAllocate().getUserName() != null) {
3100                        return (at.getNextSectionToAllocate().getUserName());
3101                    } else {
3102                        return "<none>";
3103                    }
3104                case ALLOCATEBUTTON_COLUMN:
3105                    return Bundle.getMessage("AllocateButtonName");
3106                case TERMINATEBUTTON_COLUMN:
3107                    return Bundle.getMessage("TerminateTrain");
3108                case RESTARTCHECKBOX_COLUMN:
3109                    return at.getResetWhenDone();
3110                case ISAUTO_COLUMN:
3111                    return at.getAutoRun();
3112                case CURRENTSIGNAL_COLUMN:
3113                    if (at.getAutoRun()) {
3114                        return(at.getAutoActiveTrain().getCurrentSignal());
3115                    } else {
3116                        return("NA");
3117                    }
3118                case CURRENTSIGNAL_COLUMN_U:
3119                    if (at.getAutoRun()) {
3120                        return(at.getAutoActiveTrain().getCurrentSignalUserName());
3121                    } else {
3122                        return("NA");
3123                    }
3124                case DCC_ADDRESS:
3125                    if (at.getDccAddress() != null) {
3126                        return(at.getDccAddress());
3127                    } else {
3128                        return("NA");
3129                    }
3130                default:
3131                    return (" ");
3132            }
3133        }
3134
3135        @Override
3136        public void setValueAt(Object value, int row, int col) {
3137            if (col == ALLOCATEBUTTON_COLUMN) {
3138                // open an allocate window
3139                allocateNextRequested(row);
3140            }
3141            if (col == TERMINATEBUTTON_COLUMN) {
3142                if (activeTrainsList.get(row) != null) {
3143                    terminateActiveTrain(activeTrainsList.get(row),true,false);
3144                }
3145            }
3146            if (col == RESTARTCHECKBOX_COLUMN) {
3147                ActiveTrain at = null;
3148                at = activeTrainsList.get(row);
3149                if (activeTrainsList.get(row) != null) {
3150                    if (!at.getResetWhenDone()) {
3151                        at.setResetWhenDone(true);
3152                        return;
3153                    }
3154                    at.setResetWhenDone(false);
3155                    for (int j = restartingTrainsList.size(); j > 0; j--) {
3156                        if (restartingTrainsList.get(j - 1) == at) {
3157                            restartingTrainsList.remove(j - 1);
3158                            return;
3159                        }
3160                    }
3161                }
3162            }
3163        }
3164    }
3165
3166    /**
3167     * Table model for Allocation Request Table in Dispatcher window
3168     */
3169    public class AllocationRequestTableModel extends javax.swing.table.AbstractTableModel implements
3170            java.beans.PropertyChangeListener {
3171
3172        public static final int TRANSIT_COLUMN = 0;
3173        public static final int TRANSIT_COLUMN_U = 1;
3174        public static final int TRAIN_COLUMN = 2;
3175        public static final int PRIORITY_COLUMN = 3;
3176        public static final int TRAINTYPE_COLUMN = 4;
3177        public static final int SECTION_COLUMN = 5;
3178        public static final int SECTION_COLUMN_U = 6;
3179        public static final int STATUS_COLUMN = 7;
3180        public static final int OCCUPANCY_COLUMN = 8;
3181        public static final int SECTIONLENGTH_COLUMN = 9;
3182        public static final int ALLOCATEBUTTON_COLUMN = 10;
3183        public static final int CANCELBUTTON_COLUMN = 11;
3184        public static final int MAX_COLUMN = 11;
3185
3186        public AllocationRequestTableModel() {
3187            super();
3188        }
3189
3190        @Override
3191        public void propertyChange(java.beans.PropertyChangeEvent e) {
3192            if (e.getPropertyName().equals("length")) {
3193                fireTableDataChanged();
3194            }
3195        }
3196
3197        @Override
3198        public Class<?> getColumnClass(int c) {
3199            if (c == CANCELBUTTON_COLUMN) {
3200                return JButton.class;
3201            }
3202            if (c == ALLOCATEBUTTON_COLUMN) {
3203                return JButton.class;
3204            }
3205            //if (c == CANCELRESTART_COLUMN) {
3206            //    return JButton.class;
3207            //}
3208            return String.class;
3209        }
3210
3211        @Override
3212        public int getColumnCount() {
3213            return MAX_COLUMN + 1;
3214        }
3215
3216        @Override
3217        public int getRowCount() {
3218            return (allocationRequests.size());
3219        }
3220
3221        @Override
3222        public boolean isCellEditable(int r, int c) {
3223            if (c == CANCELBUTTON_COLUMN) {
3224                return (true);
3225            }
3226            if (c == ALLOCATEBUTTON_COLUMN) {
3227                return (true);
3228            }
3229            return (false);
3230        }
3231
3232        @Override
3233        public String getColumnName(int col) {
3234            switch (col) {
3235                case TRANSIT_COLUMN:
3236                    return Bundle.getMessage("TransitColumnSysTitle");
3237                case TRANSIT_COLUMN_U:
3238                    return Bundle.getMessage("TransitColumnTitle");
3239                case TRAIN_COLUMN:
3240                    return Bundle.getMessage("TrainColumnTitle");
3241                case PRIORITY_COLUMN:
3242                    return Bundle.getMessage("PriorityLabel");
3243                case TRAINTYPE_COLUMN:
3244                    return Bundle.getMessage("TrainTypeColumnTitle");
3245                case SECTION_COLUMN:
3246                    return Bundle.getMessage("SectionColumnSysTitle");
3247                case SECTION_COLUMN_U:
3248                    return Bundle.getMessage("SectionColumnTitle");
3249                case STATUS_COLUMN:
3250                    return Bundle.getMessage("StatusColumnTitle");
3251                case OCCUPANCY_COLUMN:
3252                    return Bundle.getMessage("OccupancyColumnTitle");
3253                case SECTIONLENGTH_COLUMN:
3254                    return Bundle.getMessage("SectionLengthColumnTitle");
3255                case ALLOCATEBUTTON_COLUMN:
3256                    return Bundle.getMessage("AllocateButton");
3257                case CANCELBUTTON_COLUMN:
3258                    return Bundle.getMessage("ButtonCancel");
3259                default:
3260                    return "";
3261            }
3262        }
3263
3264        public int getPreferredWidth(int col) {
3265            switch (col) {
3266                case TRANSIT_COLUMN:
3267                case TRANSIT_COLUMN_U:
3268                case TRAIN_COLUMN:
3269                    return new JTextField(17).getPreferredSize().width;
3270                case PRIORITY_COLUMN:
3271                    return new JTextField(8).getPreferredSize().width;
3272                case TRAINTYPE_COLUMN:
3273                    return new JTextField(15).getPreferredSize().width;
3274                case SECTION_COLUMN:
3275                    return new JTextField(25).getPreferredSize().width;
3276                case STATUS_COLUMN:
3277                    return new JTextField(15).getPreferredSize().width;
3278                case OCCUPANCY_COLUMN:
3279                    return new JTextField(10).getPreferredSize().width;
3280                case SECTIONLENGTH_COLUMN:
3281                    return new JTextField(8).getPreferredSize().width;
3282                case ALLOCATEBUTTON_COLUMN:
3283                    return new JTextField(12).getPreferredSize().width;
3284                case CANCELBUTTON_COLUMN:
3285                    return new JTextField(10).getPreferredSize().width;
3286                default:
3287                    // fall through
3288                    break;
3289            }
3290            return new JTextField(5).getPreferredSize().width;
3291        }
3292
3293        @Override
3294        public Object getValueAt(int r, int c) {
3295            int rx = r;
3296            if (rx >= allocationRequests.size()) {
3297                return null;
3298            }
3299            AllocationRequest ar = allocationRequests.get(rx);
3300            switch (c) {
3301                case TRANSIT_COLUMN:
3302                    return (ar.getActiveTrain().getTransit().getSystemName());
3303                case TRANSIT_COLUMN_U:
3304                    if (ar.getActiveTrain().getTransit() != null && ar.getActiveTrain().getTransit().getUserName() != null) {
3305                        return (ar.getActiveTrain().getTransit().getUserName());
3306                    } else {
3307                        return "";
3308                    }
3309                case TRAIN_COLUMN:
3310                    return (ar.getActiveTrain().getTrainName());
3311                case PRIORITY_COLUMN:
3312                    return ("   " + ar.getActiveTrain().getPriority());
3313                case TRAINTYPE_COLUMN:
3314                    return (ar.getActiveTrain().getTrainTypeText());
3315                case SECTION_COLUMN:
3316                    if (ar.getSection() != null) {
3317                        return (ar.getSection().getSystemName());
3318                    } else {
3319                        return "<none>";
3320                    }
3321                case SECTION_COLUMN_U:
3322                    if (ar.getSection() != null && ar.getSection().getUserName() != null) {
3323                        return (ar.getSection().getUserName());
3324                    } else {
3325                        return "<none>";
3326                    }
3327                case STATUS_COLUMN:
3328                    if (ar.getSection().getState() == Section.FREE) {
3329                        return Bundle.getMessage("FREE");
3330                    }
3331                    return Bundle.getMessage("ALLOCATED");
3332                case OCCUPANCY_COLUMN:
3333                    if (!_HasOccupancyDetection) {
3334                        return Bundle.getMessage("UNKNOWN");
3335                    }
3336                    if (ar.getSection().getOccupancy() == Section.OCCUPIED) {
3337                        return Bundle.getMessage("OCCUPIED");
3338                    }
3339                    return Bundle.getMessage("UNOCCUPIED");
3340                case SECTIONLENGTH_COLUMN:
3341                    return ("  " + ar.getSection().getLengthI(_UseScaleMeters, _LayoutScale));
3342                case ALLOCATEBUTTON_COLUMN:
3343                    return Bundle.getMessage("AllocateButton");
3344                case CANCELBUTTON_COLUMN:
3345                    return Bundle.getMessage("ButtonCancel");
3346                default:
3347                    return (" ");
3348            }
3349        }
3350
3351        @Override
3352        public void setValueAt(Object value, int row, int col) {
3353            if (col == ALLOCATEBUTTON_COLUMN) {
3354                // open an allocate window
3355                allocateRequested(row);
3356            }
3357            if (col == CANCELBUTTON_COLUMN) {
3358                // open an allocate window
3359                cancelAllocationRequest(row);
3360            }
3361        }
3362    }
3363
3364    /**
3365     * Table model for Allocated Section Table
3366     */
3367    public class AllocatedSectionTableModel extends javax.swing.table.AbstractTableModel implements
3368            java.beans.PropertyChangeListener {
3369
3370        public static final int TRANSIT_COLUMN = 0;
3371        public static final int TRANSIT_COLUMN_U = 1;
3372        public static final int TRAIN_COLUMN = 2;
3373        public static final int SECTION_COLUMN = 3;
3374        public static final int SECTION_COLUMN_U = 4;
3375        public static final int OCCUPANCY_COLUMN = 5;
3376        public static final int USESTATUS_COLUMN = 6;
3377        public static final int RELEASEBUTTON_COLUMN = 7;
3378        public static final int MAX_COLUMN = 7;
3379
3380        public AllocatedSectionTableModel() {
3381            super();
3382        }
3383
3384        @Override
3385        public void propertyChange(java.beans.PropertyChangeEvent e) {
3386            if (e.getPropertyName().equals("length")) {
3387                fireTableDataChanged();
3388            }
3389        }
3390
3391        @Override
3392        public Class<?> getColumnClass(int c) {
3393            if (c == RELEASEBUTTON_COLUMN) {
3394                return JButton.class;
3395            }
3396            return String.class;
3397        }
3398
3399        @Override
3400        public int getColumnCount() {
3401            return MAX_COLUMN + 1;
3402        }
3403
3404        @Override
3405        public int getRowCount() {
3406            return (allocatedSections.size());
3407        }
3408
3409        @Override
3410        public boolean isCellEditable(int r, int c) {
3411            if (c == RELEASEBUTTON_COLUMN) {
3412                return (true);
3413            }
3414            return (false);
3415        }
3416
3417        @Override
3418        public String getColumnName(int col) {
3419            switch (col) {
3420                case TRANSIT_COLUMN:
3421                    return Bundle.getMessage("TransitColumnSysTitle");
3422                case TRANSIT_COLUMN_U:
3423                    return Bundle.getMessage("TransitColumnTitle");
3424                case TRAIN_COLUMN:
3425                    return Bundle.getMessage("TrainColumnTitle");
3426                case SECTION_COLUMN:
3427                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
3428                case SECTION_COLUMN_U:
3429                    return Bundle.getMessage("AllocatedSectionColumnTitle");
3430                case OCCUPANCY_COLUMN:
3431                    return Bundle.getMessage("OccupancyColumnTitle");
3432                case USESTATUS_COLUMN:
3433                    return Bundle.getMessage("UseStatusColumnTitle");
3434                case RELEASEBUTTON_COLUMN:
3435                    return Bundle.getMessage("ReleaseButton");
3436                default:
3437                    return "";
3438            }
3439        }
3440
3441        public int getPreferredWidth(int col) {
3442            switch (col) {
3443                case TRANSIT_COLUMN:
3444                case TRANSIT_COLUMN_U:
3445                case TRAIN_COLUMN:
3446                    return new JTextField(17).getPreferredSize().width;
3447                case SECTION_COLUMN:
3448                case SECTION_COLUMN_U:
3449                    return new JTextField(25).getPreferredSize().width;
3450                case OCCUPANCY_COLUMN:
3451                    return new JTextField(10).getPreferredSize().width;
3452                case USESTATUS_COLUMN:
3453                    return new JTextField(15).getPreferredSize().width;
3454                case RELEASEBUTTON_COLUMN:
3455                    return new JTextField(12).getPreferredSize().width;
3456                default:
3457                    // fall through
3458                    break;
3459            }
3460            return new JTextField(5).getPreferredSize().width;
3461        }
3462
3463        @Override
3464        public Object getValueAt(int r, int c) {
3465            int rx = r;
3466            if (rx >= allocatedSections.size()) {
3467                return null;
3468            }
3469            AllocatedSection as = allocatedSections.get(rx);
3470            switch (c) {
3471                case TRANSIT_COLUMN:
3472                    return (as.getActiveTrain().getTransit().getSystemName());
3473                case TRANSIT_COLUMN_U:
3474                    if (as.getActiveTrain().getTransit() != null && as.getActiveTrain().getTransit().getUserName() != null) {
3475                        return (as.getActiveTrain().getTransit().getUserName());
3476                    } else {
3477                        return "";
3478                    }
3479                case TRAIN_COLUMN:
3480                    return (as.getActiveTrain().getTrainName());
3481                case SECTION_COLUMN:
3482                    if (as.getSection() != null) {
3483                        return (as.getSection().getSystemName());
3484                    } else {
3485                        return "<none>";
3486                    }
3487                case SECTION_COLUMN_U:
3488                    if (as.getSection() != null && as.getSection().getUserName() != null) {
3489                        return (as.getSection().getUserName());
3490                    } else {
3491                        return "<none>";
3492                    }
3493                case OCCUPANCY_COLUMN:
3494                    if (!_HasOccupancyDetection) {
3495                        return Bundle.getMessage("UNKNOWN");
3496                    }
3497                    if (as.getSection().getOccupancy() == Section.OCCUPIED) {
3498                        return Bundle.getMessage("OCCUPIED");
3499                    }
3500                    return Bundle.getMessage("UNOCCUPIED");
3501                case USESTATUS_COLUMN:
3502                    if (!as.getEntered()) {
3503                        return Bundle.getMessage("NotEntered");
3504                    }
3505                    if (as.getExited()) {
3506                        return Bundle.getMessage("Exited");
3507                    }
3508                    return Bundle.getMessage("Entered");
3509                case RELEASEBUTTON_COLUMN:
3510                    return Bundle.getMessage("ReleaseButton");
3511                default:
3512                    return (" ");
3513            }
3514        }
3515
3516        @Override
3517        public void setValueAt(Object value, int row, int col) {
3518            if (col == RELEASEBUTTON_COLUMN) {
3519                releaseAllocatedSectionFromTable(row);
3520            }
3521        }
3522    }
3523
3524    /*
3525     * Mouse popup stuff
3526     */
3527
3528    /**
3529     * Process the column header click
3530     * @param e     the evnt data
3531     * @param table the JTable
3532     */
3533    protected void showTableHeaderPopup(JmriMouseEvent e, JTable table) {
3534        JPopupMenu popupMenu = new JPopupMenu();
3535        XTableColumnModel tcm = (XTableColumnModel) table.getColumnModel();
3536        for (int i = 0; i < tcm.getColumnCount(false); i++) {
3537            TableColumn tc = tcm.getColumnByModelIndex(i);
3538            String columnName = table.getModel().getColumnName(i);
3539            if (columnName != null && !columnName.equals("")) {
3540                JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(table.getModel().getColumnName(i), tcm.isColumnVisible(tc));
3541                menuItem.addActionListener(new HeaderActionListener(tc, tcm));
3542                popupMenu.add(menuItem);
3543            }
3544
3545        }
3546        popupMenu.show(e.getComponent(), e.getX(), e.getY());
3547    }
3548
3549    /**
3550     * Adds the column header pop listener to a JTable using XTableColumnModel
3551     * @param table The JTable effected.
3552     */
3553    protected void addMouseListenerToHeader(JTable table) {
3554        JmriMouseListener mouseHeaderListener = new TableHeaderListener(table);
3555        table.getTableHeader().addMouseListener(JmriMouseListener.adapt(mouseHeaderListener));
3556    }
3557
3558    static protected class HeaderActionListener implements ActionListener {
3559
3560        TableColumn tc;
3561        XTableColumnModel tcm;
3562
3563        HeaderActionListener(TableColumn tc, XTableColumnModel tcm) {
3564            this.tc = tc;
3565            this.tcm = tcm;
3566        }
3567
3568        @Override
3569        public void actionPerformed(ActionEvent e) {
3570            JCheckBoxMenuItem check = (JCheckBoxMenuItem) e.getSource();
3571            //Do not allow the last column to be hidden
3572            if (!check.isSelected() && tcm.getColumnCount(true) == 1) {
3573                return;
3574            }
3575            tcm.setColumnVisible(tc, check.isSelected());
3576        }
3577    }
3578
3579    /**
3580     * Class to support Columnheader popup menu on XTableColum model.
3581     */
3582    class TableHeaderListener extends JmriMouseAdapter {
3583
3584        JTable table;
3585
3586        TableHeaderListener(JTable tbl) {
3587            super();
3588            table = tbl;
3589        }
3590
3591        /**
3592         * {@inheritDoc}
3593         */
3594        @Override
3595        public void mousePressed(JmriMouseEvent e) {
3596            if (e.isPopupTrigger()) {
3597                showTableHeaderPopup(e, table);
3598            }
3599        }
3600
3601        /**
3602         * {@inheritDoc}
3603         */
3604        @Override
3605        public void mouseReleased(JmriMouseEvent e) {
3606            if (e.isPopupTrigger()) {
3607                showTableHeaderPopup(e, table);
3608            }
3609        }
3610
3611        /**
3612         * {@inheritDoc}
3613         */
3614        @Override
3615        public void mouseClicked(JmriMouseEvent e) {
3616            if (e.isPopupTrigger()) {
3617                showTableHeaderPopup(e, table);
3618            }
3619        }
3620    }
3621
3622    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DispatcherFrame.class);
3623
3624}