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