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