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