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