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