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