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