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