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