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