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