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     * Check an active train has an occupied section
2121     * @param at  ActiveTRain object
2122     * @return true / false
2123     */
2124    protected boolean hasTrainAnOccupiedSection(ActiveTrain at) {
2125        for (AllocatedSection asItem : at.getAllocatedSectionList()) {
2126            if (asItem.getSection().getOccupancy() == Section.OCCUPIED) {
2127                return true;
2128            }
2129        }
2130        return false;
2131    }
2132
2133    /**
2134     *
2135     * @param s Section to check
2136     * @param sSeqNum Sequence number of section
2137     * @param nextSection section after
2138     * @param at the active train
2139     * @param prevSection the section before
2140     * @return null if error else a list of the turnouts and their expected states.
2141     */
2142    List<LayoutTrackExpectedState<LayoutTurnout>> checkTurnoutStates(Section s, int sSeqNum, Section nextSection, ActiveTrain at, Section prevSection) {
2143        List<LayoutTrackExpectedState<LayoutTurnout>> turnoutsOK;
2144        if (_AutoTurnouts || at.getAutoRun()) {
2145            // automatically set the turnouts for this section before allocation
2146            turnoutsOK = autoTurnouts.setTurnoutsInSection(s, sSeqNum, nextSection,
2147                    at, _TrustKnownTurnouts, prevSection);
2148        } else {
2149            // check that turnouts are correctly set before allowing allocation to proceed
2150            turnoutsOK = autoTurnouts.checkTurnoutsInSection(s, sSeqNum, nextSection,
2151                    at, prevSection);
2152        }
2153        if (turnoutsOK == null) {
2154            if (_AutoAllocate) {
2155                return turnoutsOK;
2156            } else {
2157                // give the manual dispatcher a chance to override turnouts not OK
2158                int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
2159                        Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"),
2160                        JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2161                        new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")},
2162                        Bundle.getMessage("ButtonNo"));
2163                if (selectedValue != 0 ) { // array position 0, override not pressed
2164                    return null;
2165                }
2166                // return empty list
2167                turnoutsOK = new ArrayList<>();
2168            }
2169        }
2170        return turnoutsOK;
2171    }
2172
2173    List<HeldMastDetails> heldMasts = new ArrayList<>();
2174
2175    static class HeldMastDetails {
2176
2177        SignalMast mast = null;
2178        ActiveTrain at = null;
2179
2180        HeldMastDetails(SignalMast sm, ActiveTrain a) {
2181            mast = sm;
2182            at = a;
2183        }
2184
2185        ActiveTrain getActiveTrain() {
2186            return at;
2187        }
2188
2189        SignalMast getMast() {
2190            return mast;
2191        }
2192    }
2193
2194    public boolean isMastHeldByDispatcher(SignalMast sm, ActiveTrain at) {
2195        for (HeldMastDetails hmd : heldMasts) {
2196            if (hmd.getMast() == sm && hmd.getActiveTrain() == at) {
2197                return true;
2198            }
2199        }
2200        return false;
2201    }
2202
2203    private void removeHeldMast(SignalMast sm, ActiveTrain at) {
2204        List<HeldMastDetails> toRemove = new ArrayList<>();
2205        for (HeldMastDetails hmd : heldMasts) {
2206            if (hmd.getActiveTrain() == at) {
2207                if (sm == null) {
2208                    toRemove.add(hmd);
2209                } else if (sm == hmd.getMast()) {
2210                    toRemove.add(hmd);
2211                }
2212            }
2213        }
2214        for (HeldMastDetails hmd : toRemove) {
2215            hmd.getMast().setHeld(false);
2216            heldMasts.remove(hmd);
2217        }
2218    }
2219
2220    /*
2221     * returns a list of level crossings (0 to n) in a section.
2222     */
2223    private List<LevelXing> containedLevelXing(Section s) {
2224        List<LevelXing> _levelXingList = new ArrayList<>();
2225        if (s == null) {
2226            log.error("null argument to 'containsLevelCrossing'");
2227            return _levelXingList;
2228        }
2229
2230        for (var panel : editorManager.getAll(LayoutEditor.class)) {
2231            for (Block blk: s.getBlockList()) {
2232                for (LevelXing temLevelXing: panel.getConnectivityUtil().getLevelCrossingsThisBlock(blk)) {
2233                    // it is returned if the block is in the crossing or connected to the crossing
2234                    // we only need it if it is in the crossing
2235                    if (temLevelXing.getLayoutBlockAC().getBlock() == blk || temLevelXing.getLayoutBlockBD().getBlock() == blk ) {
2236                        _levelXingList.add(temLevelXing);
2237                    }
2238                }
2239            }
2240        }
2241        return _levelXingList;
2242    }
2243
2244    /**
2245     * Checks for a block in allocated section, except one
2246     * @param b - The Block
2247     * @param ignoreSection - ignore this section, can be null
2248     * @return true is The Block is being used in a section.
2249     */
2250    protected boolean checkForBlockInAllocatedSection ( Block b, Section ignoreSection ) {
2251        for ( AllocatedSection as : allocatedSections) {
2252            if (ignoreSection == null || as.getSection() != ignoreSection) {
2253                if (as.getSection().getBlockList().contains(b)) {
2254                    return true;
2255                }
2256            }
2257        }
2258        return false;
2259    }
2260
2261    /*
2262     * 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.
2263     */
2264    protected Section checkBlocksNotInAllocatedSection(Section s, AllocationRequest ar) {
2265        for (AllocatedSection as : allocatedSections) {
2266            if (as.getSection() != s) {
2267                List<Block> blas = as.getSection().getBlockList();
2268                //
2269                // When allocating the initial section for an Active Train,
2270                // we need not be concerned with any blocks in the initial section
2271                // which are unoccupied and to the rear of any occupied blocks in
2272                // the section as the train is not expected to enter those blocks.
2273                // When sections include the OS section these blocks prevented
2274                // allocation.
2275                //
2276                // The procedure is to remove those blocks (for the moment) from
2277                // the blocklist for the section during the initial allocation.
2278                //
2279
2280                List<Block> bls = new ArrayList<>();
2281                if (ar != null && ar.getActiveTrain().getAllocatedSectionList().size() == 0) {
2282                    int j;
2283                    if (ar.getSectionDirection() == Section.FORWARD) {
2284                        j = 0;
2285                        for (int i = 0; i < s.getBlockList().size(); i++) {
2286                            if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) {
2287                                j = 1;
2288                            }
2289                            if (j == 1) {
2290                                bls.add(s.getBlockList().get(i));
2291                            }
2292                        }
2293                    } else {
2294                        j = 0;
2295                        for (int i = s.getBlockList().size() - 1; i >= 0; i--) {
2296                            if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) {
2297                                j = 1;
2298                            }
2299                            if (j == 1) {
2300                                bls.add(s.getBlockList().get(i));
2301                            }
2302                        }
2303                    }
2304                } else {
2305                    bls = s.getBlockList();
2306                    // Add Blocks in any XCrossing, dont add ones already in the list
2307                    for ( LevelXing lx: containedLevelXing(s)) {
2308                        Block bAC = lx.getLayoutBlockAC().getBlock();
2309                        Block bBD = lx.getLayoutBlockBD().getBlock();
2310                        if (!bls.contains(bAC)) {
2311                            bls.add(bAC);
2312                        }
2313                        if (!bls.contains(bBD)) {
2314                            bls.add(bBD);
2315                        }
2316                    }
2317                }
2318
2319                for (Block b : bls) {
2320                    if (blas.contains(b)) {
2321                        if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) {
2322                            // no clue where the tail is some must assume this block still in use.
2323                            return as.getSection();
2324                        }
2325                        if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADANDTAIL) {
2326                            // if this is in the oldest section then we treat as whole train..
2327                            // if there is a section that exited but occupied the tail is there
2328                            for (AllocatedSection tas : allocatedSections) {
2329                                if (tas.getActiveTrain() == as.getActiveTrain() && tas.getExited() && tas.getSection().getOccupancy() == Section.OCCUPIED ) {
2330                                    return as.getSection();
2331                                }
2332                            }
2333                        } else if (as.getActiveTrain().getTrainDetection() != TrainDetection.TRAINDETECTION_WHOLETRAIN) {
2334                            return as.getSection();
2335                        }
2336                        if (as.getSection().getOccupancy() == Block.OCCUPIED) {
2337                            //The next check looks to see if the block has already been passed or not and therefore ready for allocation.
2338                            if (as.getSection().getState() == Section.FORWARD) {
2339                                for (int i = 0; i < blas.size(); i++) {
2340                                    //The block we get to is occupied therefore the subsequent blocks have not been entered
2341                                    if (blas.get(i).getState() == Block.OCCUPIED) {
2342                                        if (ar != null) {
2343                                            ar.setWaitingOnBlock(b);
2344                                        }
2345                                        return as.getSection();
2346                                    } else if (blas.get(i) == b) {
2347                                        break;
2348                                    }
2349                                }
2350                            } else {
2351                                for (int i = blas.size() - 1; i >= 0; i--) {
2352                                    //The block we get to is occupied therefore the subsequent blocks have not been entered
2353                                    if (blas.get(i).getState() == Block.OCCUPIED) {
2354                                        if (ar != null) {
2355                                            ar.setWaitingOnBlock(b);
2356                                        }
2357                                        return as.getSection();
2358                                    } else if (blas.get(i) == b) {
2359                                        break;
2360                                    }
2361                                }
2362                            }
2363                        } else if (as.getSection().getOccupancy() != Section.FREE) {
2364                            if (ar != null) {
2365                                ar.setWaitingOnBlock(b);
2366                            }
2367                            return as.getSection();
2368                        }
2369                    }
2370                }
2371            }
2372        }
2373        return null;
2374    }
2375
2376    // automatically make a choice of next section
2377    private Section autoChoice(List<Section> sList, AllocationRequest ar, int sectionSeqNo) {
2378        Section tSection = autoAllocate.autoNextSectionChoice(sList, ar, sectionSeqNo);
2379        if (tSection != null) {
2380            return tSection;
2381        }
2382        // if automatic choice failed, ask the dispatcher
2383        return dispatcherChoice(sList, ar);
2384    }
2385
2386    // manually make a choice of next section
2387    private Section dispatcherChoice(List<Section> sList, AllocationRequest ar) {
2388        Object choices[] = new Object[sList.size()];
2389        for (int i = 0; i < sList.size(); i++) {
2390            Section s = sList.get(i);
2391            String txt = s.getDisplayName();
2392            choices[i] = txt;
2393        }
2394        Object secName = JmriJOptionPane.showInputDialog(dispatcherFrame,
2395                Bundle.getMessage("ExplainChoice", ar.getSectionName()),
2396                Bundle.getMessage("ChoiceFrameTitle"), JmriJOptionPane
2397                        .QUESTION_MESSAGE, null, choices, choices[0]);
2398        if (secName == null) {
2399            JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("WarnCancel"));
2400            return sList.get(0);
2401        }
2402        for (int j = 0; j < sList.size(); j++) {
2403            if (secName.equals(choices[j])) {
2404                return sList.get(j);
2405            }
2406        }
2407        return sList.get(0);
2408    }
2409
2410    // submit an AllocationRequest for the next Section of an ActiveTrain
2411    private void requestNextAllocation(ActiveTrain at) {
2412        // set up an Allocation Request
2413        Section next = at.getNextSectionToAllocate();
2414        if (next == null) {
2415            return;
2416        }
2417        int seqNext = at.getNextSectionSeqNumber();
2418        int dirNext = at.getAllocationDirectionFromSectionAndSeq(next, seqNext);
2419        requestAllocation(at, next, dirNext, seqNext, true, dispatcherFrame);
2420    }
2421
2422    /**
2423     * Check if any allocation requests need to be allocated, or if any
2424     * allocated sections need to be released
2425     */
2426    protected void checkAutoRelease() {
2427        if (_AutoRelease) {
2428            // Auto release of exited sections has been requested - because of possible noise in block detection
2429            //    hardware, allocated sections are automatically released in the order they were allocated only
2430            // Only unoccupied sections that have been exited are tested.
2431            // The next allocated section must be assigned to the same train, and it must have been entered for
2432            //    the exited Section to be released.
2433            // Extra allocated sections are not automatically released (allocation number = -1).
2434            boolean foundOne = true;
2435            while ((allocatedSections.size() > 0) && foundOne) {
2436                try {
2437                    foundOne = false;
2438                    AllocatedSection as = null;
2439                    for (int i = 0; (i < allocatedSections.size()) && !foundOne; i++) {
2440                        as = allocatedSections.get(i);
2441                        if (as.getExited() && (as.getSection().getOccupancy() != Section.OCCUPIED)
2442                                && (as.getAllocationNumber() != -1)) {
2443                            // possible candidate for deallocation - check order
2444                            foundOne = true;
2445                            for (int j = 0; (j < allocatedSections.size()) && foundOne; j++) {
2446                                if (j != i) {
2447                                    AllocatedSection asx = allocatedSections.get(j);
2448                                    if ((asx.getActiveTrain() == as.getActiveTrain())
2449                                            && (asx.getAllocationNumber() != -1)
2450                                            && (asx.getAllocationNumber() < as.getAllocationNumber())) {
2451                                        foundOne = false;
2452                                    }
2453                                }
2454                            }
2455
2456                            // The train must have one occupied section.
2457                            // The train may be sitting in one of its allocated section undetected.
2458                            if ( foundOne && !hasTrainAnOccupiedSection(as.getActiveTrain())) {
2459                                log.warn("[{}]:CheckAutoRelease release section [{}] failed, train has no occupied section",
2460                                        as.getActiveTrain().getActiveTrainName(),as.getSectionName());
2461                                foundOne = false;
2462                            }
2463
2464                            if (foundOne) {
2465                                // check its not the last allocated section
2466                                int allocatedCount = 0;
2467                                for (int j = 0; (j < allocatedSections.size()); j++) {
2468                                    AllocatedSection asx = allocatedSections.get(j);
2469                                    if (asx.getActiveTrain() == as.getActiveTrain()) {
2470                                            allocatedCount++ ;
2471                                    }
2472                                }
2473                                if (allocatedCount == 1) {
2474                                    foundOne = false;
2475                                }
2476                            }
2477                            if (foundOne) {
2478                                // check if the next section is allocated to the same train and has been entered
2479                                ActiveTrain at = as.getActiveTrain();
2480                                Section ns = as.getNextSection();
2481                                AllocatedSection nas = null;
2482                                for (int k = 0; (k < allocatedSections.size()) && (nas == null); k++) {
2483                                    if (allocatedSections.get(k).getSection() == ns) {
2484                                        nas = allocatedSections.get(k);
2485                                    }
2486                                }
2487                                if ((nas == null) || (at.getStatus() == ActiveTrain.WORKING)
2488                                        || (at.getStatus() == ActiveTrain.STOPPED)
2489                                        || (at.getStatus() == ActiveTrain.READY)
2490                                        || (at.getMode() == ActiveTrain.MANUAL)) {
2491                                    // do not autorelease allocated sections from an Active Train that is
2492                                    //    STOPPED, READY, or WORKING, or is in MANUAL mode.
2493                                    foundOne = false;
2494                                    //But do so if the active train has reached its restart point
2495                                    if (nas != null && at.reachedRestartPoint()) {
2496                                        foundOne = true;
2497                                    }
2498                                } else {
2499                                    if ((nas.getActiveTrain() != as.getActiveTrain()) || (!nas.getEntered())) {
2500                                        foundOne = false;
2501                                    }
2502                                }
2503                                foundOne = sectionNotRequiredByHeadOnly(foundOne,at,as);
2504                                if (foundOne) {
2505                                    log.debug("{}: releasing section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS));
2506                                    doReleaseAllocatedSection(as, false);
2507                                }
2508                            }
2509                        }
2510                    }
2511                } catch (RuntimeException e) {
2512                    log.warn("checkAutoRelease failed  - maybe the AllocatedSection was removed due to a terminating train? {}", e.toString());
2513                    continue;
2514                }
2515            }
2516        }
2517        if (_AutoAllocate) {
2518            queueScanOfAllocationRequests();
2519        }
2520    }
2521
2522    /*
2523     * Check whether the section is in use by a "Head Only" train and can be released.
2524     * calculate the length of exited sections, subtract the length of section
2525     * being released. If the train is moving do not include the length of the occupied section,
2526     * if the train is stationary and was stopped by sensor or speed profile include the length
2527     * of the occupied section. This is done as we dont know where the train is in the section block.
2528     */
2529    private boolean sectionNotRequiredByHeadOnly(boolean foundOne, ActiveTrain at, AllocatedSection as) {
2530        if (at.getAutoActiveTrain() != null && at.getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) {
2531            long allocatedLengthMM = 0;
2532            for (AllocatedSection tas : at.getAllocatedSectionList()) {
2533                if (tas.getSection().getOccupancy() == Section.OCCUPIED) {
2534                    if (at.getAutoActiveTrain().getAutoEngineer().isStopped() &&
2535                            (at.getAutoActiveTrain().getStopBySpeedProfile() ||
2536                                    tas.getSection().getForwardStoppingSensor() != null ||
2537                                    tas.getSection().getReverseStoppingSensor() != null)) {
2538                        allocatedLengthMM += tas.getSection().getActualLength();
2539                        log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] including in length.",
2540                                at.getTrainName(),tas.getSection().getDisplayName());
2541                        break;
2542                    } else {
2543                        log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] excluding from length.",
2544                                at.getTrainName(),tas.getSection().getDisplayName());
2545                        break;
2546                    }
2547                }
2548                if (tas.getExited()) {
2549                    allocatedLengthMM += tas.getSection().getActualLength();
2550                }
2551            }
2552            long trainLengthMM = at.getAutoActiveTrain().getMaxTrainLengthMM();
2553            long releaseLengthMM = as.getSection().getActualLength();
2554            log.debug("[{}]:Release Section [{}] by Length allocated [{}] release [{}] train [{}]",
2555                    at.getTrainName(), as.getSectionName(), allocatedLengthMM, releaseLengthMM, trainLengthMM);
2556            if ((allocatedLengthMM - releaseLengthMM) < trainLengthMM) {
2557                return (false);
2558            }
2559        }
2560        return (true);
2561    }
2562
2563    /**
2564     * Releases an allocated Section, and removes it from the Dispatcher Input.
2565     *
2566     * @param as               the section to release
2567     * @param terminatingTrain true if the associated train is being terminated;
2568     *                         false otherwise
2569     */
2570    public void releaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2571        // Unless the train is termination it must have one occupied section.
2572        // The train may be sitting in an allocated section undetected.
2573        if ( !terminatingTrain && !hasTrainAnOccupiedSection(as.getActiveTrain())) {
2574                log.warn("[{}]: releaseAllocatedSection release section [{}] failed train has no occupied section",as.getActiveTrain().getActiveTrainName(),as.getSectionName());
2575            return;
2576        }
2577        if (_AutoAllocate ) {
2578            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_ONE,as,terminatingTrain));
2579        } else {
2580            doReleaseAllocatedSection( as,  terminatingTrain);
2581        }
2582    }
2583    protected void doReleaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2584        // check that section is not occupied if not terminating train
2585        if (!terminatingTrain && (as.getSection().getOccupancy() == Section.OCCUPIED)) {
2586            // warn the manual dispatcher that Allocated Section is occupied
2587            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, java.text.MessageFormat.format(
2588                    Bundle.getMessage("Question5"), new Object[]{as.getSectionName()}), Bundle.getMessage("WarningTitle"),
2589                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2590                    new Object[]{Bundle.getMessage("ButtonRelease"), Bundle.getMessage("ButtonNo")},
2591                    Bundle.getMessage("ButtonNo"));
2592            if (selectedValue != 0 ) { // array position 0, release not pressed
2593                return;   // return without releasing if "No" or "Cancel" response
2594            }
2595        }
2596        // release the Allocated Section
2597        for (int i = allocatedSections.size(); i > 0; i--) {
2598            if (as == allocatedSections.get(i - 1)) {
2599                allocatedSections.remove(i - 1);
2600            }
2601        }
2602        as.getSection().setState(Section.FREE);
2603        as.getActiveTrain().removeAllocatedSection(as);
2604        as.dispose();
2605        if (allocatedSectionTableModel != null) {
2606            allocatedSectionTableModel.fireTableDataChanged();
2607        }
2608        allocationRequestTableModel.fireTableDataChanged();
2609        activeTrainsTableModel.fireTableDataChanged();
2610        if (_AutoAllocate) {
2611            queueScanOfAllocationRequests();
2612        }
2613    }
2614
2615    /**
2616     * Updates display when occupancy of an allocated section changes Also
2617     * drives auto release if it is selected
2618     */
2619    public void sectionOccupancyChanged() {
2620        queueReleaseOfCompletedAllocations();
2621        if (allocatedSectionTableModel != null) {
2622            allocatedSectionTableModel.fireTableDataChanged();
2623        }
2624        allocationRequestTableModel.fireTableDataChanged();
2625    }
2626
2627    /**
2628     * Handle activity that is triggered by the fast clock
2629     */
2630    protected void newFastClockMinute() {
2631        for (int i = delayedTrains.size() - 1; i >= 0; i--) {
2632            ActiveTrain at = delayedTrains.get(i);
2633            // check if this Active Train is waiting to start
2634            if ((!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) {
2635                // is it time to start?
2636                if (at.getDelayedStart() == ActiveTrain.TIMEDDELAY) {
2637                    if (isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin())) {
2638                        // allow this train to start
2639                        at.setStarted();
2640                        delayedTrains.remove(i);
2641                    }
2642                }
2643            } else if (at.getStarted() && at.getStatus() == ActiveTrain.READY && at.reachedRestartPoint()) {
2644                if (isFastClockTimeGE(at.getRestartDepartHr(), at.getRestartDepartMin())) {
2645                    at.restart();
2646                    delayedTrains.remove(i);
2647                }
2648            }
2649        }
2650        if (_AutoAllocate) {
2651            queueScanOfAllocationRequests();
2652        }
2653    }
2654
2655    /**
2656     * This method tests time
2657     *
2658     * @param hr  the hour to test against (0-23)
2659     * @param min the minute to test against (0-59)
2660     * @return true if fast clock time and tested time are the same
2661     */
2662    public boolean isFastClockTimeGE(int hr, int min) {
2663        Calendar now = Calendar.getInstance();
2664        now.setTime(fastClock.getTime());
2665        int nowHours = now.get(Calendar.HOUR_OF_DAY);
2666        int nowMinutes = now.get(Calendar.MINUTE);
2667        return ((nowHours * 60) + nowMinutes) == ((hr * 60) + min);
2668    }
2669
2670    // option access methods
2671    protected LayoutEditor getLayoutEditor() {
2672        return _LE;
2673    }
2674
2675    protected void setLayoutEditor(LayoutEditor editor) {
2676        _LE = editor;
2677    }
2678
2679    protected boolean getUseConnectivity() {
2680        return _UseConnectivity;
2681    }
2682
2683    protected void setUseConnectivity(boolean set) {
2684        _UseConnectivity = set;
2685    }
2686
2687    protected void setSignalType(int type) {
2688        _SignalType = type;
2689    }
2690
2691    protected int getSignalType() {
2692        return _SignalType;
2693    }
2694
2695    protected String getSignalTypeString() {
2696        switch (_SignalType) {
2697            case SIGNALHEAD:
2698                return Bundle.getMessage("SignalType1");
2699            case SIGNALMAST:
2700                return Bundle.getMessage("SignalType2");
2701            case SECTIONSALLOCATED:
2702                return Bundle.getMessage("SignalType3");
2703            default:
2704                return "Unknown";
2705        }
2706    }
2707
2708    protected void setStoppingSpeedName(String speedName) {
2709        _StoppingSpeedName = speedName;
2710    }
2711
2712    protected String getStoppingSpeedName() {
2713        return _StoppingSpeedName;
2714    }
2715
2716    protected float getMaximumLineSpeed() {
2717        return maximumLineSpeed;
2718    }
2719
2720    protected void setTrainsFrom(TrainsFrom value ) {
2721        _TrainsFrom = value;
2722    }
2723
2724    protected TrainsFrom getTrainsFrom() {
2725        return _TrainsFrom;
2726    }
2727
2728    protected boolean getAutoAllocate() {
2729        return _AutoAllocate;
2730    }
2731
2732    protected boolean getAutoRelease() {
2733        return _AutoRelease;
2734    }
2735
2736    protected void stopStartAutoAllocateRelease() {
2737        if (_AutoAllocate || _AutoRelease) {
2738            if (editorManager.getAll(LayoutEditor.class).size() > 0) {
2739                if (autoAllocate == null) {
2740                    autoAllocate = new AutoAllocate(this,allocationRequests);
2741                    autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator ");
2742                    autoAllocateThread.start();
2743                }
2744            } else {
2745                JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Error39"),
2746                        Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
2747                _AutoAllocate = false;
2748                if (autoAllocateBox != null) {
2749                    autoAllocateBox.setSelected(_AutoAllocate);
2750                }
2751                return;
2752            }
2753        } else {
2754            //no need for autoallocateRelease
2755            if (autoAllocate != null) {
2756                autoAllocate.setAbort();
2757                autoAllocate = null;
2758            }
2759        }
2760
2761    }
2762    protected void setAutoAllocate(boolean set) {
2763        _AutoAllocate = set;
2764        stopStartAutoAllocateRelease();
2765        if (autoAllocateBox != null) {
2766            autoAllocateBox.setSelected(_AutoAllocate);
2767        }
2768    }
2769
2770    protected void setAutoRelease(boolean set) {
2771        _AutoRelease = set;
2772        stopStartAutoAllocateRelease();
2773        if (autoReleaseBox != null) {
2774            autoReleaseBox.setSelected(_AutoAllocate);
2775        }
2776    }
2777
2778    protected AutoTurnouts getAutoTurnoutsHelper () {
2779        return autoTurnouts;
2780    }
2781
2782    protected boolean getAutoTurnouts() {
2783        return _AutoTurnouts;
2784    }
2785
2786    protected void setAutoTurnouts(boolean set) {
2787        _AutoTurnouts = set;
2788    }
2789
2790    protected boolean getTrustKnownTurnouts() {
2791        return _TrustKnownTurnouts;
2792    }
2793
2794    protected void setTrustKnownTurnouts(boolean set) {
2795        _TrustKnownTurnouts = set;
2796    }
2797
2798    protected int getMinThrottleInterval() {
2799        return _MinThrottleInterval;
2800    }
2801
2802    protected void setMinThrottleInterval(int set) {
2803        _MinThrottleInterval = set;
2804    }
2805
2806    protected int getFullRampTime() {
2807        return _FullRampTime;
2808    }
2809
2810    protected void setFullRampTime(int set) {
2811        _FullRampTime = set;
2812    }
2813
2814    protected boolean getHasOccupancyDetection() {
2815        return _HasOccupancyDetection;
2816    }
2817
2818    protected void setHasOccupancyDetection(boolean set) {
2819        _HasOccupancyDetection = set;
2820    }
2821
2822    protected boolean getSetSSLDirectionalSensors() {
2823        return _SetSSLDirectionalSensors;
2824    }
2825
2826    protected void setSetSSLDirectionalSensors(boolean set) {
2827        _SetSSLDirectionalSensors = set;
2828    }
2829
2830    protected boolean getUseScaleMeters() {
2831        return _UseScaleMeters;
2832    }
2833
2834    protected void setUseScaleMeters(boolean set) {
2835        _UseScaleMeters = set;
2836    }
2837
2838    protected boolean getShortActiveTrainNames() {
2839        return _ShortActiveTrainNames;
2840    }
2841
2842    protected void setShortActiveTrainNames(boolean set) {
2843        _ShortActiveTrainNames = set;
2844        if (allocatedSectionTableModel != null) {
2845            allocatedSectionTableModel.fireTableDataChanged();
2846        }
2847        if (allocationRequestTableModel != null) {
2848            allocationRequestTableModel.fireTableDataChanged();
2849        }
2850    }
2851
2852    protected boolean getShortNameInBlock() {
2853        return _ShortNameInBlock;
2854    }
2855
2856    protected void setShortNameInBlock(boolean set) {
2857        _ShortNameInBlock = set;
2858    }
2859
2860    protected boolean getRosterEntryInBlock() {
2861        return _RosterEntryInBlock;
2862    }
2863
2864    protected void setRosterEntryInBlock(boolean set) {
2865        _RosterEntryInBlock = set;
2866    }
2867
2868    protected boolean getExtraColorForAllocated() {
2869        return _ExtraColorForAllocated;
2870    }
2871
2872    protected void setExtraColorForAllocated(boolean set) {
2873        _ExtraColorForAllocated = set;
2874    }
2875
2876    protected boolean getNameInAllocatedBlock() {
2877        return _NameInAllocatedBlock;
2878    }
2879
2880    protected void setNameInAllocatedBlock(boolean set) {
2881        _NameInAllocatedBlock = set;
2882    }
2883
2884    protected Scale getScale() {
2885        return _LayoutScale;
2886    }
2887
2888    protected void setScale(Scale sc) {
2889        _LayoutScale = sc;
2890    }
2891
2892    public List<ActiveTrain> getActiveTrainsList() {
2893        return activeTrainsList;
2894    }
2895
2896    protected List<AllocatedSection> getAllocatedSectionsList() {
2897        return allocatedSections;
2898    }
2899
2900    public ActiveTrain getActiveTrainForRoster(RosterEntry re) {
2901        if ( _TrainsFrom != TrainsFrom.TRAINSFROMROSTER) {
2902            return null;
2903        }
2904        for (ActiveTrain at : activeTrainsList) {
2905            if (at.getRosterEntry().equals(re)) {
2906                return at;
2907            }
2908        }
2909        return null;
2910
2911    }
2912
2913    protected boolean getSupportVSDecoder() {
2914        return _SupportVSDecoder;
2915    }
2916
2917    protected void setSupportVSDecoder(boolean set) {
2918        _SupportVSDecoder = set;
2919    }
2920
2921    // called by ActivateTrainFrame after a new train is all set up
2922    //      Dispatcher side of activating a new train should be completed here
2923    // Jay Janzen protection changed to public for access via scripting
2924    public void newTrainDone(ActiveTrain at) {
2925        if (at != null) {
2926            // a new active train was created, check for delayed start
2927            if (at.getDelayedStart() != ActiveTrain.NODELAY && (!at.getStarted())) {
2928                delayedTrains.add(at);
2929                fastClockWarn(true);
2930            } // djd needs work here
2931            // check for delayed restart
2932            else if (at.getDelayedRestart() == ActiveTrain.TIMEDDELAY) {
2933                fastClockWarn(false);
2934            }
2935        }
2936        if (atFrame != null) {
2937            atFrame.setVisible(false);
2938            atFrame.dispose();
2939            atFrame = null;
2940        }
2941        newTrainActive = false;
2942    }
2943
2944    protected void removeDelayedTrain(ActiveTrain at) {
2945        delayedTrains.remove(at);
2946    }
2947
2948    private void fastClockWarn(boolean wMess) {
2949        if (fastClockSensor.getState() == Sensor.ACTIVE) {
2950            return;
2951        }
2952        // warn that the fast clock is not running
2953        String mess = "";
2954        if (wMess) {
2955            mess = Bundle.getMessage("FastClockWarn");
2956        } else {
2957            mess = Bundle.getMessage("FastClockWarn2");
2958        }
2959        int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
2960                mess, Bundle.getMessage("WarningTitle"),
2961                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2962                new Object[]{Bundle.getMessage("ButtonYesStart"), Bundle.getMessage("ButtonNo")},
2963                Bundle.getMessage("ButtonNo"));
2964        if (selectedValue == 0) {
2965            try {
2966                fastClockSensor.setState(Sensor.ACTIVE);
2967            } catch (jmri.JmriException reason) {
2968                log.error("Exception when setting fast clock sensor");
2969            }
2970        }
2971    }
2972
2973    // Jay Janzen
2974    // Protection changed to public to allow access via scripting
2975    public AutoTrainsFrame getAutoTrainsFrame() {
2976        return _autoTrainsFrame;
2977    }
2978
2979    /**
2980     * Table model for Active Trains Table in Dispatcher window
2981     */
2982    public class ActiveTrainsTableModel extends javax.swing.table.AbstractTableModel implements
2983            java.beans.PropertyChangeListener {
2984
2985        public static final int TRANSIT_COLUMN = 0;
2986        public static final int TRANSIT_COLUMN_U = 1;
2987        public static final int TRAIN_COLUMN = 2;
2988        public static final int TYPE_COLUMN = 3;
2989        public static final int STATUS_COLUMN = 4;
2990        public static final int MODE_COLUMN = 5;
2991        public static final int ALLOCATED_COLUMN = 6;
2992        public static final int ALLOCATED_COLUMN_U = 7;
2993        public static final int NEXTSECTION_COLUMN = 8;
2994        public static final int NEXTSECTION_COLUMN_U = 9;
2995        public static final int ALLOCATEBUTTON_COLUMN = 10;
2996        public static final int TERMINATEBUTTON_COLUMN = 11;
2997        public static final int RESTARTCHECKBOX_COLUMN = 12;
2998        public static final int ISAUTO_COLUMN = 13;
2999        public static final int CURRENTSIGNAL_COLUMN = 14;
3000        public static final int CURRENTSIGNAL_COLUMN_U = 15;
3001        public static final int DCC_ADDRESS = 16;
3002        public static final int MAX_COLUMN = 16;
3003        public ActiveTrainsTableModel() {
3004            super();
3005        }
3006
3007        @Override
3008        public void propertyChange(java.beans.PropertyChangeEvent e) {
3009            if (e.getPropertyName().equals("length")) {
3010                fireTableDataChanged();
3011            }
3012        }
3013
3014        @Override
3015        public Class<?> getColumnClass(int col) {
3016            switch (col) {
3017                case ALLOCATEBUTTON_COLUMN:
3018                case TERMINATEBUTTON_COLUMN:
3019                    return JButton.class;
3020                case RESTARTCHECKBOX_COLUMN:
3021                case ISAUTO_COLUMN:
3022                    return Boolean.class;
3023                default:
3024                    return String.class;
3025            }
3026        }
3027
3028        @Override
3029        public int getColumnCount() {
3030            return MAX_COLUMN + 1;
3031        }
3032
3033        @Override
3034        public int getRowCount() {
3035            return (activeTrainsList.size());
3036        }
3037
3038        @Override
3039        public boolean isCellEditable(int row, int col) {
3040            switch (col) {
3041                case ALLOCATEBUTTON_COLUMN:
3042                case TERMINATEBUTTON_COLUMN:
3043                case RESTARTCHECKBOX_COLUMN:
3044                    return (true);
3045                default:
3046                    return (false);
3047            }
3048        }
3049
3050        @Override
3051        public String getColumnName(int col) {
3052            switch (col) {
3053                case TRANSIT_COLUMN:
3054                    return Bundle.getMessage("TransitColumnSysTitle");
3055                case TRANSIT_COLUMN_U:
3056                    return Bundle.getMessage("TransitColumnTitle");
3057                case TRAIN_COLUMN:
3058                    return Bundle.getMessage("TrainColumnTitle");
3059                case TYPE_COLUMN:
3060                    return Bundle.getMessage("TrainTypeColumnTitle");
3061                case STATUS_COLUMN:
3062                    return Bundle.getMessage("TrainStatusColumnTitle");
3063                case MODE_COLUMN:
3064                    return Bundle.getMessage("TrainModeColumnTitle");
3065                case ALLOCATED_COLUMN:
3066                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
3067                case ALLOCATED_COLUMN_U:
3068                    return Bundle.getMessage("AllocatedSectionColumnTitle");
3069                case NEXTSECTION_COLUMN:
3070                    return Bundle.getMessage("NextSectionColumnSysTitle");
3071                case NEXTSECTION_COLUMN_U:
3072                    return Bundle.getMessage("NextSectionColumnTitle");
3073                case RESTARTCHECKBOX_COLUMN:
3074                    return(Bundle.getMessage("AutoRestartColumnTitle"));
3075                case ALLOCATEBUTTON_COLUMN:
3076                    return(Bundle.getMessage("AllocateButton"));
3077                case TERMINATEBUTTON_COLUMN:
3078                    return(Bundle.getMessage("TerminateTrain"));
3079                case ISAUTO_COLUMN:
3080                    return(Bundle.getMessage("AutoColumnTitle"));
3081                case CURRENTSIGNAL_COLUMN:
3082                    return(Bundle.getMessage("CurrentSignalSysColumnTitle"));
3083                case CURRENTSIGNAL_COLUMN_U:
3084                    return(Bundle.getMessage("CurrentSignalColumnTitle"));
3085                case DCC_ADDRESS:
3086                    return(Bundle.getMessage("DccColumnTitleColumnTitle"));
3087                default:
3088                    return "";
3089            }
3090        }
3091
3092        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
3093                                justification="better to keep cases in column order rather than to combine")
3094        public int getPreferredWidth(int col) {
3095            switch (col) {
3096                case TRANSIT_COLUMN:
3097                case TRANSIT_COLUMN_U:
3098                case TRAIN_COLUMN:
3099                    return new JTextField(17).getPreferredSize().width;
3100                case TYPE_COLUMN:
3101                    return new JTextField(16).getPreferredSize().width;
3102                case STATUS_COLUMN:
3103                    return new JTextField(8).getPreferredSize().width;
3104                case MODE_COLUMN:
3105                    return new JTextField(11).getPreferredSize().width;
3106                case ALLOCATED_COLUMN:
3107                case ALLOCATED_COLUMN_U:
3108                    return new JTextField(17).getPreferredSize().width;
3109                case NEXTSECTION_COLUMN:
3110                case NEXTSECTION_COLUMN_U:
3111                    return new JTextField(17).getPreferredSize().width;
3112                case ALLOCATEBUTTON_COLUMN:
3113                case TERMINATEBUTTON_COLUMN:
3114                case RESTARTCHECKBOX_COLUMN:
3115                case ISAUTO_COLUMN:
3116                case CURRENTSIGNAL_COLUMN:
3117                case CURRENTSIGNAL_COLUMN_U:
3118                case DCC_ADDRESS:
3119                    return new JTextField(5).getPreferredSize().width;
3120                default:
3121                    // fall through
3122                    break;
3123            }
3124            return new JTextField(5).getPreferredSize().width;
3125        }
3126
3127        @Override
3128        public Object getValueAt(int r, int c) {
3129            int rx = r;
3130            if (rx >= activeTrainsList.size()) {
3131                return null;
3132            }
3133            ActiveTrain at = activeTrainsList.get(rx);
3134            switch (c) {
3135                case TRANSIT_COLUMN:
3136                    return (at.getTransit().getSystemName());
3137                case TRANSIT_COLUMN_U:
3138                    if (at.getTransit() != null && at.getTransit().getUserName() != null) {
3139                        return (at.getTransit().getUserName());
3140                    } else {
3141                        return "";
3142                    }
3143                case TRAIN_COLUMN:
3144                    return (at.getTrainName());
3145                case TYPE_COLUMN:
3146                    return (at.getTrainTypeText());
3147                case STATUS_COLUMN:
3148                    return (at.getStatusText());
3149                case MODE_COLUMN:
3150                    return (at.getModeText());
3151                case ALLOCATED_COLUMN:
3152                    if (at.getLastAllocatedSection() != null) {
3153                        return (at.getLastAllocatedSection().getSystemName());
3154                    } else {
3155                        return "<none>";
3156                    }
3157                case ALLOCATED_COLUMN_U:
3158                    if (at.getLastAllocatedSection() != null && at.getLastAllocatedSection().getUserName() != null) {
3159                        return (at.getLastAllocatedSection().getUserName());
3160                    } else {
3161                        return "<none>";
3162                    }
3163                case NEXTSECTION_COLUMN:
3164                    if (at.getNextSectionToAllocate() != null) {
3165                        return (at.getNextSectionToAllocate().getSystemName());
3166                    } else {
3167                        return "<none>";
3168                    }
3169                case NEXTSECTION_COLUMN_U:
3170                    if (at.getNextSectionToAllocate() != null && at.getNextSectionToAllocate().getUserName() != null) {
3171                        return (at.getNextSectionToAllocate().getUserName());
3172                    } else {
3173                        return "<none>";
3174                    }
3175                case ALLOCATEBUTTON_COLUMN:
3176                    return Bundle.getMessage("AllocateButtonName");
3177                case TERMINATEBUTTON_COLUMN:
3178                    return Bundle.getMessage("TerminateTrain");
3179                case RESTARTCHECKBOX_COLUMN:
3180                    return at.getResetWhenDone();
3181                case ISAUTO_COLUMN:
3182                    return at.getAutoRun();
3183                case CURRENTSIGNAL_COLUMN:
3184                    if (at.getAutoRun()) {
3185                        return(at.getAutoActiveTrain().getCurrentSignal());
3186                    } else {
3187                        return("NA");
3188                    }
3189                case CURRENTSIGNAL_COLUMN_U:
3190                    if (at.getAutoRun()) {
3191                        return(at.getAutoActiveTrain().getCurrentSignalUserName());
3192                    } else {
3193                        return("NA");
3194                    }
3195                case DCC_ADDRESS:
3196                    if (at.getDccAddress() != null) {
3197                        return(at.getDccAddress());
3198                    } else {
3199                        return("NA");
3200                    }
3201                default:
3202                    return (" ");
3203            }
3204        }
3205
3206        @Override
3207        public void setValueAt(Object value, int row, int col) {
3208            if (col == ALLOCATEBUTTON_COLUMN) {
3209                // open an allocate window
3210                allocateNextRequested(row);
3211            }
3212            if (col == TERMINATEBUTTON_COLUMN) {
3213                if (activeTrainsList.get(row) != null) {
3214                    terminateActiveTrain(activeTrainsList.get(row),true,false);
3215                }
3216            }
3217            if (col == RESTARTCHECKBOX_COLUMN) {
3218                ActiveTrain at = null;
3219                at = activeTrainsList.get(row);
3220                if (activeTrainsList.get(row) != null) {
3221                    if (!at.getResetWhenDone()) {
3222                        at.setResetWhenDone(true);
3223                        return;
3224                    }
3225                    at.setResetWhenDone(false);
3226                    for (int j = restartingTrainsList.size(); j > 0; j--) {
3227                        if (restartingTrainsList.get(j - 1) == at) {
3228                            restartingTrainsList.remove(j - 1);
3229                            return;
3230                        }
3231                    }
3232                }
3233            }
3234        }
3235    }
3236
3237    /**
3238     * Table model for Allocation Request Table in Dispatcher window
3239     */
3240    public class AllocationRequestTableModel extends javax.swing.table.AbstractTableModel implements
3241            java.beans.PropertyChangeListener {
3242
3243        public static final int TRANSIT_COLUMN = 0;
3244        public static final int TRANSIT_COLUMN_U = 1;
3245        public static final int TRAIN_COLUMN = 2;
3246        public static final int PRIORITY_COLUMN = 3;
3247        public static final int TRAINTYPE_COLUMN = 4;
3248        public static final int SECTION_COLUMN = 5;
3249        public static final int SECTION_COLUMN_U = 6;
3250        public static final int STATUS_COLUMN = 7;
3251        public static final int OCCUPANCY_COLUMN = 8;
3252        public static final int SECTIONLENGTH_COLUMN = 9;
3253        public static final int ALLOCATEBUTTON_COLUMN = 10;
3254        public static final int CANCELBUTTON_COLUMN = 11;
3255        public static final int MAX_COLUMN = 11;
3256
3257        public AllocationRequestTableModel() {
3258            super();
3259        }
3260
3261        @Override
3262        public void propertyChange(java.beans.PropertyChangeEvent e) {
3263            if (e.getPropertyName().equals("length")) {
3264                fireTableDataChanged();
3265            }
3266        }
3267
3268        @Override
3269        public Class<?> getColumnClass(int c) {
3270            if (c == CANCELBUTTON_COLUMN) {
3271                return JButton.class;
3272            }
3273            if (c == ALLOCATEBUTTON_COLUMN) {
3274                return JButton.class;
3275            }
3276            //if (c == CANCELRESTART_COLUMN) {
3277            //    return JButton.class;
3278            //}
3279            return String.class;
3280        }
3281
3282        @Override
3283        public int getColumnCount() {
3284            return MAX_COLUMN + 1;
3285        }
3286
3287        @Override
3288        public int getRowCount() {
3289            return (allocationRequests.size());
3290        }
3291
3292        @Override
3293        public boolean isCellEditable(int r, int c) {
3294            if (c == CANCELBUTTON_COLUMN) {
3295                return (true);
3296            }
3297            if (c == ALLOCATEBUTTON_COLUMN) {
3298                return (true);
3299            }
3300            return (false);
3301        }
3302
3303        @Override
3304        public String getColumnName(int col) {
3305            switch (col) {
3306                case TRANSIT_COLUMN:
3307                    return Bundle.getMessage("TransitColumnSysTitle");
3308                case TRANSIT_COLUMN_U:
3309                    return Bundle.getMessage("TransitColumnTitle");
3310                case TRAIN_COLUMN:
3311                    return Bundle.getMessage("TrainColumnTitle");
3312                case PRIORITY_COLUMN:
3313                    return Bundle.getMessage("PriorityLabel");
3314                case TRAINTYPE_COLUMN:
3315                    return Bundle.getMessage("TrainTypeColumnTitle");
3316                case SECTION_COLUMN:
3317                    return Bundle.getMessage("SectionColumnSysTitle");
3318                case SECTION_COLUMN_U:
3319                    return Bundle.getMessage("SectionColumnTitle");
3320                case STATUS_COLUMN:
3321                    return Bundle.getMessage("StatusColumnTitle");
3322                case OCCUPANCY_COLUMN:
3323                    return Bundle.getMessage("OccupancyColumnTitle");
3324                case SECTIONLENGTH_COLUMN:
3325                    return Bundle.getMessage("SectionLengthColumnTitle");
3326                case ALLOCATEBUTTON_COLUMN:
3327                    return Bundle.getMessage("AllocateButton");
3328                case CANCELBUTTON_COLUMN:
3329                    return Bundle.getMessage("ButtonCancel");
3330                default:
3331                    return "";
3332            }
3333        }
3334
3335        public int getPreferredWidth(int col) {
3336            switch (col) {
3337                case TRANSIT_COLUMN:
3338                case TRANSIT_COLUMN_U:
3339                case TRAIN_COLUMN:
3340                    return new JTextField(17).getPreferredSize().width;
3341                case PRIORITY_COLUMN:
3342                    return new JTextField(8).getPreferredSize().width;
3343                case TRAINTYPE_COLUMN:
3344                    return new JTextField(15).getPreferredSize().width;
3345                case SECTION_COLUMN:
3346                    return new JTextField(25).getPreferredSize().width;
3347                case STATUS_COLUMN:
3348                    return new JTextField(15).getPreferredSize().width;
3349                case OCCUPANCY_COLUMN:
3350                    return new JTextField(10).getPreferredSize().width;
3351                case SECTIONLENGTH_COLUMN:
3352                    return new JTextField(8).getPreferredSize().width;
3353                case ALLOCATEBUTTON_COLUMN:
3354                    return new JTextField(12).getPreferredSize().width;
3355                case CANCELBUTTON_COLUMN:
3356                    return new JTextField(10).getPreferredSize().width;
3357                default:
3358                    // fall through
3359                    break;
3360            }
3361            return new JTextField(5).getPreferredSize().width;
3362        }
3363
3364        @Override
3365        public Object getValueAt(int r, int c) {
3366            int rx = r;
3367            if (rx >= allocationRequests.size()) {
3368                return null;
3369            }
3370            AllocationRequest ar = allocationRequests.get(rx);
3371            switch (c) {
3372                case TRANSIT_COLUMN:
3373                    return (ar.getActiveTrain().getTransit().getSystemName());
3374                case TRANSIT_COLUMN_U:
3375                    if (ar.getActiveTrain().getTransit() != null && ar.getActiveTrain().getTransit().getUserName() != null) {
3376                        return (ar.getActiveTrain().getTransit().getUserName());
3377                    } else {
3378                        return "";
3379                    }
3380                case TRAIN_COLUMN:
3381                    return (ar.getActiveTrain().getTrainName());
3382                case PRIORITY_COLUMN:
3383                    return ("   " + ar.getActiveTrain().getPriority());
3384                case TRAINTYPE_COLUMN:
3385                    return (ar.getActiveTrain().getTrainTypeText());
3386                case SECTION_COLUMN:
3387                    if (ar.getSection() != null) {
3388                        return (ar.getSection().getSystemName());
3389                    } else {
3390                        return "<none>";
3391                    }
3392                case SECTION_COLUMN_U:
3393                    if (ar.getSection() != null && ar.getSection().getUserName() != null) {
3394                        return (ar.getSection().getUserName());
3395                    } else {
3396                        return "<none>";
3397                    }
3398                case STATUS_COLUMN:
3399                    if (ar.getSection().getState() == Section.FREE) {
3400                        return Bundle.getMessage("FREE");
3401                    }
3402                    return Bundle.getMessage("ALLOCATED");
3403                case OCCUPANCY_COLUMN:
3404                    if (!_HasOccupancyDetection) {
3405                        return Bundle.getMessage("UNKNOWN");
3406                    }
3407                    if (ar.getSection().getOccupancy() == Section.OCCUPIED) {
3408                        return Bundle.getMessage("OCCUPIED");
3409                    }
3410                    return Bundle.getMessage("UNOCCUPIED");
3411                case SECTIONLENGTH_COLUMN:
3412                    return ("  " + ar.getSection().getLengthI(_UseScaleMeters, _LayoutScale));
3413                case ALLOCATEBUTTON_COLUMN:
3414                    return Bundle.getMessage("AllocateButton");
3415                case CANCELBUTTON_COLUMN:
3416                    return Bundle.getMessage("ButtonCancel");
3417                default:
3418                    return (" ");
3419            }
3420        }
3421
3422        @Override
3423        public void setValueAt(Object value, int row, int col) {
3424            if (col == ALLOCATEBUTTON_COLUMN) {
3425                // open an allocate window
3426                allocateRequested(row);
3427            }
3428            if (col == CANCELBUTTON_COLUMN) {
3429                // open an allocate window
3430                cancelAllocationRequest(row);
3431            }
3432        }
3433    }
3434
3435    /**
3436     * Table model for Allocated Section Table
3437     */
3438    public class AllocatedSectionTableModel extends javax.swing.table.AbstractTableModel implements
3439            java.beans.PropertyChangeListener {
3440
3441        public static final int TRANSIT_COLUMN = 0;
3442        public static final int TRANSIT_COLUMN_U = 1;
3443        public static final int TRAIN_COLUMN = 2;
3444        public static final int SECTION_COLUMN = 3;
3445        public static final int SECTION_COLUMN_U = 4;
3446        public static final int OCCUPANCY_COLUMN = 5;
3447        public static final int USESTATUS_COLUMN = 6;
3448        public static final int RELEASEBUTTON_COLUMN = 7;
3449        public static final int MAX_COLUMN = 7;
3450
3451        public AllocatedSectionTableModel() {
3452            super();
3453        }
3454
3455        @Override
3456        public void propertyChange(java.beans.PropertyChangeEvent e) {
3457            if (e.getPropertyName().equals("length")) {
3458                fireTableDataChanged();
3459            }
3460        }
3461
3462        @Override
3463        public Class<?> getColumnClass(int c) {
3464            if (c == RELEASEBUTTON_COLUMN) {
3465                return JButton.class;
3466            }
3467            return String.class;
3468        }
3469
3470        @Override
3471        public int getColumnCount() {
3472            return MAX_COLUMN + 1;
3473        }
3474
3475        @Override
3476        public int getRowCount() {
3477            return (allocatedSections.size());
3478        }
3479
3480        @Override
3481        public boolean isCellEditable(int r, int c) {
3482            if (c == RELEASEBUTTON_COLUMN) {
3483                return (true);
3484            }
3485            return (false);
3486        }
3487
3488        @Override
3489        public String getColumnName(int col) {
3490            switch (col) {
3491                case TRANSIT_COLUMN:
3492                    return Bundle.getMessage("TransitColumnSysTitle");
3493                case TRANSIT_COLUMN_U:
3494                    return Bundle.getMessage("TransitColumnTitle");
3495                case TRAIN_COLUMN:
3496                    return Bundle.getMessage("TrainColumnTitle");
3497                case SECTION_COLUMN:
3498                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
3499                case SECTION_COLUMN_U:
3500                    return Bundle.getMessage("AllocatedSectionColumnTitle");
3501                case OCCUPANCY_COLUMN:
3502                    return Bundle.getMessage("OccupancyColumnTitle");
3503                case USESTATUS_COLUMN:
3504                    return Bundle.getMessage("UseStatusColumnTitle");
3505                case RELEASEBUTTON_COLUMN:
3506                    return Bundle.getMessage("ReleaseButton");
3507                default:
3508                    return "";
3509            }
3510        }
3511
3512        public int getPreferredWidth(int col) {
3513            switch (col) {
3514                case TRANSIT_COLUMN:
3515                case TRANSIT_COLUMN_U:
3516                case TRAIN_COLUMN:
3517                    return new JTextField(17).getPreferredSize().width;
3518                case SECTION_COLUMN:
3519                case SECTION_COLUMN_U:
3520                    return new JTextField(25).getPreferredSize().width;
3521                case OCCUPANCY_COLUMN:
3522                    return new JTextField(10).getPreferredSize().width;
3523                case USESTATUS_COLUMN:
3524                    return new JTextField(15).getPreferredSize().width;
3525                case RELEASEBUTTON_COLUMN:
3526                    return new JTextField(12).getPreferredSize().width;
3527                default:
3528                    // fall through
3529                    break;
3530            }
3531            return new JTextField(5).getPreferredSize().width;
3532        }
3533
3534        @Override
3535        public Object getValueAt(int r, int c) {
3536            int rx = r;
3537            if (rx >= allocatedSections.size()) {
3538                return null;
3539            }
3540            AllocatedSection as = allocatedSections.get(rx);
3541            switch (c) {
3542                case TRANSIT_COLUMN:
3543                    return (as.getActiveTrain().getTransit().getSystemName());
3544                case TRANSIT_COLUMN_U:
3545                    if (as.getActiveTrain().getTransit() != null && as.getActiveTrain().getTransit().getUserName() != null) {
3546                        return (as.getActiveTrain().getTransit().getUserName());
3547                    } else {
3548                        return "";
3549                    }
3550                case TRAIN_COLUMN:
3551                    return (as.getActiveTrain().getTrainName());
3552                case SECTION_COLUMN:
3553                    if (as.getSection() != null) {
3554                        return (as.getSection().getSystemName());
3555                    } else {
3556                        return "<none>";
3557                    }
3558                case SECTION_COLUMN_U:
3559                    if (as.getSection() != null && as.getSection().getUserName() != null) {
3560                        return (as.getSection().getUserName());
3561                    } else {
3562                        return "<none>";
3563                    }
3564                case OCCUPANCY_COLUMN:
3565                    if (!_HasOccupancyDetection) {
3566                        return Bundle.getMessage("UNKNOWN");
3567                    }
3568                    if (as.getSection().getOccupancy() == Section.OCCUPIED) {
3569                        return Bundle.getMessage("OCCUPIED");
3570                    }
3571                    return Bundle.getMessage("UNOCCUPIED");
3572                case USESTATUS_COLUMN:
3573                    if (!as.getEntered()) {
3574                        return Bundle.getMessage("NotEntered");
3575                    }
3576                    if (as.getExited()) {
3577                        return Bundle.getMessage("Exited");
3578                    }
3579                    return Bundle.getMessage("Entered");
3580                case RELEASEBUTTON_COLUMN:
3581                    return Bundle.getMessage("ReleaseButton");
3582                default:
3583                    return (" ");
3584            }
3585        }
3586
3587        @Override
3588        public void setValueAt(Object value, int row, int col) {
3589            if (col == RELEASEBUTTON_COLUMN) {
3590                releaseAllocatedSectionFromTable(row);
3591            }
3592        }
3593    }
3594
3595    /*
3596     * Mouse popup stuff
3597     */
3598
3599    /**
3600     * Process the column header click
3601     * @param e     the evnt data
3602     * @param table the JTable
3603     */
3604    protected void showTableHeaderPopup(JmriMouseEvent e, JTable table) {
3605        JPopupMenu popupMenu = new JPopupMenu();
3606        XTableColumnModel tcm = (XTableColumnModel) table.getColumnModel();
3607        for (int i = 0; i < tcm.getColumnCount(false); i++) {
3608            TableColumn tc = tcm.getColumnByModelIndex(i);
3609            String columnName = table.getModel().getColumnName(i);
3610            if (columnName != null && !columnName.equals("")) {
3611                JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(table.getModel().getColumnName(i), tcm.isColumnVisible(tc));
3612                menuItem.addActionListener(new HeaderActionListener(tc, tcm));
3613                popupMenu.add(menuItem);
3614            }
3615
3616        }
3617        popupMenu.show(e.getComponent(), e.getX(), e.getY());
3618    }
3619
3620    /**
3621     * Adds the column header pop listener to a JTable using XTableColumnModel
3622     * @param table The JTable effected.
3623     */
3624    protected void addMouseListenerToHeader(JTable table) {
3625        JmriMouseListener mouseHeaderListener = new TableHeaderListener(table);
3626        table.getTableHeader().addMouseListener(JmriMouseListener.adapt(mouseHeaderListener));
3627    }
3628
3629    static protected class HeaderActionListener implements ActionListener {
3630
3631        TableColumn tc;
3632        XTableColumnModel tcm;
3633
3634        HeaderActionListener(TableColumn tc, XTableColumnModel tcm) {
3635            this.tc = tc;
3636            this.tcm = tcm;
3637        }
3638
3639        @Override
3640        public void actionPerformed(ActionEvent e) {
3641            JCheckBoxMenuItem check = (JCheckBoxMenuItem) e.getSource();
3642            //Do not allow the last column to be hidden
3643            if (!check.isSelected() && tcm.getColumnCount(true) == 1) {
3644                return;
3645            }
3646            tcm.setColumnVisible(tc, check.isSelected());
3647        }
3648    }
3649
3650    /**
3651     * Class to support Columnheader popup menu on XTableColum model.
3652     */
3653    class TableHeaderListener extends JmriMouseAdapter {
3654
3655        JTable table;
3656
3657        TableHeaderListener(JTable tbl) {
3658            super();
3659            table = tbl;
3660        }
3661
3662        /**
3663         * {@inheritDoc}
3664         */
3665        @Override
3666        public void mousePressed(JmriMouseEvent e) {
3667            if (e.isPopupTrigger()) {
3668                showTableHeaderPopup(e, table);
3669            }
3670        }
3671
3672        /**
3673         * {@inheritDoc}
3674         */
3675        @Override
3676        public void mouseReleased(JmriMouseEvent e) {
3677            if (e.isPopupTrigger()) {
3678                showTableHeaderPopup(e, table);
3679            }
3680        }
3681
3682        /**
3683         * {@inheritDoc}
3684         */
3685        @Override
3686        public void mouseClicked(JmriMouseEvent e) {
3687            if (e.isPopupTrigger()) {
3688                showTableHeaderPopup(e, table);
3689            }
3690        }
3691    }
3692
3693    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DispatcherFrame.class);
3694
3695}