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