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