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 */ 134 public void loadAtStartup() { 135 log.debug("Loading saved trains flagged as LoadAtStartup"); 136 TrainInfoFile tif = new TrainInfoFile(); 137 String[] names = tif.getTrainInfoFileNames(); 138 log.debug("initializing block paths early"); //TODO: figure out how to prevent the "regular" init 139 InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class) 140 .initializeLayoutBlockPaths(); 141 if (names.length > 0) { 142 for (int i = 0; i < names.length; i++) { 143 TrainInfo info; 144 try { 145 info = tif.readTrainInfo(names[i]); 146 } catch (java.io.IOException ioe) { 147 log.error("IO Exception when reading train info file {}", names[i], ioe); 148 continue; 149 } catch (org.jdom2.JDOMException jde) { 150 log.error("JDOM Exception when reading train info file {}", names[i], jde); 151 continue; 152 } 153 if (info != null && info.getLoadAtStartup()) { 154 if (loadTrainFromTrainInfo(info) != 0) { 155 /* 156 * Error loading occurred The error will have already 157 * been sent to the log and to screen 158 */ 159 } else { 160 /* give time to set up throttles etc */ 161 try { 162 Thread.sleep(500); 163 } catch (InterruptedException e) { 164 log.warn("Sleep Interrupted in loading trains, likely being stopped", e); 165 Thread.currentThread().interrupt(); 166 } 167 } 168 } 169 } 170 } 171 } 172 173 @Override 174 public void dispose( ) { 175 super.dispose(); 176 if (autoAllocate != null) { 177 autoAllocate.setAbort(); 178 } 179 } 180 181 /** 182 * Constants for the override type 183 */ 184 public static final String OVERRIDETYPE_NONE = "NONE"; 185 public static final String OVERRIDETYPE_USER = "USER"; 186 public static final String OVERRIDETYPE_DCCADDRESS = "DCCADDRESS"; 187 public static final String OVERRIDETYPE_OPERATIONS = "OPERATIONS"; 188 public static final String OVERRIDETYPE_ROSTER = "ROSTER"; 189 190 /** 191 * Loads a train into the Dispatcher from a traininfo file 192 * 193 * @param traininfoFileName the file name of a traininfo file. 194 * @return 0 good, -1 create failure, -2 -3 file errors, -9 bother. 195 */ 196 public int loadTrainFromTrainInfo(String traininfoFileName) { 197 return loadTrainFromTrainInfo(traininfoFileName, "NONE", ""); 198 } 199 200 /** 201 * Loads a train into the Dispatcher from a traininfo file, overriding 202 * dccaddress 203 * 204 * @param traininfoFileName the file name of a traininfo file. 205 * @param overRideType "NONE", "USER", "ROSTER" or "OPERATIONS" 206 * @param overRideValue "" , dccAddress, RosterEntryName or Operations 207 * trainname. 208 * @return 0 good, -1 create failure, -2 -3 file errors, -9 bother. 209 */ 210 public int loadTrainFromTrainInfo(String traininfoFileName, String overRideType, String overRideValue) { 211 //read xml data from selected filename and move it into trainfo 212 try { 213 // maybe called from jthon protect our selves 214 TrainInfoFile tif = new TrainInfoFile(); 215 TrainInfo info; 216 try { 217 info = tif.readTrainInfo(traininfoFileName); 218 } catch (java.io.FileNotFoundException fnfe) { 219 log.error("Train info file not found {}", traininfoFileName); 220 return -2; 221 } catch (java.io.IOException ioe) { 222 log.error("IO Exception when reading train info file {}", traininfoFileName, ioe); 223 return -2; 224 } catch (org.jdom2.JDOMException jde) { 225 log.error("JDOM Exception when reading train info file {}", traininfoFileName, jde); 226 return -3; 227 } 228 return loadTrainFromTrainInfo(info, overRideType, overRideValue); 229 } catch (RuntimeException ex) { 230 log.error("Unexpected, uncaught exception loading traininfofile [{}]", traininfoFileName, ex); 231 return -9; 232 } 233 } 234 235 /** 236 * Loads a train into the Dispatcher 237 * 238 * @param info a completed TrainInfo class. 239 * @return 0 good, -1 failure 240 */ 241 public int loadTrainFromTrainInfo(TrainInfo info) { 242 return loadTrainFromTrainInfo(info, "NONE", ""); 243 } 244 245 /** 246 * Loads a train into the Dispatcher 247 * returns an integer. Messages written to log. 248 * @param info a completed TrainInfo class. 249 * @param overRideType "NONE", "USER", "ROSTER" or "OPERATIONS" 250 * @param overRideValue "" , dccAddress, RosterEntryName or Operations 251 * trainName. 252 * @return 0 good, -1 failure 253 */ 254 public int loadTrainFromTrainInfo(TrainInfo info, String overRideType, String overRideValue) { 255 try { 256 loadTrainFromTrainInfoThrowsException( info, overRideType, overRideValue); 257 return 0; 258 } catch (IllegalArgumentException ex) { 259 return -1; 260 } 261 } 262 263 /** 264 * Loads a train into the Dispatcher 265 * throws IllegalArgumentException on errors 266 * @param info a completed TrainInfo class. 267 * @param overRideType "NONE", "USER", "ROSTER" or "OPERATIONS" 268 * @param overRideValue "" , dccAddress, RosterEntryName or Operations 269 * trainName. 270 * @throws IllegalArgumentException validation errors. 271 */ 272 public void loadTrainFromTrainInfoThrowsException(TrainInfo info, String overRideType, String overRideValue) 273 throws IllegalArgumentException { 274 275 log.debug("loading train:{}, startblockname:{}, destinationBlockName:{}", info.getTrainName(), 276 info.getStartBlockName(), info.getDestinationBlockName()); 277 // create a new Active Train 278 279 //set up defaults from traininfo 280 int tSource = 0; 281 if (info.getTrainFromRoster()) { 282 tSource = ActiveTrain.ROSTER; 283 } else if (info.getTrainFromTrains()) { 284 tSource = ActiveTrain.OPERATIONS; 285 } else if (info.getTrainFromUser()) { 286 tSource = ActiveTrain.USER; 287 } 288 String dccAddressToUse = info.getDccAddress(); 289 String trainNameToUse = info.getTrainUserName(); 290 String rosterIDToUse = info.getRosterId(); 291 //process override 292 switch (overRideType) { 293 case "": 294 case OVERRIDETYPE_NONE: 295 break; 296 case OVERRIDETYPE_USER: 297 case OVERRIDETYPE_DCCADDRESS: 298 tSource = ActiveTrain.USER; 299 dccAddressToUse = overRideValue; 300 if (trainNameToUse.isEmpty()) { 301 trainNameToUse = overRideValue; 302 } 303 break; 304 case OVERRIDETYPE_OPERATIONS: 305 tSource = ActiveTrain.OPERATIONS; 306 trainNameToUse = overRideValue; 307 break; 308 case OVERRIDETYPE_ROSTER: 309 tSource = ActiveTrain.ROSTER; 310 rosterIDToUse = overRideValue; 311 RosterEntry re = Roster.getDefault().getEntryForId(rosterIDToUse); 312 if (re != null) { 313 dccAddressToUse = re.getDccAddress(); 314 } 315 if (trainNameToUse.isEmpty()) { 316 trainNameToUse = overRideValue; 317 } 318 break; 319 default: 320 /* just leave as in traininfo */ 321 } 322 if (info.getDynamicTransit()) { 323 // attempt to build transit 324 Transit tmpTransit = createTemporaryTransit(InstanceManager.getDefault(jmri.BlockManager.class).getBlock(info.getStartBlockName()), 325 InstanceManager.getDefault(jmri.BlockManager.class).getBlock(info.getDestinationBlockName()), 326 InstanceManager.getDefault(jmri.BlockManager.class).getBlock(info.getViaBlockName())); 327 if (tmpTransit == null ) { 328 throw new IllegalArgumentException(Bundle.getMessage("Error51")); 329 } 330 info.setTransitName(tmpTransit.getDisplayName()); 331 info.setTransitId(tmpTransit.getDisplayName()); 332 info.setDestinationBlockSeq(tmpTransit.getMaxSequence()); 333 } 334 if (tSource == 0) { 335 log.warn("Invalid Trains From [{}]", 336 tSource); 337 throw new IllegalArgumentException(Bundle.getMessage("Error21")); 338 } 339 if (!isTrainFree(trainNameToUse)) { 340 log.warn("TrainName [{}] already in use", 341 trainNameToUse); 342 throw new IllegalArgumentException(Bundle.getMessage("Error24",trainNameToUse)); 343 } 344 ActiveTrain at = createActiveTrain(info.getTransitId(), trainNameToUse, tSource, 345 info.getStartBlockId(), info.getStartBlockSeq(), info.getDestinationBlockId(), 346 info.getDestinationBlockSeq(), 347 info.getAutoRun(), dccAddressToUse, info.getPriority(), 348 info.getResetWhenDone(), info.getReverseAtEnd(), true, null, info.getAllocationMethod()); 349 if (at != null) { 350 if (tSource == ActiveTrain.ROSTER) { 351 RosterEntry re = Roster.getDefault().getEntryForId(rosterIDToUse); 352 if (re != null) { 353 at.setRosterEntry(re); 354 at.setDccAddress(re.getDccAddress()); 355 } else { 356 log.warn("Roster Entry '{}' not found, could not create ActiveTrain '{}'", 357 trainNameToUse, info.getTrainName()); 358 throw new IllegalArgumentException(Bundle.getMessage("Error40",rosterIDToUse)); 359 } 360 } 361 at.setTrainDetection(info.getTrainDetection()); 362 at.setAllocateMethod(info.getAllocationMethod()); 363 at.setDelayedStart(info.getDelayedStart()); //this is a code: NODELAY, TIMEDDELAY, SENSORDELAY 364 at.setDepartureTimeHr(info.getDepartureTimeHr()); // hour of day (fast-clock) to start this train 365 at.setDepartureTimeMin(info.getDepartureTimeMin()); //minute of hour to start this train 366 at.setDelayedRestart(info.getDelayedRestart()); //this is a code: NODELAY, TIMEDDELAY, SENSORDELAY 367 at.setRestartDelay(info.getRestartDelayMin()); //this is number of minutes to delay between runs 368 at.setDelaySensor(info.getDelaySensor()); 369 at.setResetStartSensor(info.getResetStartSensor()); 370 if ((isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin()) && 371 info.getDelayedStart() != ActiveTrain.SENSORDELAY) || 372 info.getDelayedStart() == ActiveTrain.NODELAY) { 373 at.setStarted(); 374 } 375 at.setRestartSensor(info.getRestartSensor()); 376 at.setResetRestartSensor(info.getResetRestartSensor()); 377 at.setReverseDelayRestart(info.getReverseDelayedRestart()); 378 at.setReverseRestartDelay(info.getReverseRestartDelayMin()); 379 at.setReverseDelaySensor(info.getReverseRestartSensor()); 380 at.setReverseResetRestartSensor(info.getReverseResetRestartSensor()); 381 at.setTrainType(info.getTrainType()); 382 at.setTerminateWhenDone(info.getTerminateWhenDone()); 383 at.setNextTrain(info.getNextTrain()); 384 if (info.getAutoRun()) { 385 AutoActiveTrain aat = new AutoActiveTrain(at); 386 aat.setSpeedFactor(info.getSpeedFactor()); 387 aat.setMaxSpeed(info.getMaxSpeed()); 388 aat.setMinReliableOperatingSpeed(info.getMinReliableOperatingSpeed()); 389 aat.setRampRate(AutoActiveTrain.getRampRateFromName(info.getRampRate())); 390 aat.setRunInReverse(info.getRunInReverse()); 391 aat.setSoundDecoder(info.getSoundDecoder()); 392 aat.setMaxTrainLength(info.getMaxTrainLengthScaleMeters(),getScale().getScaleFactor()); 393 aat.setStopBySpeedProfile(info.getStopBySpeedProfile()); 394 aat.setStopBySpeedProfileAdjust(info.getStopBySpeedProfileAdjust()); 395 aat.setUseSpeedProfile(info.getUseSpeedProfile()); 396 getAutoTrainsFrame().addAutoActiveTrain(aat); 397 if (!aat.initialize()) { 398 log.error("ERROR initializing autorunning for train {}", at.getTrainName()); 399 throw new IllegalArgumentException(Bundle.getMessage("Error27",at.getTrainName())); 400 } 401 } 402 // we can go no further without attaching this. 403 at.setDispatcher(this); 404 allocateNewActiveTrain(at); 405 newTrainDone(at); 406 407 } else { 408 log.warn("failed to create Active Train '{}'", info.getTrainName()); 409 throw new IllegalArgumentException(Bundle.getMessage("Error48",info.getTrainName())); 410 } 411 } 412 413 /** 414 * Get a list of {@link jmri.jmrit.display.layoutEditor.LayoutBlock} that represent a route 415 * @param start First Block 416 * @param dest Last Block 417 * @param via Next Block 418 * @return null if a route cannot be found, else the list. 419 */ 420 protected List<LayoutBlock> getAdHocRoute(Block start, Block dest, Block via) { 421 LayoutBlockManager lBM = jmri.InstanceManager.getDefault(LayoutBlockManager.class); 422 LayoutBlock lbStart = lBM.getByUserName(start.getDisplayName(DisplayOptions.USERNAME)); 423 LayoutBlock lbEnd = lBM.getByUserName(dest.getDisplayName(DisplayOptions.USERNAME)); 424 LayoutBlock lbVia = lBM.getByUserName(via.getDisplayName(DisplayOptions.USERNAME)); 425 List<LayoutBlock> blocks = new ArrayList<LayoutBlock>(); 426 try { 427 boolean result = lBM.getLayoutBlockConnectivityTools().checkValidDest( 428 lbStart, lbVia, lbEnd, blocks, LayoutBlockConnectivityTools.Routing.NONE); 429 if (!result) { 430 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error51"), 431 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 432 } 433 blocks = lBM.getLayoutBlockConnectivityTools().getLayoutBlocks( 434 lbStart, lbEnd, lbVia, false, LayoutBlockConnectivityTools.Routing.NONE); 435 } catch (JmriException JEx) { 436 log.error("Finding route {}",JEx.getMessage()); 437 return null; 438 } 439 return blocks; 440 } 441 442 /** 443 * Converts a list of {@link jmri.jmrit.display.layoutEditor.LayoutBlock} that represent a route to a transit. 444 * @param start First Block 445 * @param dest Last Block 446 * @param via Next Block 447 * @return null if the transit is valid. Else an AdHoc transit 448 */ 449 protected Transit createTemporaryTransit(Block start, Block dest, Block via) { 450 List<LayoutBlock> blocks = getAdHocRoute( start, dest, via); 451 if (blocks == null) { 452 return null; 453 } 454 SectionManager sm = jmri.InstanceManager.getDefault(SectionManager.class); 455 Transit tempTransit = null; 456 int wNo = 0; 457 String baseTransitName = "-" + start.getDisplayName() + "-" + dest.getDisplayName(); 458 while (tempTransit == null && wNo < 99) { 459 wNo++; 460 try { 461 tempTransit = transitManager.createNewTransit("#" + Integer.toString(wNo) + baseTransitName); 462 } catch (Exception ex) { 463 log.trace("Transit [{}} already used, try next.", "#" + Integer.toString(wNo) + baseTransitName); 464 } 465 } 466 if (tempTransit == null) { 467 log.error("Limit of Dynamic Transits for [{}] has been exceeded!", baseTransitName); 468 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("DynamicTransitsExceeded",baseTransitName), 469 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 470 return null; 471 } 472 tempTransit.setTransitType(TransitType.DYNAMICADHOC); 473 int seq = 1; 474 TransitSection prevTs = null; 475 TransitSection curTs = null; 476 for (LayoutBlock lB : blocks) { 477 Block b = lB.getBlock(); 478 Section currentSection = sm.createNewSection(tempTransit.getUserName() + Integer.toString(seq) + "-" + b.getDisplayName()); 479 currentSection.setSectionType(Section.DYNAMICADHOC); 480 currentSection.addBlock(b); 481 if (curTs == null) { 482 //first block shove it in. 483 curTs = new TransitSection(currentSection, seq, Section.FORWARD); 484 } else { 485 prevTs = curTs; 486 EntryPoint fEp = new EntryPoint(prevTs.getSection().getBlockBySequenceNumber(0),b,"up"); 487 fEp.setTypeReverse(); 488 prevTs.getSection().addToReverseList(fEp); 489 EntryPoint rEp = new EntryPoint(b,prevTs.getSection().getBlockBySequenceNumber(0),"down"); 490 rEp.setTypeForward(); 491 currentSection.addToForwardList(rEp); 492 curTs = new TransitSection(currentSection, seq, Section.FORWARD); 493 } 494 curTs.setTemporary(true); 495 tempTransit.addTransitSection(curTs); 496 seq++; 497 } 498 return tempTransit; 499 } 500 501 protected enum TrainsFrom { 502 TRAINSFROMROSTER, 503 TRAINSFROMOPS, 504 TRAINSFROMUSER, 505 TRAINSFROMSETLATER 506 } 507 508 // Dispatcher options (saved to disk if user requests, and restored if present) 509 private LayoutEditor _LE = null; 510 public static final int SIGNALHEAD = 0x00; 511 public static final int SIGNALMAST = 0x01; 512 public static final int SECTIONSALLOCATED = 2; 513 private int _SignalType = SIGNALHEAD; 514 private String _StoppingSpeedName = "RestrictedSlow"; 515 private boolean _UseConnectivity = false; 516 private boolean _HasOccupancyDetection = false; // "true" if blocks have occupancy detection 517 private boolean _SetSSLDirectionalSensors = true; 518 private TrainsFrom _TrainsFrom = TrainsFrom.TRAINSFROMROSTER; 519 private boolean _AutoAllocate = false; 520 private boolean _AutoRelease = false; 521 private boolean _AutoTurnouts = false; 522 private boolean _TrustKnownTurnouts = 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()); 2180 if (as != null) { 2181 as.setAutoTurnoutsResponse(expectedTurnOutStates); 2182 } 2183 2184 if (intermediateSections.size() > 1 && mastHeldAtSection != s) { 2185 Section tmpcur = nextSection; 2186 int tmpSeqNo = nextSectionSeqNo; 2187 int tmpNxtSeqNo = tmpSeqNo; 2188 if (at.isAllocationReversed()) { 2189 tmpNxtSeqNo -= 1; 2190 } else { 2191 tmpNxtSeqNo += 1; 2192 } 2193 //The first section in the list will be the same as the nextSection, so we skip that. 2194 for (int i = 1; i < intermediateSections.size(); i++) { 2195 if (tmpcur == mastHeldAtSection) { 2196 log.debug("Section is beyond held mast do not allocate any more sections {}", (tmpcur != null ? tmpcur.getDisplayName(USERSYS) : "null")); 2197 break; 2198 } 2199 Section se = intermediateSections.get(i); 2200 as = allocateSection(at, tmpcur, tmpSeqNo, se, tmpNxtSeqNo, ar.getSectionDirection()); 2201 tmpcur = se; 2202 if (at.isAllocationReversed()) { 2203 tmpSeqNo -= 1; 2204 tmpNxtSeqNo -= 1; 2205 } else { 2206 tmpSeqNo += 1; 2207 tmpNxtSeqNo += 1; 2208 } 2209 } 2210 } 2211 int ix = -1; 2212 for (int i = 0; i < allocationRequests.size(); i++) { 2213 if (ar == allocationRequests.get(i)) { 2214 ix = i; 2215 } 2216 } 2217 if (ix != -1) { 2218 allocationRequests.remove(ix); 2219 } 2220 ar.dispose(); 2221 allocationRequestTableModel.fireTableDataChanged(); 2222 activeTrainsTableModel.fireTableDataChanged(); 2223 if (allocatedSectionTableModel != null) { 2224 allocatedSectionTableModel.fireTableDataChanged(); 2225 } 2226 if (extraFrame != null) { 2227 cancelExtraRequested(null); 2228 } 2229 if (_AutoAllocate) { 2230 requestNextAllocation(at); 2231 queueScanOfAllocationRequests(); 2232 } 2233 return as; 2234 } 2235 2236 private AllocatedSection allocateSection(ActiveTrain at, Section s, int seqNum, Section nextSection, int nextSectionSeqNo, int direction) { 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 at.addAllocatedSection(as); 2276 allocatedSections.add(as); 2277 log.debug("{}: Allocated section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS)); 2278 return as; 2279 } 2280 2281 /** 2282 * Check an active train has an occupied section 2283 * @param at ActiveTRain object 2284 * @return true / false 2285 */ 2286 protected boolean hasTrainAnOccupiedSection(ActiveTrain at) { 2287 for (AllocatedSection asItem : at.getAllocatedSectionList()) { 2288 if (asItem.getSection().getOccupancy() == Section.OCCUPIED) { 2289 return true; 2290 } 2291 } 2292 return false; 2293 } 2294 2295 /** 2296 * 2297 * @param s Section to check 2298 * @param sSeqNum Sequence number of section 2299 * @param nextSection section after 2300 * @param at the active train 2301 * @param prevSection the section before 2302 * @return null if error else a list of the turnouts and their expected states. 2303 */ 2304 List<LayoutTrackExpectedState<LayoutTurnout>> checkTurnoutStates(Section s, int sSeqNum, Section nextSection, ActiveTrain at, Section prevSection) { 2305 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutsOK; 2306 if (_AutoTurnouts || at.getAutoRun()) { 2307 // automatically set the turnouts for this section before allocation 2308 turnoutsOK = autoTurnouts.setTurnoutsInSection(s, sSeqNum, nextSection, 2309 at, _TrustKnownTurnouts, prevSection, _useTurnoutConnectionDelay); 2310 } else { 2311 // check that turnouts are correctly set before allowing allocation to proceed 2312 turnoutsOK = autoTurnouts.checkTurnoutsInSection(s, sSeqNum, nextSection, 2313 at, prevSection, _useTurnoutConnectionDelay); 2314 } 2315 if (turnoutsOK == null) { 2316 if (_AutoAllocate) { 2317 return turnoutsOK; 2318 } else { 2319 // give the manual dispatcher a chance to override turnouts not OK 2320 int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, 2321 Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"), 2322 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 2323 new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")}, 2324 Bundle.getMessage("ButtonNo")); 2325 if (selectedValue != 0 ) { // array position 0, override not pressed 2326 return null; 2327 } 2328 // return empty list 2329 turnoutsOK = new ArrayList<>(); 2330 } 2331 } 2332 return turnoutsOK; 2333 } 2334 2335 List<HeldMastDetails> heldMasts = new ArrayList<>(); 2336 2337 static class HeldMastDetails { 2338 2339 SignalMast mast = null; 2340 ActiveTrain at = null; 2341 2342 HeldMastDetails(SignalMast sm, ActiveTrain a) { 2343 mast = sm; 2344 at = a; 2345 } 2346 2347 ActiveTrain getActiveTrain() { 2348 return at; 2349 } 2350 2351 SignalMast getMast() { 2352 return mast; 2353 } 2354 } 2355 2356 public boolean isMastHeldByDispatcher(SignalMast sm, ActiveTrain at) { 2357 for (HeldMastDetails hmd : heldMasts) { 2358 if (hmd.getMast() == sm && hmd.getActiveTrain() == at) { 2359 return true; 2360 } 2361 } 2362 return false; 2363 } 2364 2365 private void removeHeldMast(SignalMast sm, ActiveTrain at) { 2366 List<HeldMastDetails> toRemove = new ArrayList<>(); 2367 for (HeldMastDetails hmd : heldMasts) { 2368 if (hmd.getActiveTrain() == at) { 2369 if (sm == null) { 2370 toRemove.add(hmd); 2371 } else if (sm == hmd.getMast()) { 2372 toRemove.add(hmd); 2373 } 2374 } 2375 } 2376 for (HeldMastDetails hmd : toRemove) { 2377 hmd.getMast().setHeld(false); 2378 heldMasts.remove(hmd); 2379 } 2380 } 2381 2382 /* 2383 * returns a list of level crossings (0 to n) in a section. 2384 */ 2385 private List<LevelXing> containedLevelXing(Section s) { 2386 List<LevelXing> _levelXingList = new ArrayList<>(); 2387 if (s == null) { 2388 log.error("null argument to 'containsLevelCrossing'"); 2389 return _levelXingList; 2390 } 2391 2392 for (var panel : editorManager.getAll(LayoutEditor.class)) { 2393 for (Block blk: s.getBlockList()) { 2394 for (LevelXing temLevelXing: panel.getConnectivityUtil().getLevelCrossingsThisBlock(blk)) { 2395 // it is returned if the block is in the crossing or connected to the crossing 2396 // we only need it if it is in the crossing 2397 if (temLevelXing.getLayoutBlockAC().getBlock() == blk || temLevelXing.getLayoutBlockBD().getBlock() == blk ) { 2398 _levelXingList.add(temLevelXing); 2399 } 2400 } 2401 } 2402 } 2403 return _levelXingList; 2404 } 2405 2406 /* 2407 * returns a list of XOvers (0 to n) in a list of blocks 2408 */ 2409 private List<LayoutTurnout> containedXOver( Section s ) { 2410 List<LayoutTurnout> _XOverList = new ArrayList<>(); 2411 LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 2412 for (var panel : editorManager.getAll(LayoutEditor.class)) { 2413 for (Block blk: s.getBlockList()) { 2414 LayoutBlock lb = lbm.getLayoutBlock(blk); 2415 List<LayoutTurnout> turnoutsInBlock = panel.getConnectivityUtil().getAllTurnoutsThisBlock(lb); 2416 for (LayoutTurnout lt: turnoutsInBlock) { 2417 if (lt.isTurnoutTypeXover() && !_XOverList.contains(lt)) { 2418 _XOverList.add(lt); 2419 } 2420 } 2421 } 2422 } 2423 return _XOverList; 2424 } 2425 2426 /** 2427 * Checks for a block in allocated section, except one 2428 * @param b - The Block 2429 * @param ignoreSection - ignore this section, can be null 2430 * @return true is The Block is being used in a section. 2431 */ 2432 protected boolean checkForBlockInAllocatedSection ( Block b, Section ignoreSection ) { 2433 for ( AllocatedSection as : allocatedSections) { 2434 if (ignoreSection == null || as.getSection() != ignoreSection) { 2435 if (as.getSection().getBlockList().contains(b)) { 2436 return true; 2437 } 2438 } 2439 } 2440 return false; 2441 } 2442 2443 /* 2444 * 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. 2445 */ 2446 protected Section checkBlocksNotInAllocatedSection(Section s, AllocationRequest ar) { 2447 ActiveTrain at = null; 2448 if (ar != null) { 2449 at = ar.getActiveTrain(); 2450 } 2451 for (AllocatedSection as : allocatedSections) { 2452 if (as.getSection() != s) { 2453 List<Block> blas = as.getSection().getBlockList(); 2454 // 2455 // When allocating the initial section for an Active Train, 2456 // we need not be concerned with any blocks in the initial section 2457 // which are unoccupied and to the rear of any occupied blocks in 2458 // the section as the train is not expected to enter those blocks. 2459 // When sections include the OS section these blocks prevented 2460 // allocation. 2461 // 2462 // The procedure is to remove those blocks (for the moment) from 2463 // the blocklist for the section during the initial allocation. 2464 // 2465 2466 List<Block> bls = new ArrayList<>(); 2467 if (ar != null && ar.getActiveTrain().getAllocatedSectionList().size() == 0) { 2468 int j; 2469 if (ar.getSectionDirection() == Section.FORWARD) { 2470 j = 0; 2471 for (int i = 0; i < s.getBlockList().size(); i++) { 2472 if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) { 2473 j = 1; 2474 } 2475 if (j == 1) { 2476 bls.add(s.getBlockList().get(i)); 2477 } 2478 } 2479 } else { 2480 j = 0; 2481 for (int i = s.getBlockList().size() - 1; i >= 0; i--) { 2482 if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) { 2483 j = 1; 2484 } 2485 if (j == 1) { 2486 bls.add(s.getBlockList().get(i)); 2487 } 2488 } 2489 } 2490 } else { 2491 bls = s.getBlockList(); 2492 // Add Blocks in any XCrossing, dont add ones already in the list 2493 for ( LevelXing lx: containedLevelXing(s)) { 2494 Block bAC = lx.getLayoutBlockAC().getBlock(); 2495 Block bBD = lx.getLayoutBlockBD().getBlock(); 2496 if (!bls.contains(bAC)) { 2497 bls.add(bAC); 2498 } 2499 if (!bls.contains(bBD)) { 2500 bls.add(bBD); 2501 } 2502 } 2503 for (LayoutTurnout lx : containedXOver(s)) { 2504 if (lx instanceof LayoutDoubleXOver) { 2505 HashSet<Block> bhs = new HashSet<Block>(4); 2506 /* quickest way to count number of unique blocks */ 2507 bhs.add(lx.getLayoutBlock().getBlock()); 2508 bhs.add(lx.getLayoutBlockB().getBlock()); 2509 bhs.add(lx.getLayoutBlockC().getBlock()); 2510 bhs.add(lx.getLayoutBlockD().getBlock()); 2511 if (bhs.size() == 4) { 2512 for (Block b : bhs) { 2513 if ( checkBlockInAnyAllocatedSection(b, at) 2514 || b.getState() == Block.OCCUPIED) { 2515 // the die is cast and switch can not be changed. 2516 // Check diagonal. If we are going continuing or divergeing 2517 // we need to check the diagonal. 2518 if (lx.getTurnout().getKnownState() != Turnout.CLOSED) { 2519 if (bls.contains(lx.getLayoutBlock().getBlock()) || 2520 bls.contains(lx.getLayoutBlockC().getBlock())) { 2521 bls.add(lx.getLayoutBlockB().getBlock()); 2522 bls.add(lx.getLayoutBlockD().getBlock()); 2523 } else { 2524 bls.add(lx.getLayoutBlock().getBlock()); 2525 bls.add(lx.getLayoutBlockC().getBlock()); 2526 } 2527 } 2528 } 2529 } 2530 } 2531 /* If further processing needed for other crossover types it goes here. 2532 } else if (lx instanceof LayoutRHXOver) { 2533 } else if (lx instanceof LayoutLHXOver) { 2534 } else { 2535*/ 2536 } 2537 } 2538 } 2539 2540 for (Block b : bls) { 2541 if (blas.contains(b)) { 2542 if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) { 2543 // no clue where the tail is some must assume this block still in use. 2544 return as.getSection(); 2545 } 2546 if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADANDTAIL) { 2547 // if this is in the oldest section then we treat as whole train.. 2548 // if there is a section that exited but occupied the tail is there 2549 for (AllocatedSection tas : allocatedSections) { 2550 if (tas.getActiveTrain() == as.getActiveTrain() && tas.getExited() && tas.getSection().getOccupancy() == Section.OCCUPIED ) { 2551 return as.getSection(); 2552 } 2553 } 2554 } else if (at != as.getActiveTrain() && as.getActiveTrain().getTrainDetection() != TrainDetection.TRAINDETECTION_WHOLETRAIN) { 2555 return as.getSection(); 2556 } 2557 if (as.getSection().getOccupancy() == Block.OCCUPIED) { 2558 //The next check looks to see if the block has already been passed or not and therefore ready for allocation. 2559 if (as.getSection().getState() == Section.FORWARD) { 2560 for (int i = 0; i < blas.size(); i++) { 2561 //The block we get to is occupied therefore the subsequent blocks have not been entered 2562 if (blas.get(i).getState() == Block.OCCUPIED) { 2563 if (ar != null) { 2564 ar.setWaitingOnBlock(b); 2565 } 2566 return as.getSection(); 2567 } else if (blas.get(i) == b) { 2568 break; 2569 } 2570 } 2571 } else { 2572 for (int i = blas.size() - 1; i >= 0; i--) { 2573 //The block we get to is occupied therefore the subsequent blocks have not been entered 2574 if (blas.get(i).getState() == Block.OCCUPIED) { 2575 if (ar != null) { 2576 ar.setWaitingOnBlock(b); 2577 } 2578 return as.getSection(); 2579 } else if (blas.get(i) == b) { 2580 break; 2581 } 2582 } 2583 } 2584 } else if (as.getSection().getOccupancy() != Section.FREE) { 2585 if (ar != null) { 2586 ar.setWaitingOnBlock(b); 2587 } 2588 return as.getSection(); 2589 } 2590 } 2591 } 2592 } 2593 } 2594 return null; 2595 } 2596 2597 // check if block is being used by anyone else but us 2598 private boolean checkBlockInAnyAllocatedSection(Block b, ActiveTrain at) { 2599 for (AllocatedSection as : allocatedSections) { 2600 if (as.getActiveTrain() != at && as.getSection().getBlockList().contains(b)) { 2601 return true; 2602 } 2603 } 2604 return false; 2605 } 2606 2607 // automatically make a choice of next section 2608 private Section autoChoice(List<Section> sList, AllocationRequest ar, int sectionSeqNo) { 2609 Section tSection = autoAllocate.autoNextSectionChoice(sList, ar, sectionSeqNo); 2610 if (tSection != null) { 2611 return tSection; 2612 } 2613 // if automatic choice failed, ask the dispatcher 2614 return dispatcherChoice(sList, ar); 2615 } 2616 2617 // manually make a choice of next section 2618 private Section dispatcherChoice(List<Section> sList, AllocationRequest ar) { 2619 Object choices[] = new Object[sList.size()]; 2620 for (int i = 0; i < sList.size(); i++) { 2621 Section s = sList.get(i); 2622 String txt = s.getDisplayName(); 2623 choices[i] = txt; 2624 } 2625 Object secName = JmriJOptionPane.showInputDialog(dispatcherFrame, 2626 Bundle.getMessage("ExplainChoice", ar.getSectionName()), 2627 Bundle.getMessage("ChoiceFrameTitle"), JmriJOptionPane 2628 .QUESTION_MESSAGE, null, choices, choices[0]); 2629 if (secName == null) { 2630 JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("WarnCancel")); 2631 return sList.get(0); 2632 } 2633 for (int j = 0; j < sList.size(); j++) { 2634 if (secName.equals(choices[j])) { 2635 return sList.get(j); 2636 } 2637 } 2638 return sList.get(0); 2639 } 2640 2641 // submit an AllocationRequest for the next Section of an ActiveTrain 2642 private void requestNextAllocation(ActiveTrain at) { 2643 // set up an Allocation Request 2644 Section next = at.getNextSectionToAllocate(); 2645 if (next == null) { 2646 return; 2647 } 2648 int seqNext = at.getNextSectionSeqNumber(); 2649 int dirNext = at.getAllocationDirectionFromSectionAndSeq(next, seqNext); 2650 requestAllocation(at, next, dirNext, seqNext, true, dispatcherFrame); 2651 } 2652 2653 /** 2654 * Check if any allocation requests need to be allocated, or if any 2655 * allocated sections need to be released 2656 */ 2657 protected void checkAutoRelease() { 2658 if (_AutoRelease) { 2659 // Auto release of exited sections has been requested - because of possible noise in block detection 2660 // hardware, allocated sections are automatically released in the order they were allocated only 2661 // Only unoccupied sections that have been exited are tested. 2662 // The next allocated section must be assigned to the same train, and it must have been entered for 2663 // the exited Section to be released. 2664 // Extra allocated sections are not automatically released (allocation number = -1). 2665 boolean foundOne = true; 2666 while ((allocatedSections.size() > 0) && foundOne) { 2667 try { 2668 foundOne = false; 2669 AllocatedSection as = null; 2670 for (int i = 0; (i < allocatedSections.size()) && !foundOne; i++) { 2671 as = allocatedSections.get(i); 2672 if (as.getExited() && (as.getSection().getOccupancy() != Section.OCCUPIED) 2673 && (as.getAllocationNumber() != -1)) { 2674 // possible candidate for deallocation - check order 2675 foundOne = true; 2676 for (int j = 0; (j < allocatedSections.size()) && foundOne; j++) { 2677 if (j != i) { 2678 AllocatedSection asx = allocatedSections.get(j); 2679 if ((asx.getActiveTrain() == as.getActiveTrain()) 2680 && (asx.getAllocationNumber() != -1) 2681 && (asx.getAllocationNumber() < as.getAllocationNumber())) { 2682 foundOne = false; 2683 } 2684 } 2685 } 2686 2687 // The train must have one occupied section. 2688 // The train may be sitting in one of its allocated section undetected. 2689 if ( foundOne && !hasTrainAnOccupiedSection(as.getActiveTrain())) { 2690 log.warn("[{}]:CheckAutoRelease release section [{}] failed, train has no occupied section", 2691 as.getActiveTrain().getActiveTrainName(),as.getSectionName()); 2692 foundOne = false; 2693 } 2694 2695 if (foundOne) { 2696 // check its not the last allocated section 2697 int allocatedCount = 0; 2698 for (int j = 0; (j < allocatedSections.size()); j++) { 2699 AllocatedSection asx = allocatedSections.get(j); 2700 if (asx.getActiveTrain() == as.getActiveTrain()) { 2701 allocatedCount++ ; 2702 } 2703 } 2704 if (allocatedCount == 1) { 2705 foundOne = false; 2706 } 2707 } 2708 if (foundOne) { 2709 // check if the next section is allocated to the same train and has been entered 2710 ActiveTrain at = as.getActiveTrain(); 2711 Section ns = as.getNextSection(); 2712 AllocatedSection nas = null; 2713 for (int k = 0; (k < allocatedSections.size()) && (nas == null); k++) { 2714 if (allocatedSections.get(k).getSection() == ns) { 2715 nas = allocatedSections.get(k); 2716 } 2717 } 2718 if ((nas == null) || (at.getStatus() == ActiveTrain.WORKING) 2719 || (at.getStatus() == ActiveTrain.STOPPED) 2720 || (at.getStatus() == ActiveTrain.READY) 2721 || (at.getMode() == ActiveTrain.MANUAL)) { 2722 // do not autorelease allocated sections from an Active Train that is 2723 // STOPPED, READY, or WORKING, or is in MANUAL mode. 2724 foundOne = false; 2725 //But do so if the active train has reached its restart point 2726 if (nas != null && at.reachedRestartPoint()) { 2727 foundOne = true; 2728 } 2729 } else { 2730 if ((nas.getActiveTrain() != as.getActiveTrain()) || (!nas.getEntered())) { 2731 foundOne = false; 2732 } 2733 } 2734 foundOne = sectionNotRequiredByHeadOnly(foundOne,at,as); 2735 if (foundOne) { 2736 log.debug("{}: releasing section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS)); 2737 doReleaseAllocatedSection(as, false); 2738 } 2739 } 2740 } 2741 } 2742 } catch (RuntimeException e) { 2743 log.warn("checkAutoRelease failed - maybe the AllocatedSection was removed due to a terminating train? {}", e.toString()); 2744 continue; 2745 } 2746 } 2747 } 2748 if (_AutoAllocate) { 2749 queueScanOfAllocationRequests(); 2750 } 2751 } 2752 2753 /* 2754 * Check whether the section is in use by a "Head Only" train and can be released. 2755 * calculate the length of exited sections, subtract the length of section 2756 * being released. If the train is moving do not include the length of the occupied section, 2757 * if the train is stationary and was stopped by sensor or speed profile include the length 2758 * of the occupied section. This is done as we dont know where the train is in the section block. 2759 */ 2760 private boolean sectionNotRequiredByHeadOnly(boolean foundOne, ActiveTrain at, AllocatedSection as) { 2761 if (at.getAutoActiveTrain() != null && at.getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) { 2762 long allocatedLengthMM = 0; 2763 for (AllocatedSection tas : at.getAllocatedSectionList()) { 2764 if (tas.getSection().getOccupancy() == Section.OCCUPIED) { 2765 if (at.getAutoActiveTrain().getAutoEngineer().isStopped() && 2766 (at.getAutoActiveTrain().getStopBySpeedProfile() || 2767 tas.getSection().getForwardStoppingSensor() != null || 2768 tas.getSection().getReverseStoppingSensor() != null)) { 2769 allocatedLengthMM += tas.getSection().getActualLength(); 2770 log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] including in length.", 2771 at.getTrainName(),tas.getSection().getDisplayName()); 2772 break; 2773 } else { 2774 log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] excluding from length.", 2775 at.getTrainName(),tas.getSection().getDisplayName()); 2776 break; 2777 } 2778 } 2779 if (tas.getExited()) { 2780 allocatedLengthMM += tas.getSection().getActualLength(); 2781 } 2782 } 2783 long trainLengthMM = at.getAutoActiveTrain().getMaxTrainLengthMM(); 2784 long releaseLengthMM = as.getSection().getActualLength(); 2785 log.debug("[{}]:Release Section [{}] by Length allocated [{}] release [{}] train [{}]", 2786 at.getTrainName(), as.getSectionName(), allocatedLengthMM, releaseLengthMM, trainLengthMM); 2787 if ((allocatedLengthMM - releaseLengthMM) < trainLengthMM) { 2788 return (false); 2789 } 2790 } 2791 return (true); 2792 } 2793 2794 /** 2795 * Releases an allocated Section, and removes it from the Dispatcher Input. 2796 * 2797 * @param as the section to release 2798 * @param terminatingTrain true if the associated train is being terminated; 2799 * false otherwise 2800 */ 2801 public void releaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) { 2802 // Unless the train is termination it must have one occupied section. 2803 // The train may be sitting in an allocated section undetected. 2804 if ( !terminatingTrain && !hasTrainAnOccupiedSection(as.getActiveTrain())) { 2805 log.warn("[{}]: releaseAllocatedSection release section [{}] failed train has no occupied section",as.getActiveTrain().getActiveTrainName(),as.getSectionName()); 2806 return; 2807 } 2808 if (_AutoAllocate ) { 2809 autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_ONE,as,terminatingTrain)); 2810 } else { 2811 doReleaseAllocatedSection( as, terminatingTrain); 2812 } 2813 } 2814 protected void doReleaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) { 2815 // check that section is not occupied if not terminating train 2816 if (!terminatingTrain && (as.getSection().getOccupancy() == Section.OCCUPIED)) { 2817 // warn the manual dispatcher that Allocated Section is occupied 2818 int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, java.text.MessageFormat.format( 2819 Bundle.getMessage("Question5"), new Object[]{as.getSectionName()}), Bundle.getMessage("WarningTitle"), 2820 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 2821 new Object[]{Bundle.getMessage("ButtonRelease"), Bundle.getMessage("ButtonNo")}, 2822 Bundle.getMessage("ButtonNo")); 2823 if (selectedValue != 0 ) { // array position 0, release not pressed 2824 return; // return without releasing if "No" or "Cancel" response 2825 } 2826 } 2827 // release the Allocated Section 2828 for (int i = allocatedSections.size(); i > 0; i--) { 2829 if (as == allocatedSections.get(i - 1)) { 2830 allocatedSections.remove(i - 1); 2831 } 2832 } 2833 as.getSection().setState(Section.FREE); 2834 as.getActiveTrain().removeAllocatedSection(as); 2835 as.dispose(); 2836 if (allocatedSectionTableModel != null) { 2837 allocatedSectionTableModel.fireTableDataChanged(); 2838 } 2839 allocationRequestTableModel.fireTableDataChanged(); 2840 activeTrainsTableModel.fireTableDataChanged(); 2841 if (_AutoAllocate) { 2842 queueScanOfAllocationRequests(); 2843 } 2844 } 2845 2846 /** 2847 * Updates display when occupancy of an allocated section changes Also 2848 * drives auto release if it is selected 2849 */ 2850 public void sectionOccupancyChanged() { 2851 queueReleaseOfCompletedAllocations(); 2852 if (allocatedSectionTableModel != null) { 2853 allocatedSectionTableModel.fireTableDataChanged(); 2854 } 2855 allocationRequestTableModel.fireTableDataChanged(); 2856 } 2857 2858 /** 2859 * Handle activity that is triggered by the fast clock 2860 */ 2861 protected void newFastClockMinute() { 2862 for (int i = delayedTrains.size() - 1; i >= 0; i--) { 2863 ActiveTrain at = delayedTrains.get(i); 2864 // check if this Active Train is waiting to start 2865 if ((!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) { 2866 // is it time to start? 2867 if (at.getDelayedStart() == ActiveTrain.TIMEDDELAY) { 2868 if (isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin())) { 2869 // allow this train to start 2870 at.setStarted(); 2871 delayedTrains.remove(i); 2872 } 2873 } 2874 } else if (at.getStarted() && at.getStatus() == ActiveTrain.READY && at.reachedRestartPoint()) { 2875 if (isFastClockTimeGE(at.getRestartDepartHr(), at.getRestartDepartMin())) { 2876 at.restart(); 2877 delayedTrains.remove(i); 2878 } 2879 } 2880 } 2881 if (_AutoAllocate) { 2882 queueScanOfAllocationRequests(); 2883 } 2884 } 2885 2886 /** 2887 * This method tests time 2888 * 2889 * @param hr the hour to test against (0-23) 2890 * @param min the minute to test against (0-59) 2891 * @return true if fast clock time and tested time are the same 2892 */ 2893 public boolean isFastClockTimeGE(int hr, int min) { 2894 Calendar now = Calendar.getInstance(); 2895 now.setTime(fastClock.getTime()); 2896 int nowHours = now.get(Calendar.HOUR_OF_DAY); 2897 int nowMinutes = now.get(Calendar.MINUTE); 2898 return ((nowHours * 60) + nowMinutes) == ((hr * 60) + min); 2899 } 2900 2901 // option access methods 2902 protected LayoutEditor getLayoutEditor() { 2903 return _LE; 2904 } 2905 2906 protected void setLayoutEditor(LayoutEditor editor) { 2907 _LE = editor; 2908 } 2909 2910 protected boolean getUseConnectivity() { 2911 return _UseConnectivity; 2912 } 2913 2914 protected void setUseConnectivity(boolean set) { 2915 _UseConnectivity = set; 2916 } 2917 2918 protected void setSignalType(int type) { 2919 _SignalType = type; 2920 } 2921 2922 protected int getSignalType() { 2923 return _SignalType; 2924 } 2925 2926 protected String getSignalTypeString() { 2927 switch (_SignalType) { 2928 case SIGNALHEAD: 2929 return Bundle.getMessage("SignalType1"); 2930 case SIGNALMAST: 2931 return Bundle.getMessage("SignalType2"); 2932 case SECTIONSALLOCATED: 2933 return Bundle.getMessage("SignalType3"); 2934 default: 2935 return "Unknown"; 2936 } 2937 } 2938 2939 protected void setStoppingSpeedName(String speedName) { 2940 _StoppingSpeedName = speedName; 2941 } 2942 2943 protected String getStoppingSpeedName() { 2944 return _StoppingSpeedName; 2945 } 2946 2947 protected float getMaximumLineSpeed() { 2948 return maximumLineSpeed; 2949 } 2950 2951 protected void setTrainsFrom(TrainsFrom value ) { 2952 _TrainsFrom = value; 2953 } 2954 2955 protected TrainsFrom getTrainsFrom() { 2956 return _TrainsFrom; 2957 } 2958 2959 protected boolean getAutoAllocate() { 2960 return _AutoAllocate; 2961 } 2962 2963 protected boolean getAutoRelease() { 2964 return _AutoRelease; 2965 } 2966 2967 protected void stopStartAutoAllocateRelease() { 2968 if (_AutoAllocate || _AutoRelease) { 2969 if (editorManager.getAll(LayoutEditor.class).size() > 0) { 2970 if (autoAllocate == null) { 2971 autoAllocate = new AutoAllocate(this,allocationRequests); 2972 autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator "); 2973 autoAllocateThread.start(); 2974 } 2975 } else { 2976 JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Error39"), 2977 Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 2978 _AutoAllocate = false; 2979 if (autoAllocateBox != null) { 2980 autoAllocateBox.setSelected(_AutoAllocate); 2981 } 2982 return; 2983 } 2984 } else { 2985 //no need for autoallocateRelease 2986 if (autoAllocate != null) { 2987 autoAllocate.setAbort(); 2988 autoAllocate = null; 2989 } 2990 } 2991 2992 } 2993 protected void setAutoAllocate(boolean set) { 2994 _AutoAllocate = set; 2995 stopStartAutoAllocateRelease(); 2996 if (autoAllocateBox != null) { 2997 autoAllocateBox.setSelected(_AutoAllocate); 2998 } 2999 } 3000 3001 protected void setAutoRelease(boolean set) { 3002 _AutoRelease = set; 3003 stopStartAutoAllocateRelease(); 3004 if (autoReleaseBox != null) { 3005 autoReleaseBox.setSelected(_AutoAllocate); 3006 } 3007 } 3008 3009 protected AutoTurnouts getAutoTurnoutsHelper () { 3010 return autoTurnouts; 3011 } 3012 3013 protected boolean getAutoTurnouts() { 3014 return _AutoTurnouts; 3015 } 3016 3017 protected void setAutoTurnouts(boolean set) { 3018 _AutoTurnouts = set; 3019 } 3020 3021 protected boolean getTrustKnownTurnouts() { 3022 return _TrustKnownTurnouts; 3023 } 3024 3025 protected void setTrustKnownTurnouts(boolean set) { 3026 _TrustKnownTurnouts = set; 3027 } 3028 3029 protected boolean getUseTurnoutConnectionDelay() { 3030 return _useTurnoutConnectionDelay; 3031 } 3032 3033 protected void setUseTurnoutConnectionDelay(boolean set) { 3034 _useTurnoutConnectionDelay = set; 3035 } 3036 3037 protected int getMinThrottleInterval() { 3038 return _MinThrottleInterval; 3039 } 3040 3041 protected void setMinThrottleInterval(int set) { 3042 _MinThrottleInterval = set; 3043 } 3044 3045 protected int getFullRampTime() { 3046 return _FullRampTime; 3047 } 3048 3049 protected void setFullRampTime(int set) { 3050 _FullRampTime = set; 3051 } 3052 3053 protected boolean getHasOccupancyDetection() { 3054 return _HasOccupancyDetection; 3055 } 3056 3057 protected void setHasOccupancyDetection(boolean set) { 3058 _HasOccupancyDetection = set; 3059 } 3060 3061 protected boolean getSetSSLDirectionalSensors() { 3062 return _SetSSLDirectionalSensors; 3063 } 3064 3065 protected void setSetSSLDirectionalSensors(boolean set) { 3066 _SetSSLDirectionalSensors = set; 3067 } 3068 3069 protected boolean getUseScaleMeters() { 3070 return _UseScaleMeters; 3071 } 3072 3073 protected void setUseScaleMeters(boolean set) { 3074 _UseScaleMeters = set; 3075 } 3076 3077 protected boolean getShortActiveTrainNames() { 3078 return _ShortActiveTrainNames; 3079 } 3080 3081 protected void setShortActiveTrainNames(boolean set) { 3082 _ShortActiveTrainNames = set; 3083 if (allocatedSectionTableModel != null) { 3084 allocatedSectionTableModel.fireTableDataChanged(); 3085 } 3086 if (allocationRequestTableModel != null) { 3087 allocationRequestTableModel.fireTableDataChanged(); 3088 } 3089 } 3090 3091 protected boolean getShortNameInBlock() { 3092 return _ShortNameInBlock; 3093 } 3094 3095 protected void setShortNameInBlock(boolean set) { 3096 _ShortNameInBlock = set; 3097 } 3098 3099 protected boolean getRosterEntryInBlock() { 3100 return _RosterEntryInBlock; 3101 } 3102 3103 protected void setRosterEntryInBlock(boolean set) { 3104 _RosterEntryInBlock = set; 3105 } 3106 3107 protected boolean getExtraColorForAllocated() { 3108 return _ExtraColorForAllocated; 3109 } 3110 3111 protected void setExtraColorForAllocated(boolean set) { 3112 _ExtraColorForAllocated = set; 3113 } 3114 3115 protected boolean getNameInAllocatedBlock() { 3116 return _NameInAllocatedBlock; 3117 } 3118 3119 protected void setNameInAllocatedBlock(boolean set) { 3120 _NameInAllocatedBlock = set; 3121 } 3122 3123 protected Scale getScale() { 3124 return _LayoutScale; 3125 } 3126 3127 protected void setScale(Scale sc) { 3128 _LayoutScale = sc; 3129 } 3130 3131 public List<ActiveTrain> getActiveTrainsList() { 3132 return activeTrainsList; 3133 } 3134 3135 protected List<AllocatedSection> getAllocatedSectionsList() { 3136 return allocatedSections; 3137 } 3138 3139 public ActiveTrain getActiveTrainForRoster(RosterEntry re) { 3140 if ( _TrainsFrom != TrainsFrom.TRAINSFROMROSTER) { 3141 return null; 3142 } 3143 for (ActiveTrain at : activeTrainsList) { 3144 if (at.getRosterEntry().equals(re)) { 3145 return at; 3146 } 3147 } 3148 return null; 3149 3150 } 3151 3152 protected boolean getSupportVSDecoder() { 3153 return _SupportVSDecoder; 3154 } 3155 3156 protected void setSupportVSDecoder(boolean set) { 3157 _SupportVSDecoder = set; 3158 } 3159 3160 // called by ActivateTrainFrame after a new train is all set up 3161 // Dispatcher side of activating a new train should be completed here 3162 // Jay Janzen protection changed to public for access via scripting 3163 public void newTrainDone(ActiveTrain at) { 3164 if (at != null) { 3165 // a new active train was created, check for delayed start 3166 if (at.getDelayedStart() != ActiveTrain.NODELAY && (!at.getStarted())) { 3167 delayedTrains.add(at); 3168 fastClockWarn(true); 3169 } // djd needs work here 3170 // check for delayed restart 3171 else if (at.getDelayedRestart() == ActiveTrain.TIMEDDELAY) { 3172 fastClockWarn(false); 3173 } 3174 } 3175 if (atFrame != null) { 3176 ThreadingUtil.runOnGUI( () -> atFrame.setVisible(false)); 3177 atFrame.dispose(); 3178 atFrame = null; 3179 } 3180 newTrainActive = false; 3181 } 3182 3183 protected void removeDelayedTrain(ActiveTrain at) { 3184 delayedTrains.remove(at); 3185 } 3186 3187 private void fastClockWarn(boolean wMess) { 3188 if (fastClockSensor.getState() == Sensor.ACTIVE) { 3189 return; 3190 } 3191 // warn that the fast clock is not running 3192 String mess = ""; 3193 if (wMess) { 3194 mess = Bundle.getMessage("FastClockWarn"); 3195 } else { 3196 mess = Bundle.getMessage("FastClockWarn2"); 3197 } 3198 int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, 3199 mess, Bundle.getMessage("WarningTitle"), 3200 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 3201 new Object[]{Bundle.getMessage("ButtonYesStart"), Bundle.getMessage("ButtonNo")}, 3202 Bundle.getMessage("ButtonNo")); 3203 if (selectedValue == 0) { 3204 try { 3205 fastClockSensor.setState(Sensor.ACTIVE); 3206 } catch (jmri.JmriException reason) { 3207 log.error("Exception when setting fast clock sensor"); 3208 } 3209 } 3210 } 3211 3212 // Jay Janzen 3213 // Protection changed to public to allow access via scripting 3214 public AutoTrainsFrame getAutoTrainsFrame() { 3215 return _autoTrainsFrame; 3216 } 3217 3218 /** 3219 * Table model for Active Trains Table in Dispatcher window 3220 */ 3221 public class ActiveTrainsTableModel extends javax.swing.table.AbstractTableModel implements 3222 java.beans.PropertyChangeListener { 3223 3224 public static final int TRANSIT_COLUMN = 0; 3225 public static final int TRANSIT_COLUMN_U = 1; 3226 public static final int TRAIN_COLUMN = 2; 3227 public static final int TYPE_COLUMN = 3; 3228 public static final int STATUS_COLUMN = 4; 3229 public static final int MODE_COLUMN = 5; 3230 public static final int ALLOCATED_COLUMN = 6; 3231 public static final int ALLOCATED_COLUMN_U = 7; 3232 public static final int NEXTSECTION_COLUMN = 8; 3233 public static final int NEXTSECTION_COLUMN_U = 9; 3234 public static final int ALLOCATEBUTTON_COLUMN = 10; 3235 public static final int TERMINATEBUTTON_COLUMN = 11; 3236 public static final int RESTARTCHECKBOX_COLUMN = 12; 3237 public static final int ISAUTO_COLUMN = 13; 3238 public static final int CURRENTSIGNAL_COLUMN = 14; 3239 public static final int CURRENTSIGNAL_COLUMN_U = 15; 3240 public static final int DCC_ADDRESS = 16; 3241 public static final int MAX_COLUMN = 16; 3242 public ActiveTrainsTableModel() { 3243 super(); 3244 } 3245 3246 @Override 3247 public void propertyChange(java.beans.PropertyChangeEvent e) { 3248 if (e.getPropertyName().equals("length")) { 3249 fireTableDataChanged(); 3250 } 3251 } 3252 3253 @Override 3254 public Class<?> getColumnClass(int col) { 3255 switch (col) { 3256 case ALLOCATEBUTTON_COLUMN: 3257 case TERMINATEBUTTON_COLUMN: 3258 return JButton.class; 3259 case RESTARTCHECKBOX_COLUMN: 3260 case ISAUTO_COLUMN: 3261 return Boolean.class; 3262 default: 3263 return String.class; 3264 } 3265 } 3266 3267 @Override 3268 public int getColumnCount() { 3269 return MAX_COLUMN + 1; 3270 } 3271 3272 @Override 3273 public int getRowCount() { 3274 return (activeTrainsList.size()); 3275 } 3276 3277 @Override 3278 public boolean isCellEditable(int row, int col) { 3279 switch (col) { 3280 case ALLOCATEBUTTON_COLUMN: 3281 case TERMINATEBUTTON_COLUMN: 3282 case RESTARTCHECKBOX_COLUMN: 3283 return (true); 3284 default: 3285 return (false); 3286 } 3287 } 3288 3289 @Override 3290 public String getColumnName(int col) { 3291 switch (col) { 3292 case TRANSIT_COLUMN: 3293 return Bundle.getMessage("TransitColumnSysTitle"); 3294 case TRANSIT_COLUMN_U: 3295 return Bundle.getMessage("TransitColumnTitle"); 3296 case TRAIN_COLUMN: 3297 return Bundle.getMessage("TrainColumnTitle"); 3298 case TYPE_COLUMN: 3299 return Bundle.getMessage("TrainTypeColumnTitle"); 3300 case STATUS_COLUMN: 3301 return Bundle.getMessage("TrainStatusColumnTitle"); 3302 case MODE_COLUMN: 3303 return Bundle.getMessage("TrainModeColumnTitle"); 3304 case ALLOCATED_COLUMN: 3305 return Bundle.getMessage("AllocatedSectionColumnSysTitle"); 3306 case ALLOCATED_COLUMN_U: 3307 return Bundle.getMessage("AllocatedSectionColumnTitle"); 3308 case NEXTSECTION_COLUMN: 3309 return Bundle.getMessage("NextSectionColumnSysTitle"); 3310 case NEXTSECTION_COLUMN_U: 3311 return Bundle.getMessage("NextSectionColumnTitle"); 3312 case RESTARTCHECKBOX_COLUMN: 3313 return(Bundle.getMessage("AutoRestartColumnTitle")); 3314 case ALLOCATEBUTTON_COLUMN: 3315 return(Bundle.getMessage("AllocateButton")); 3316 case TERMINATEBUTTON_COLUMN: 3317 return(Bundle.getMessage("TerminateTrain")); 3318 case ISAUTO_COLUMN: 3319 return(Bundle.getMessage("AutoColumnTitle")); 3320 case CURRENTSIGNAL_COLUMN: 3321 return(Bundle.getMessage("CurrentSignalSysColumnTitle")); 3322 case CURRENTSIGNAL_COLUMN_U: 3323 return(Bundle.getMessage("CurrentSignalColumnTitle")); 3324 case DCC_ADDRESS: 3325 return(Bundle.getMessage("DccColumnTitleColumnTitle")); 3326 default: 3327 return ""; 3328 } 3329 } 3330 3331 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", 3332 justification="better to keep cases in column order rather than to combine") 3333 public int getPreferredWidth(int col) { 3334 switch (col) { 3335 case TRANSIT_COLUMN: 3336 case TRANSIT_COLUMN_U: 3337 case TRAIN_COLUMN: 3338 return new JTextField(17).getPreferredSize().width; 3339 case TYPE_COLUMN: 3340 return new JTextField(16).getPreferredSize().width; 3341 case STATUS_COLUMN: 3342 return new JTextField(8).getPreferredSize().width; 3343 case MODE_COLUMN: 3344 return new JTextField(11).getPreferredSize().width; 3345 case ALLOCATED_COLUMN: 3346 case ALLOCATED_COLUMN_U: 3347 return new JTextField(17).getPreferredSize().width; 3348 case NEXTSECTION_COLUMN: 3349 case NEXTSECTION_COLUMN_U: 3350 return new JTextField(17).getPreferredSize().width; 3351 case ALLOCATEBUTTON_COLUMN: 3352 case TERMINATEBUTTON_COLUMN: 3353 case RESTARTCHECKBOX_COLUMN: 3354 case ISAUTO_COLUMN: 3355 case CURRENTSIGNAL_COLUMN: 3356 case CURRENTSIGNAL_COLUMN_U: 3357 case DCC_ADDRESS: 3358 return new JTextField(5).getPreferredSize().width; 3359 default: 3360 // fall through 3361 break; 3362 } 3363 return new JTextField(5).getPreferredSize().width; 3364 } 3365 3366 @Override 3367 public Object getValueAt(int r, int c) { 3368 int rx = r; 3369 if (rx >= activeTrainsList.size()) { 3370 return null; 3371 } 3372 ActiveTrain at = activeTrainsList.get(rx); 3373 switch (c) { 3374 case TRANSIT_COLUMN: 3375 return (at.getTransit().getSystemName()); 3376 case TRANSIT_COLUMN_U: 3377 if (at.getTransit() != null && at.getTransit().getUserName() != null) { 3378 return (at.getTransit().getUserName()); 3379 } else { 3380 return ""; 3381 } 3382 case TRAIN_COLUMN: 3383 return (at.getTrainName()); 3384 case TYPE_COLUMN: 3385 return (at.getTrainTypeText()); 3386 case STATUS_COLUMN: 3387 return (at.getStatusText()); 3388 case MODE_COLUMN: 3389 return (at.getModeText()); 3390 case ALLOCATED_COLUMN: 3391 if (at.getLastAllocatedSection() != null) { 3392 return (at.getLastAllocatedSection().getSystemName()); 3393 } else { 3394 return "<none>"; 3395 } 3396 case ALLOCATED_COLUMN_U: 3397 if (at.getLastAllocatedSection() != null && at.getLastAllocatedSection().getUserName() != null) { 3398 return (at.getLastAllocatedSection().getUserName()); 3399 } else { 3400 return "<none>"; 3401 } 3402 case NEXTSECTION_COLUMN: 3403 if (at.getNextSectionToAllocate() != null) { 3404 return (at.getNextSectionToAllocate().getSystemName()); 3405 } else { 3406 return "<none>"; 3407 } 3408 case NEXTSECTION_COLUMN_U: 3409 if (at.getNextSectionToAllocate() != null && at.getNextSectionToAllocate().getUserName() != null) { 3410 return (at.getNextSectionToAllocate().getUserName()); 3411 } else { 3412 return "<none>"; 3413 } 3414 case ALLOCATEBUTTON_COLUMN: 3415 return Bundle.getMessage("AllocateButtonName"); 3416 case TERMINATEBUTTON_COLUMN: 3417 return Bundle.getMessage("TerminateTrain"); 3418 case RESTARTCHECKBOX_COLUMN: 3419 return at.getResetWhenDone(); 3420 case ISAUTO_COLUMN: 3421 return at.getAutoRun(); 3422 case CURRENTSIGNAL_COLUMN: 3423 if (at.getAutoRun()) { 3424 return(at.getAutoActiveTrain().getCurrentSignal()); 3425 } else { 3426 return("NA"); 3427 } 3428 case CURRENTSIGNAL_COLUMN_U: 3429 if (at.getAutoRun()) { 3430 return(at.getAutoActiveTrain().getCurrentSignalUserName()); 3431 } else { 3432 return("NA"); 3433 } 3434 case DCC_ADDRESS: 3435 if (at.getDccAddress() != null) { 3436 return(at.getDccAddress()); 3437 } else { 3438 return("NA"); 3439 } 3440 default: 3441 return (" "); 3442 } 3443 } 3444 3445 @Override 3446 public void setValueAt(Object value, int row, int col) { 3447 if (col == ALLOCATEBUTTON_COLUMN) { 3448 // open an allocate window 3449 allocateNextRequested(row); 3450 } 3451 if (col == TERMINATEBUTTON_COLUMN) { 3452 if (activeTrainsList.get(row) != null) { 3453 terminateActiveTrain(activeTrainsList.get(row),true,false); 3454 } 3455 } 3456 if (col == RESTARTCHECKBOX_COLUMN) { 3457 ActiveTrain at = null; 3458 at = activeTrainsList.get(row); 3459 if (activeTrainsList.get(row) != null) { 3460 if (!at.getResetWhenDone()) { 3461 at.setResetWhenDone(true); 3462 return; 3463 } 3464 at.setResetWhenDone(false); 3465 for (int j = restartingTrainsList.size(); j > 0; j--) { 3466 if (restartingTrainsList.get(j - 1) == at) { 3467 restartingTrainsList.remove(j - 1); 3468 return; 3469 } 3470 } 3471 } 3472 } 3473 } 3474 } 3475 3476 /** 3477 * Table model for Allocation Request Table in Dispatcher window 3478 */ 3479 public class AllocationRequestTableModel extends javax.swing.table.AbstractTableModel implements 3480 java.beans.PropertyChangeListener { 3481 3482 public static final int TRANSIT_COLUMN = 0; 3483 public static final int TRANSIT_COLUMN_U = 1; 3484 public static final int TRAIN_COLUMN = 2; 3485 public static final int PRIORITY_COLUMN = 3; 3486 public static final int TRAINTYPE_COLUMN = 4; 3487 public static final int SECTION_COLUMN = 5; 3488 public static final int SECTION_COLUMN_U = 6; 3489 public static final int STATUS_COLUMN = 7; 3490 public static final int OCCUPANCY_COLUMN = 8; 3491 public static final int SECTIONLENGTH_COLUMN = 9; 3492 public static final int ALLOCATEBUTTON_COLUMN = 10; 3493 public static final int CANCELBUTTON_COLUMN = 11; 3494 public static final int MAX_COLUMN = 11; 3495 3496 public AllocationRequestTableModel() { 3497 super(); 3498 } 3499 3500 @Override 3501 public void propertyChange(java.beans.PropertyChangeEvent e) { 3502 if (e.getPropertyName().equals("length")) { 3503 fireTableDataChanged(); 3504 } 3505 } 3506 3507 @Override 3508 public Class<?> getColumnClass(int c) { 3509 if (c == CANCELBUTTON_COLUMN) { 3510 return JButton.class; 3511 } 3512 if (c == ALLOCATEBUTTON_COLUMN) { 3513 return JButton.class; 3514 } 3515 //if (c == CANCELRESTART_COLUMN) { 3516 // return JButton.class; 3517 //} 3518 return String.class; 3519 } 3520 3521 @Override 3522 public int getColumnCount() { 3523 return MAX_COLUMN + 1; 3524 } 3525 3526 @Override 3527 public int getRowCount() { 3528 return (allocationRequests.size()); 3529 } 3530 3531 @Override 3532 public boolean isCellEditable(int r, int c) { 3533 if (c == CANCELBUTTON_COLUMN) { 3534 return (true); 3535 } 3536 if (c == ALLOCATEBUTTON_COLUMN) { 3537 return (true); 3538 } 3539 return (false); 3540 } 3541 3542 @Override 3543 public String getColumnName(int col) { 3544 switch (col) { 3545 case TRANSIT_COLUMN: 3546 return Bundle.getMessage("TransitColumnSysTitle"); 3547 case TRANSIT_COLUMN_U: 3548 return Bundle.getMessage("TransitColumnTitle"); 3549 case TRAIN_COLUMN: 3550 return Bundle.getMessage("TrainColumnTitle"); 3551 case PRIORITY_COLUMN: 3552 return Bundle.getMessage("PriorityLabel"); 3553 case TRAINTYPE_COLUMN: 3554 return Bundle.getMessage("TrainTypeColumnTitle"); 3555 case SECTION_COLUMN: 3556 return Bundle.getMessage("SectionColumnSysTitle"); 3557 case SECTION_COLUMN_U: 3558 return Bundle.getMessage("SectionColumnTitle"); 3559 case STATUS_COLUMN: 3560 return Bundle.getMessage("StatusColumnTitle"); 3561 case OCCUPANCY_COLUMN: 3562 return Bundle.getMessage("OccupancyColumnTitle"); 3563 case SECTIONLENGTH_COLUMN: 3564 return Bundle.getMessage("SectionLengthColumnTitle"); 3565 case ALLOCATEBUTTON_COLUMN: 3566 return Bundle.getMessage("AllocateButton"); 3567 case CANCELBUTTON_COLUMN: 3568 return Bundle.getMessage("ButtonCancel"); 3569 default: 3570 return ""; 3571 } 3572 } 3573 3574 public int getPreferredWidth(int col) { 3575 switch (col) { 3576 case TRANSIT_COLUMN: 3577 case TRANSIT_COLUMN_U: 3578 case TRAIN_COLUMN: 3579 return new JTextField(17).getPreferredSize().width; 3580 case PRIORITY_COLUMN: 3581 return new JTextField(8).getPreferredSize().width; 3582 case TRAINTYPE_COLUMN: 3583 return new JTextField(15).getPreferredSize().width; 3584 case SECTION_COLUMN: 3585 return new JTextField(25).getPreferredSize().width; 3586 case STATUS_COLUMN: 3587 return new JTextField(15).getPreferredSize().width; 3588 case OCCUPANCY_COLUMN: 3589 return new JTextField(10).getPreferredSize().width; 3590 case SECTIONLENGTH_COLUMN: 3591 return new JTextField(8).getPreferredSize().width; 3592 case ALLOCATEBUTTON_COLUMN: 3593 return new JTextField(12).getPreferredSize().width; 3594 case CANCELBUTTON_COLUMN: 3595 return new JTextField(10).getPreferredSize().width; 3596 default: 3597 // fall through 3598 break; 3599 } 3600 return new JTextField(5).getPreferredSize().width; 3601 } 3602 3603 @Override 3604 public Object getValueAt(int r, int c) { 3605 int rx = r; 3606 if (rx >= allocationRequests.size()) { 3607 return null; 3608 } 3609 AllocationRequest ar = allocationRequests.get(rx); 3610 switch (c) { 3611 case TRANSIT_COLUMN: 3612 return (ar.getActiveTrain().getTransit().getSystemName()); 3613 case TRANSIT_COLUMN_U: 3614 if (ar.getActiveTrain().getTransit() != null && ar.getActiveTrain().getTransit().getUserName() != null) { 3615 return (ar.getActiveTrain().getTransit().getUserName()); 3616 } else { 3617 return ""; 3618 } 3619 case TRAIN_COLUMN: 3620 return (ar.getActiveTrain().getTrainName()); 3621 case PRIORITY_COLUMN: 3622 return (" " + ar.getActiveTrain().getPriority()); 3623 case TRAINTYPE_COLUMN: 3624 return (ar.getActiveTrain().getTrainTypeText()); 3625 case SECTION_COLUMN: 3626 if (ar.getSection() != null) { 3627 return (ar.getSection().getSystemName()); 3628 } else { 3629 return "<none>"; 3630 } 3631 case SECTION_COLUMN_U: 3632 if (ar.getSection() != null && ar.getSection().getUserName() != null) { 3633 return (ar.getSection().getUserName()); 3634 } else { 3635 return "<none>"; 3636 } 3637 case STATUS_COLUMN: 3638 if (ar.getSection().getState() == Section.FREE) { 3639 return Bundle.getMessage("FREE"); 3640 } 3641 return Bundle.getMessage("ALLOCATED"); 3642 case OCCUPANCY_COLUMN: 3643 if (!_HasOccupancyDetection) { 3644 return Bundle.getMessage("UNKNOWN"); 3645 } 3646 if (ar.getSection().getOccupancy() == Section.OCCUPIED) { 3647 return Bundle.getMessage("OCCUPIED"); 3648 } 3649 return Bundle.getMessage("UNOCCUPIED"); 3650 case SECTIONLENGTH_COLUMN: 3651 return (" " + ar.getSection().getLengthI(_UseScaleMeters, _LayoutScale)); 3652 case ALLOCATEBUTTON_COLUMN: 3653 return Bundle.getMessage("AllocateButton"); 3654 case CANCELBUTTON_COLUMN: 3655 return Bundle.getMessage("ButtonCancel"); 3656 default: 3657 return (" "); 3658 } 3659 } 3660 3661 @Override 3662 public void setValueAt(Object value, int row, int col) { 3663 if (col == ALLOCATEBUTTON_COLUMN) { 3664 // open an allocate window 3665 allocateRequested(row); 3666 } 3667 if (col == CANCELBUTTON_COLUMN) { 3668 // open an allocate window 3669 cancelAllocationRequest(row); 3670 } 3671 } 3672 } 3673 3674 /** 3675 * Table model for Allocated Section Table 3676 */ 3677 public class AllocatedSectionTableModel extends javax.swing.table.AbstractTableModel implements 3678 java.beans.PropertyChangeListener { 3679 3680 public static final int TRANSIT_COLUMN = 0; 3681 public static final int TRANSIT_COLUMN_U = 1; 3682 public static final int TRAIN_COLUMN = 2; 3683 public static final int SECTION_COLUMN = 3; 3684 public static final int SECTION_COLUMN_U = 4; 3685 public static final int OCCUPANCY_COLUMN = 5; 3686 public static final int USESTATUS_COLUMN = 6; 3687 public static final int RELEASEBUTTON_COLUMN = 7; 3688 public static final int MAX_COLUMN = 7; 3689 3690 public AllocatedSectionTableModel() { 3691 super(); 3692 } 3693 3694 @Override 3695 public void propertyChange(java.beans.PropertyChangeEvent e) { 3696 if (e.getPropertyName().equals("length")) { 3697 fireTableDataChanged(); 3698 } 3699 } 3700 3701 @Override 3702 public Class<?> getColumnClass(int c) { 3703 if (c == RELEASEBUTTON_COLUMN) { 3704 return JButton.class; 3705 } 3706 return String.class; 3707 } 3708 3709 @Override 3710 public int getColumnCount() { 3711 return MAX_COLUMN + 1; 3712 } 3713 3714 @Override 3715 public int getRowCount() { 3716 return (allocatedSections.size()); 3717 } 3718 3719 @Override 3720 public boolean isCellEditable(int r, int c) { 3721 if (c == RELEASEBUTTON_COLUMN) { 3722 return (true); 3723 } 3724 return (false); 3725 } 3726 3727 @Override 3728 public String getColumnName(int col) { 3729 switch (col) { 3730 case TRANSIT_COLUMN: 3731 return Bundle.getMessage("TransitColumnSysTitle"); 3732 case TRANSIT_COLUMN_U: 3733 return Bundle.getMessage("TransitColumnTitle"); 3734 case TRAIN_COLUMN: 3735 return Bundle.getMessage("TrainColumnTitle"); 3736 case SECTION_COLUMN: 3737 return Bundle.getMessage("AllocatedSectionColumnSysTitle"); 3738 case SECTION_COLUMN_U: 3739 return Bundle.getMessage("AllocatedSectionColumnTitle"); 3740 case OCCUPANCY_COLUMN: 3741 return Bundle.getMessage("OccupancyColumnTitle"); 3742 case USESTATUS_COLUMN: 3743 return Bundle.getMessage("UseStatusColumnTitle"); 3744 case RELEASEBUTTON_COLUMN: 3745 return Bundle.getMessage("ReleaseButton"); 3746 default: 3747 return ""; 3748 } 3749 } 3750 3751 public int getPreferredWidth(int col) { 3752 switch (col) { 3753 case TRANSIT_COLUMN: 3754 case TRANSIT_COLUMN_U: 3755 case TRAIN_COLUMN: 3756 return new JTextField(17).getPreferredSize().width; 3757 case SECTION_COLUMN: 3758 case SECTION_COLUMN_U: 3759 return new JTextField(25).getPreferredSize().width; 3760 case OCCUPANCY_COLUMN: 3761 return new JTextField(10).getPreferredSize().width; 3762 case USESTATUS_COLUMN: 3763 return new JTextField(15).getPreferredSize().width; 3764 case RELEASEBUTTON_COLUMN: 3765 return new JTextField(12).getPreferredSize().width; 3766 default: 3767 // fall through 3768 break; 3769 } 3770 return new JTextField(5).getPreferredSize().width; 3771 } 3772 3773 @Override 3774 public Object getValueAt(int r, int c) { 3775 int rx = r; 3776 if (rx >= allocatedSections.size()) { 3777 return null; 3778 } 3779 AllocatedSection as = allocatedSections.get(rx); 3780 switch (c) { 3781 case TRANSIT_COLUMN: 3782 return (as.getActiveTrain().getTransit().getSystemName()); 3783 case TRANSIT_COLUMN_U: 3784 if (as.getActiveTrain().getTransit() != null && as.getActiveTrain().getTransit().getUserName() != null) { 3785 return (as.getActiveTrain().getTransit().getUserName()); 3786 } else { 3787 return ""; 3788 } 3789 case TRAIN_COLUMN: 3790 return (as.getActiveTrain().getTrainName()); 3791 case SECTION_COLUMN: 3792 if (as.getSection() != null) { 3793 return (as.getSection().getSystemName()); 3794 } else { 3795 return "<none>"; 3796 } 3797 case SECTION_COLUMN_U: 3798 if (as.getSection() != null && as.getSection().getUserName() != null) { 3799 return (as.getSection().getUserName()); 3800 } else { 3801 return "<none>"; 3802 } 3803 case OCCUPANCY_COLUMN: 3804 if (!_HasOccupancyDetection) { 3805 return Bundle.getMessage("UNKNOWN"); 3806 } 3807 if (as.getSection().getOccupancy() == Section.OCCUPIED) { 3808 return Bundle.getMessage("OCCUPIED"); 3809 } 3810 return Bundle.getMessage("UNOCCUPIED"); 3811 case USESTATUS_COLUMN: 3812 if (!as.getEntered()) { 3813 return Bundle.getMessage("NotEntered"); 3814 } 3815 if (as.getExited()) { 3816 return Bundle.getMessage("Exited"); 3817 } 3818 return Bundle.getMessage("Entered"); 3819 case RELEASEBUTTON_COLUMN: 3820 return Bundle.getMessage("ReleaseButton"); 3821 default: 3822 return (" "); 3823 } 3824 } 3825 3826 @Override 3827 public void setValueAt(Object value, int row, int col) { 3828 if (col == RELEASEBUTTON_COLUMN) { 3829 releaseAllocatedSectionFromTable(row); 3830 } 3831 } 3832 } 3833 3834 /* 3835 * Mouse popup stuff 3836 */ 3837 3838 /** 3839 * Process the column header click 3840 * @param e the evnt data 3841 * @param table the JTable 3842 */ 3843 protected void showTableHeaderPopup(JmriMouseEvent e, JTable table) { 3844 JPopupMenu popupMenu = new JPopupMenu(); 3845 XTableColumnModel tcm = (XTableColumnModel) table.getColumnModel(); 3846 for (int i = 0; i < tcm.getColumnCount(false); i++) { 3847 TableColumn tc = tcm.getColumnByModelIndex(i); 3848 String columnName = table.getModel().getColumnName(i); 3849 if (columnName != null && !columnName.equals("")) { 3850 JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(table.getModel().getColumnName(i), tcm.isColumnVisible(tc)); 3851 menuItem.addActionListener(new HeaderActionListener(tc, tcm)); 3852 popupMenu.add(menuItem); 3853 } 3854 3855 } 3856 popupMenu.show(e.getComponent(), e.getX(), e.getY()); 3857 } 3858 3859 /** 3860 * Adds the column header pop listener to a JTable using XTableColumnModel 3861 * @param table The JTable effected. 3862 */ 3863 protected void addMouseListenerToHeader(JTable table) { 3864 JmriMouseListener mouseHeaderListener = new TableHeaderListener(table); 3865 table.getTableHeader().addMouseListener(JmriMouseListener.adapt(mouseHeaderListener)); 3866 } 3867 3868 static protected class HeaderActionListener implements ActionListener { 3869 3870 TableColumn tc; 3871 XTableColumnModel tcm; 3872 3873 HeaderActionListener(TableColumn tc, XTableColumnModel tcm) { 3874 this.tc = tc; 3875 this.tcm = tcm; 3876 } 3877 3878 @Override 3879 public void actionPerformed(ActionEvent e) { 3880 JCheckBoxMenuItem check = (JCheckBoxMenuItem) e.getSource(); 3881 //Do not allow the last column to be hidden 3882 if (!check.isSelected() && tcm.getColumnCount(true) == 1) { 3883 return; 3884 } 3885 tcm.setColumnVisible(tc, check.isSelected()); 3886 } 3887 } 3888 3889 /** 3890 * Class to support Columnheader popup menu on XTableColum model. 3891 */ 3892 class TableHeaderListener extends JmriMouseAdapter { 3893 3894 JTable table; 3895 3896 TableHeaderListener(JTable tbl) { 3897 super(); 3898 table = tbl; 3899 } 3900 3901 /** 3902 * {@inheritDoc} 3903 */ 3904 @Override 3905 public void mousePressed(JmriMouseEvent e) { 3906 if (e.isPopupTrigger()) { 3907 showTableHeaderPopup(e, table); 3908 } 3909 } 3910 3911 /** 3912 * {@inheritDoc} 3913 */ 3914 @Override 3915 public void mouseReleased(JmriMouseEvent e) { 3916 if (e.isPopupTrigger()) { 3917 showTableHeaderPopup(e, table); 3918 } 3919 } 3920 3921 /** 3922 * {@inheritDoc} 3923 */ 3924 @Override 3925 public void mouseClicked(JmriMouseEvent e) { 3926 if (e.isPopupTrigger()) { 3927 showTableHeaderPopup(e, table); 3928 } 3929 } 3930 } 3931 3932 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DispatcherFrame.class); 3933 3934}