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