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