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