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