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