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