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