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