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