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