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