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 * Check an active train has an occupied section 2121 * @param at ActiveTRain object 2122 * @return true / false 2123 */ 2124 protected boolean hasTrainAnOccupiedSection(ActiveTrain at) { 2125 for (AllocatedSection asItem : at.getAllocatedSectionList()) { 2126 if (asItem.getSection().getOccupancy() == Section.OCCUPIED) { 2127 return true; 2128 } 2129 } 2130 return false; 2131 } 2132 2133 /** 2134 * 2135 * @param s Section to check 2136 * @param sSeqNum Sequence number of section 2137 * @param nextSection section after 2138 * @param at the active train 2139 * @param prevSection the section before 2140 * @return null if error else a list of the turnouts and their expected states. 2141 */ 2142 List<LayoutTrackExpectedState<LayoutTurnout>> checkTurnoutStates(Section s, int sSeqNum, Section nextSection, ActiveTrain at, Section prevSection) { 2143 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutsOK; 2144 if (_AutoTurnouts || at.getAutoRun()) { 2145 // automatically set the turnouts for this section before allocation 2146 turnoutsOK = autoTurnouts.setTurnoutsInSection(s, sSeqNum, nextSection, 2147 at, _TrustKnownTurnouts, prevSection); 2148 } else { 2149 // check that turnouts are correctly set before allowing allocation to proceed 2150 turnoutsOK = autoTurnouts.checkTurnoutsInSection(s, sSeqNum, nextSection, 2151 at, prevSection); 2152 } 2153 if (turnoutsOK == null) { 2154 if (_AutoAllocate) { 2155 return turnoutsOK; 2156 } else { 2157 // give the manual dispatcher a chance to override turnouts not OK 2158 int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, 2159 Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"), 2160 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 2161 new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")}, 2162 Bundle.getMessage("ButtonNo")); 2163 if (selectedValue != 0 ) { // array position 0, override not pressed 2164 return null; 2165 } 2166 // return empty list 2167 turnoutsOK = new ArrayList<>(); 2168 } 2169 } 2170 return turnoutsOK; 2171 } 2172 2173 List<HeldMastDetails> heldMasts = new ArrayList<>(); 2174 2175 static class HeldMastDetails { 2176 2177 SignalMast mast = null; 2178 ActiveTrain at = null; 2179 2180 HeldMastDetails(SignalMast sm, ActiveTrain a) { 2181 mast = sm; 2182 at = a; 2183 } 2184 2185 ActiveTrain getActiveTrain() { 2186 return at; 2187 } 2188 2189 SignalMast getMast() { 2190 return mast; 2191 } 2192 } 2193 2194 public boolean isMastHeldByDispatcher(SignalMast sm, ActiveTrain at) { 2195 for (HeldMastDetails hmd : heldMasts) { 2196 if (hmd.getMast() == sm && hmd.getActiveTrain() == at) { 2197 return true; 2198 } 2199 } 2200 return false; 2201 } 2202 2203 private void removeHeldMast(SignalMast sm, ActiveTrain at) { 2204 List<HeldMastDetails> toRemove = new ArrayList<>(); 2205 for (HeldMastDetails hmd : heldMasts) { 2206 if (hmd.getActiveTrain() == at) { 2207 if (sm == null) { 2208 toRemove.add(hmd); 2209 } else if (sm == hmd.getMast()) { 2210 toRemove.add(hmd); 2211 } 2212 } 2213 } 2214 for (HeldMastDetails hmd : toRemove) { 2215 hmd.getMast().setHeld(false); 2216 heldMasts.remove(hmd); 2217 } 2218 } 2219 2220 /* 2221 * returns a list of level crossings (0 to n) in a section. 2222 */ 2223 private List<LevelXing> containedLevelXing(Section s) { 2224 List<LevelXing> _levelXingList = new ArrayList<>(); 2225 if (s == null) { 2226 log.error("null argument to 'containsLevelCrossing'"); 2227 return _levelXingList; 2228 } 2229 2230 for (var panel : editorManager.getAll(LayoutEditor.class)) { 2231 for (Block blk: s.getBlockList()) { 2232 for (LevelXing temLevelXing: panel.getConnectivityUtil().getLevelCrossingsThisBlock(blk)) { 2233 // it is returned if the block is in the crossing or connected to the crossing 2234 // we only need it if it is in the crossing 2235 if (temLevelXing.getLayoutBlockAC().getBlock() == blk || temLevelXing.getLayoutBlockBD().getBlock() == blk ) { 2236 _levelXingList.add(temLevelXing); 2237 } 2238 } 2239 } 2240 } 2241 return _levelXingList; 2242 } 2243 2244 /** 2245 * Checks for a block in allocated section, except one 2246 * @param b - The Block 2247 * @param ignoreSection - ignore this section, can be null 2248 * @return true is The Block is being used in a section. 2249 */ 2250 protected boolean checkForBlockInAllocatedSection ( Block b, Section ignoreSection ) { 2251 for ( AllocatedSection as : allocatedSections) { 2252 if (ignoreSection == null || as.getSection() != ignoreSection) { 2253 if (as.getSection().getBlockList().contains(b)) { 2254 return true; 2255 } 2256 } 2257 } 2258 return false; 2259 } 2260 2261 /* 2262 * 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. 2263 */ 2264 protected Section checkBlocksNotInAllocatedSection(Section s, AllocationRequest ar) { 2265 for (AllocatedSection as : allocatedSections) { 2266 if (as.getSection() != s) { 2267 List<Block> blas = as.getSection().getBlockList(); 2268 // 2269 // When allocating the initial section for an Active Train, 2270 // we need not be concerned with any blocks in the initial section 2271 // which are unoccupied and to the rear of any occupied blocks in 2272 // the section as the train is not expected to enter those blocks. 2273 // When sections include the OS section these blocks prevented 2274 // allocation. 2275 // 2276 // The procedure is to remove those blocks (for the moment) from 2277 // the blocklist for the section during the initial allocation. 2278 // 2279 2280 List<Block> bls = new ArrayList<>(); 2281 if (ar != null && ar.getActiveTrain().getAllocatedSectionList().size() == 0) { 2282 int j; 2283 if (ar.getSectionDirection() == Section.FORWARD) { 2284 j = 0; 2285 for (int i = 0; i < s.getBlockList().size(); i++) { 2286 if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) { 2287 j = 1; 2288 } 2289 if (j == 1) { 2290 bls.add(s.getBlockList().get(i)); 2291 } 2292 } 2293 } else { 2294 j = 0; 2295 for (int i = s.getBlockList().size() - 1; i >= 0; i--) { 2296 if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) { 2297 j = 1; 2298 } 2299 if (j == 1) { 2300 bls.add(s.getBlockList().get(i)); 2301 } 2302 } 2303 } 2304 } else { 2305 bls = s.getBlockList(); 2306 // Add Blocks in any XCrossing, dont add ones already in the list 2307 for ( LevelXing lx: containedLevelXing(s)) { 2308 Block bAC = lx.getLayoutBlockAC().getBlock(); 2309 Block bBD = lx.getLayoutBlockBD().getBlock(); 2310 if (!bls.contains(bAC)) { 2311 bls.add(bAC); 2312 } 2313 if (!bls.contains(bBD)) { 2314 bls.add(bBD); 2315 } 2316 } 2317 } 2318 2319 for (Block b : bls) { 2320 if (blas.contains(b)) { 2321 if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) { 2322 // no clue where the tail is some must assume this block still in use. 2323 return as.getSection(); 2324 } 2325 if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADANDTAIL) { 2326 // if this is in the oldest section then we treat as whole train.. 2327 // if there is a section that exited but occupied the tail is there 2328 for (AllocatedSection tas : allocatedSections) { 2329 if (tas.getActiveTrain() == as.getActiveTrain() && tas.getExited() && tas.getSection().getOccupancy() == Section.OCCUPIED ) { 2330 return as.getSection(); 2331 } 2332 } 2333 } else if (as.getActiveTrain().getTrainDetection() != TrainDetection.TRAINDETECTION_WHOLETRAIN) { 2334 return as.getSection(); 2335 } 2336 if (as.getSection().getOccupancy() == Block.OCCUPIED) { 2337 //The next check looks to see if the block has already been passed or not and therefore ready for allocation. 2338 if (as.getSection().getState() == Section.FORWARD) { 2339 for (int i = 0; i < blas.size(); i++) { 2340 //The block we get to is occupied therefore the subsequent blocks have not been entered 2341 if (blas.get(i).getState() == Block.OCCUPIED) { 2342 if (ar != null) { 2343 ar.setWaitingOnBlock(b); 2344 } 2345 return as.getSection(); 2346 } else if (blas.get(i) == b) { 2347 break; 2348 } 2349 } 2350 } else { 2351 for (int i = blas.size() - 1; i >= 0; i--) { 2352 //The block we get to is occupied therefore the subsequent blocks have not been entered 2353 if (blas.get(i).getState() == Block.OCCUPIED) { 2354 if (ar != null) { 2355 ar.setWaitingOnBlock(b); 2356 } 2357 return as.getSection(); 2358 } else if (blas.get(i) == b) { 2359 break; 2360 } 2361 } 2362 } 2363 } else if (as.getSection().getOccupancy() != Section.FREE) { 2364 if (ar != null) { 2365 ar.setWaitingOnBlock(b); 2366 } 2367 return as.getSection(); 2368 } 2369 } 2370 } 2371 } 2372 } 2373 return null; 2374 } 2375 2376 // automatically make a choice of next section 2377 private Section autoChoice(List<Section> sList, AllocationRequest ar, int sectionSeqNo) { 2378 Section tSection = autoAllocate.autoNextSectionChoice(sList, ar, sectionSeqNo); 2379 if (tSection != null) { 2380 return tSection; 2381 } 2382 // if automatic choice failed, ask the dispatcher 2383 return dispatcherChoice(sList, ar); 2384 } 2385 2386 // manually make a choice of next section 2387 private Section dispatcherChoice(List<Section> sList, AllocationRequest ar) { 2388 Object choices[] = new Object[sList.size()]; 2389 for (int i = 0; i < sList.size(); i++) { 2390 Section s = sList.get(i); 2391 String txt = s.getDisplayName(); 2392 choices[i] = txt; 2393 } 2394 Object secName = JmriJOptionPane.showInputDialog(dispatcherFrame, 2395 Bundle.getMessage("ExplainChoice", ar.getSectionName()), 2396 Bundle.getMessage("ChoiceFrameTitle"), JmriJOptionPane 2397 .QUESTION_MESSAGE, null, choices, choices[0]); 2398 if (secName == null) { 2399 JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("WarnCancel")); 2400 return sList.get(0); 2401 } 2402 for (int j = 0; j < sList.size(); j++) { 2403 if (secName.equals(choices[j])) { 2404 return sList.get(j); 2405 } 2406 } 2407 return sList.get(0); 2408 } 2409 2410 // submit an AllocationRequest for the next Section of an ActiveTrain 2411 private void requestNextAllocation(ActiveTrain at) { 2412 // set up an Allocation Request 2413 Section next = at.getNextSectionToAllocate(); 2414 if (next == null) { 2415 return; 2416 } 2417 int seqNext = at.getNextSectionSeqNumber(); 2418 int dirNext = at.getAllocationDirectionFromSectionAndSeq(next, seqNext); 2419 requestAllocation(at, next, dirNext, seqNext, true, dispatcherFrame); 2420 } 2421 2422 /** 2423 * Check if any allocation requests need to be allocated, or if any 2424 * allocated sections need to be released 2425 */ 2426 protected void checkAutoRelease() { 2427 if (_AutoRelease) { 2428 // Auto release of exited sections has been requested - because of possible noise in block detection 2429 // hardware, allocated sections are automatically released in the order they were allocated only 2430 // Only unoccupied sections that have been exited are tested. 2431 // The next allocated section must be assigned to the same train, and it must have been entered for 2432 // the exited Section to be released. 2433 // Extra allocated sections are not automatically released (allocation number = -1). 2434 boolean foundOne = true; 2435 while ((allocatedSections.size() > 0) && foundOne) { 2436 try { 2437 foundOne = false; 2438 AllocatedSection as = null; 2439 for (int i = 0; (i < allocatedSections.size()) && !foundOne; i++) { 2440 as = allocatedSections.get(i); 2441 if (as.getExited() && (as.getSection().getOccupancy() != Section.OCCUPIED) 2442 && (as.getAllocationNumber() != -1)) { 2443 // possible candidate for deallocation - check order 2444 foundOne = true; 2445 for (int j = 0; (j < allocatedSections.size()) && foundOne; j++) { 2446 if (j != i) { 2447 AllocatedSection asx = allocatedSections.get(j); 2448 if ((asx.getActiveTrain() == as.getActiveTrain()) 2449 && (asx.getAllocationNumber() != -1) 2450 && (asx.getAllocationNumber() < as.getAllocationNumber())) { 2451 foundOne = false; 2452 } 2453 } 2454 } 2455 2456 // The train must have one occupied section. 2457 // The train may be sitting in one of its allocated section undetected. 2458 if ( foundOne && !hasTrainAnOccupiedSection(as.getActiveTrain())) { 2459 log.warn("[{}]:CheckAutoRelease release section [{}] failed, train has no occupied section", 2460 as.getActiveTrain().getActiveTrainName(),as.getSectionName()); 2461 foundOne = false; 2462 } 2463 2464 if (foundOne) { 2465 // check its not the last allocated section 2466 int allocatedCount = 0; 2467 for (int j = 0; (j < allocatedSections.size()); j++) { 2468 AllocatedSection asx = allocatedSections.get(j); 2469 if (asx.getActiveTrain() == as.getActiveTrain()) { 2470 allocatedCount++ ; 2471 } 2472 } 2473 if (allocatedCount == 1) { 2474 foundOne = false; 2475 } 2476 } 2477 if (foundOne) { 2478 // check if the next section is allocated to the same train and has been entered 2479 ActiveTrain at = as.getActiveTrain(); 2480 Section ns = as.getNextSection(); 2481 AllocatedSection nas = null; 2482 for (int k = 0; (k < allocatedSections.size()) && (nas == null); k++) { 2483 if (allocatedSections.get(k).getSection() == ns) { 2484 nas = allocatedSections.get(k); 2485 } 2486 } 2487 if ((nas == null) || (at.getStatus() == ActiveTrain.WORKING) 2488 || (at.getStatus() == ActiveTrain.STOPPED) 2489 || (at.getStatus() == ActiveTrain.READY) 2490 || (at.getMode() == ActiveTrain.MANUAL)) { 2491 // do not autorelease allocated sections from an Active Train that is 2492 // STOPPED, READY, or WORKING, or is in MANUAL mode. 2493 foundOne = false; 2494 //But do so if the active train has reached its restart point 2495 if (nas != null && at.reachedRestartPoint()) { 2496 foundOne = true; 2497 } 2498 } else { 2499 if ((nas.getActiveTrain() != as.getActiveTrain()) || (!nas.getEntered())) { 2500 foundOne = false; 2501 } 2502 } 2503 foundOne = sectionNotRequiredByHeadOnly(foundOne,at,as); 2504 if (foundOne) { 2505 log.debug("{}: releasing section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS)); 2506 doReleaseAllocatedSection(as, false); 2507 } 2508 } 2509 } 2510 } 2511 } catch (RuntimeException e) { 2512 log.warn("checkAutoRelease failed - maybe the AllocatedSection was removed due to a terminating train? {}", e.toString()); 2513 continue; 2514 } 2515 } 2516 } 2517 if (_AutoAllocate) { 2518 queueScanOfAllocationRequests(); 2519 } 2520 } 2521 2522 /* 2523 * Check whether the section is in use by a "Head Only" train and can be released. 2524 * calculate the length of exited sections, subtract the length of section 2525 * being released. If the train is moving do not include the length of the occupied section, 2526 * if the train is stationary and was stopped by sensor or speed profile include the length 2527 * of the occupied section. This is done as we dont know where the train is in the section block. 2528 */ 2529 private boolean sectionNotRequiredByHeadOnly(boolean foundOne, ActiveTrain at, AllocatedSection as) { 2530 if (at.getAutoActiveTrain() != null && at.getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) { 2531 long allocatedLengthMM = 0; 2532 for (AllocatedSection tas : at.getAllocatedSectionList()) { 2533 if (tas.getSection().getOccupancy() == Section.OCCUPIED) { 2534 if (at.getAutoActiveTrain().getAutoEngineer().isStopped() && 2535 (at.getAutoActiveTrain().getStopBySpeedProfile() || 2536 tas.getSection().getForwardStoppingSensor() != null || 2537 tas.getSection().getReverseStoppingSensor() != null)) { 2538 allocatedLengthMM += tas.getSection().getActualLength(); 2539 log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] including in length.", 2540 at.getTrainName(),tas.getSection().getDisplayName()); 2541 break; 2542 } else { 2543 log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] excluding from length.", 2544 at.getTrainName(),tas.getSection().getDisplayName()); 2545 break; 2546 } 2547 } 2548 if (tas.getExited()) { 2549 allocatedLengthMM += tas.getSection().getActualLength(); 2550 } 2551 } 2552 long trainLengthMM = at.getAutoActiveTrain().getMaxTrainLengthMM(); 2553 long releaseLengthMM = as.getSection().getActualLength(); 2554 log.debug("[{}]:Release Section [{}] by Length allocated [{}] release [{}] train [{}]", 2555 at.getTrainName(), as.getSectionName(), allocatedLengthMM, releaseLengthMM, trainLengthMM); 2556 if ((allocatedLengthMM - releaseLengthMM) < trainLengthMM) { 2557 return (false); 2558 } 2559 } 2560 return (true); 2561 } 2562 2563 /** 2564 * Releases an allocated Section, and removes it from the Dispatcher Input. 2565 * 2566 * @param as the section to release 2567 * @param terminatingTrain true if the associated train is being terminated; 2568 * false otherwise 2569 */ 2570 public void releaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) { 2571 // Unless the train is termination it must have one occupied section. 2572 // The train may be sitting in an allocated section undetected. 2573 if ( !terminatingTrain && !hasTrainAnOccupiedSection(as.getActiveTrain())) { 2574 log.warn("[{}]: releaseAllocatedSection release section [{}] failed train has no occupied section",as.getActiveTrain().getActiveTrainName(),as.getSectionName()); 2575 return; 2576 } 2577 if (_AutoAllocate ) { 2578 autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_ONE,as,terminatingTrain)); 2579 } else { 2580 doReleaseAllocatedSection( as, terminatingTrain); 2581 } 2582 } 2583 protected void doReleaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) { 2584 // check that section is not occupied if not terminating train 2585 if (!terminatingTrain && (as.getSection().getOccupancy() == Section.OCCUPIED)) { 2586 // warn the manual dispatcher that Allocated Section is occupied 2587 int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, java.text.MessageFormat.format( 2588 Bundle.getMessage("Question5"), new Object[]{as.getSectionName()}), Bundle.getMessage("WarningTitle"), 2589 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 2590 new Object[]{Bundle.getMessage("ButtonRelease"), Bundle.getMessage("ButtonNo")}, 2591 Bundle.getMessage("ButtonNo")); 2592 if (selectedValue != 0 ) { // array position 0, release not pressed 2593 return; // return without releasing if "No" or "Cancel" response 2594 } 2595 } 2596 // release the Allocated Section 2597 for (int i = allocatedSections.size(); i > 0; i--) { 2598 if (as == allocatedSections.get(i - 1)) { 2599 allocatedSections.remove(i - 1); 2600 } 2601 } 2602 as.getSection().setState(Section.FREE); 2603 as.getActiveTrain().removeAllocatedSection(as); 2604 as.dispose(); 2605 if (allocatedSectionTableModel != null) { 2606 allocatedSectionTableModel.fireTableDataChanged(); 2607 } 2608 allocationRequestTableModel.fireTableDataChanged(); 2609 activeTrainsTableModel.fireTableDataChanged(); 2610 if (_AutoAllocate) { 2611 queueScanOfAllocationRequests(); 2612 } 2613 } 2614 2615 /** 2616 * Updates display when occupancy of an allocated section changes Also 2617 * drives auto release if it is selected 2618 */ 2619 public void sectionOccupancyChanged() { 2620 queueReleaseOfCompletedAllocations(); 2621 if (allocatedSectionTableModel != null) { 2622 allocatedSectionTableModel.fireTableDataChanged(); 2623 } 2624 allocationRequestTableModel.fireTableDataChanged(); 2625 } 2626 2627 /** 2628 * Handle activity that is triggered by the fast clock 2629 */ 2630 protected void newFastClockMinute() { 2631 for (int i = delayedTrains.size() - 1; i >= 0; i--) { 2632 ActiveTrain at = delayedTrains.get(i); 2633 // check if this Active Train is waiting to start 2634 if ((!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) { 2635 // is it time to start? 2636 if (at.getDelayedStart() == ActiveTrain.TIMEDDELAY) { 2637 if (isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin())) { 2638 // allow this train to start 2639 at.setStarted(); 2640 delayedTrains.remove(i); 2641 } 2642 } 2643 } else if (at.getStarted() && at.getStatus() == ActiveTrain.READY && at.reachedRestartPoint()) { 2644 if (isFastClockTimeGE(at.getRestartDepartHr(), at.getRestartDepartMin())) { 2645 at.restart(); 2646 delayedTrains.remove(i); 2647 } 2648 } 2649 } 2650 if (_AutoAllocate) { 2651 queueScanOfAllocationRequests(); 2652 } 2653 } 2654 2655 /** 2656 * This method tests time 2657 * 2658 * @param hr the hour to test against (0-23) 2659 * @param min the minute to test against (0-59) 2660 * @return true if fast clock time and tested time are the same 2661 */ 2662 public boolean isFastClockTimeGE(int hr, int min) { 2663 Calendar now = Calendar.getInstance(); 2664 now.setTime(fastClock.getTime()); 2665 int nowHours = now.get(Calendar.HOUR_OF_DAY); 2666 int nowMinutes = now.get(Calendar.MINUTE); 2667 return ((nowHours * 60) + nowMinutes) == ((hr * 60) + min); 2668 } 2669 2670 // option access methods 2671 protected LayoutEditor getLayoutEditor() { 2672 return _LE; 2673 } 2674 2675 protected void setLayoutEditor(LayoutEditor editor) { 2676 _LE = editor; 2677 } 2678 2679 protected boolean getUseConnectivity() { 2680 return _UseConnectivity; 2681 } 2682 2683 protected void setUseConnectivity(boolean set) { 2684 _UseConnectivity = set; 2685 } 2686 2687 protected void setSignalType(int type) { 2688 _SignalType = type; 2689 } 2690 2691 protected int getSignalType() { 2692 return _SignalType; 2693 } 2694 2695 protected String getSignalTypeString() { 2696 switch (_SignalType) { 2697 case SIGNALHEAD: 2698 return Bundle.getMessage("SignalType1"); 2699 case SIGNALMAST: 2700 return Bundle.getMessage("SignalType2"); 2701 case SECTIONSALLOCATED: 2702 return Bundle.getMessage("SignalType3"); 2703 default: 2704 return "Unknown"; 2705 } 2706 } 2707 2708 protected void setStoppingSpeedName(String speedName) { 2709 _StoppingSpeedName = speedName; 2710 } 2711 2712 protected String getStoppingSpeedName() { 2713 return _StoppingSpeedName; 2714 } 2715 2716 protected float getMaximumLineSpeed() { 2717 return maximumLineSpeed; 2718 } 2719 2720 protected void setTrainsFrom(TrainsFrom value ) { 2721 _TrainsFrom = value; 2722 } 2723 2724 protected TrainsFrom getTrainsFrom() { 2725 return _TrainsFrom; 2726 } 2727 2728 protected boolean getAutoAllocate() { 2729 return _AutoAllocate; 2730 } 2731 2732 protected boolean getAutoRelease() { 2733 return _AutoRelease; 2734 } 2735 2736 protected void stopStartAutoAllocateRelease() { 2737 if (_AutoAllocate || _AutoRelease) { 2738 if (editorManager.getAll(LayoutEditor.class).size() > 0) { 2739 if (autoAllocate == null) { 2740 autoAllocate = new AutoAllocate(this,allocationRequests); 2741 autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator "); 2742 autoAllocateThread.start(); 2743 } 2744 } else { 2745 JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Error39"), 2746 Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 2747 _AutoAllocate = false; 2748 if (autoAllocateBox != null) { 2749 autoAllocateBox.setSelected(_AutoAllocate); 2750 } 2751 return; 2752 } 2753 } else { 2754 //no need for autoallocateRelease 2755 if (autoAllocate != null) { 2756 autoAllocate.setAbort(); 2757 autoAllocate = null; 2758 } 2759 } 2760 2761 } 2762 protected void setAutoAllocate(boolean set) { 2763 _AutoAllocate = set; 2764 stopStartAutoAllocateRelease(); 2765 if (autoAllocateBox != null) { 2766 autoAllocateBox.setSelected(_AutoAllocate); 2767 } 2768 } 2769 2770 protected void setAutoRelease(boolean set) { 2771 _AutoRelease = set; 2772 stopStartAutoAllocateRelease(); 2773 if (autoReleaseBox != null) { 2774 autoReleaseBox.setSelected(_AutoAllocate); 2775 } 2776 } 2777 2778 protected AutoTurnouts getAutoTurnoutsHelper () { 2779 return autoTurnouts; 2780 } 2781 2782 protected boolean getAutoTurnouts() { 2783 return _AutoTurnouts; 2784 } 2785 2786 protected void setAutoTurnouts(boolean set) { 2787 _AutoTurnouts = set; 2788 } 2789 2790 protected boolean getTrustKnownTurnouts() { 2791 return _TrustKnownTurnouts; 2792 } 2793 2794 protected void setTrustKnownTurnouts(boolean set) { 2795 _TrustKnownTurnouts = set; 2796 } 2797 2798 protected int getMinThrottleInterval() { 2799 return _MinThrottleInterval; 2800 } 2801 2802 protected void setMinThrottleInterval(int set) { 2803 _MinThrottleInterval = set; 2804 } 2805 2806 protected int getFullRampTime() { 2807 return _FullRampTime; 2808 } 2809 2810 protected void setFullRampTime(int set) { 2811 _FullRampTime = set; 2812 } 2813 2814 protected boolean getHasOccupancyDetection() { 2815 return _HasOccupancyDetection; 2816 } 2817 2818 protected void setHasOccupancyDetection(boolean set) { 2819 _HasOccupancyDetection = set; 2820 } 2821 2822 protected boolean getSetSSLDirectionalSensors() { 2823 return _SetSSLDirectionalSensors; 2824 } 2825 2826 protected void setSetSSLDirectionalSensors(boolean set) { 2827 _SetSSLDirectionalSensors = set; 2828 } 2829 2830 protected boolean getUseScaleMeters() { 2831 return _UseScaleMeters; 2832 } 2833 2834 protected void setUseScaleMeters(boolean set) { 2835 _UseScaleMeters = set; 2836 } 2837 2838 protected boolean getShortActiveTrainNames() { 2839 return _ShortActiveTrainNames; 2840 } 2841 2842 protected void setShortActiveTrainNames(boolean set) { 2843 _ShortActiveTrainNames = set; 2844 if (allocatedSectionTableModel != null) { 2845 allocatedSectionTableModel.fireTableDataChanged(); 2846 } 2847 if (allocationRequestTableModel != null) { 2848 allocationRequestTableModel.fireTableDataChanged(); 2849 } 2850 } 2851 2852 protected boolean getShortNameInBlock() { 2853 return _ShortNameInBlock; 2854 } 2855 2856 protected void setShortNameInBlock(boolean set) { 2857 _ShortNameInBlock = set; 2858 } 2859 2860 protected boolean getRosterEntryInBlock() { 2861 return _RosterEntryInBlock; 2862 } 2863 2864 protected void setRosterEntryInBlock(boolean set) { 2865 _RosterEntryInBlock = set; 2866 } 2867 2868 protected boolean getExtraColorForAllocated() { 2869 return _ExtraColorForAllocated; 2870 } 2871 2872 protected void setExtraColorForAllocated(boolean set) { 2873 _ExtraColorForAllocated = set; 2874 } 2875 2876 protected boolean getNameInAllocatedBlock() { 2877 return _NameInAllocatedBlock; 2878 } 2879 2880 protected void setNameInAllocatedBlock(boolean set) { 2881 _NameInAllocatedBlock = set; 2882 } 2883 2884 protected Scale getScale() { 2885 return _LayoutScale; 2886 } 2887 2888 protected void setScale(Scale sc) { 2889 _LayoutScale = sc; 2890 } 2891 2892 public List<ActiveTrain> getActiveTrainsList() { 2893 return activeTrainsList; 2894 } 2895 2896 protected List<AllocatedSection> getAllocatedSectionsList() { 2897 return allocatedSections; 2898 } 2899 2900 public ActiveTrain getActiveTrainForRoster(RosterEntry re) { 2901 if ( _TrainsFrom != TrainsFrom.TRAINSFROMROSTER) { 2902 return null; 2903 } 2904 for (ActiveTrain at : activeTrainsList) { 2905 if (at.getRosterEntry().equals(re)) { 2906 return at; 2907 } 2908 } 2909 return null; 2910 2911 } 2912 2913 protected boolean getSupportVSDecoder() { 2914 return _SupportVSDecoder; 2915 } 2916 2917 protected void setSupportVSDecoder(boolean set) { 2918 _SupportVSDecoder = set; 2919 } 2920 2921 // called by ActivateTrainFrame after a new train is all set up 2922 // Dispatcher side of activating a new train should be completed here 2923 // Jay Janzen protection changed to public for access via scripting 2924 public void newTrainDone(ActiveTrain at) { 2925 if (at != null) { 2926 // a new active train was created, check for delayed start 2927 if (at.getDelayedStart() != ActiveTrain.NODELAY && (!at.getStarted())) { 2928 delayedTrains.add(at); 2929 fastClockWarn(true); 2930 } // djd needs work here 2931 // check for delayed restart 2932 else if (at.getDelayedRestart() == ActiveTrain.TIMEDDELAY) { 2933 fastClockWarn(false); 2934 } 2935 } 2936 if (atFrame != null) { 2937 atFrame.setVisible(false); 2938 atFrame.dispose(); 2939 atFrame = null; 2940 } 2941 newTrainActive = false; 2942 } 2943 2944 protected void removeDelayedTrain(ActiveTrain at) { 2945 delayedTrains.remove(at); 2946 } 2947 2948 private void fastClockWarn(boolean wMess) { 2949 if (fastClockSensor.getState() == Sensor.ACTIVE) { 2950 return; 2951 } 2952 // warn that the fast clock is not running 2953 String mess = ""; 2954 if (wMess) { 2955 mess = Bundle.getMessage("FastClockWarn"); 2956 } else { 2957 mess = Bundle.getMessage("FastClockWarn2"); 2958 } 2959 int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, 2960 mess, Bundle.getMessage("WarningTitle"), 2961 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 2962 new Object[]{Bundle.getMessage("ButtonYesStart"), Bundle.getMessage("ButtonNo")}, 2963 Bundle.getMessage("ButtonNo")); 2964 if (selectedValue == 0) { 2965 try { 2966 fastClockSensor.setState(Sensor.ACTIVE); 2967 } catch (jmri.JmriException reason) { 2968 log.error("Exception when setting fast clock sensor"); 2969 } 2970 } 2971 } 2972 2973 // Jay Janzen 2974 // Protection changed to public to allow access via scripting 2975 public AutoTrainsFrame getAutoTrainsFrame() { 2976 return _autoTrainsFrame; 2977 } 2978 2979 /** 2980 * Table model for Active Trains Table in Dispatcher window 2981 */ 2982 public class ActiveTrainsTableModel extends javax.swing.table.AbstractTableModel implements 2983 java.beans.PropertyChangeListener { 2984 2985 public static final int TRANSIT_COLUMN = 0; 2986 public static final int TRANSIT_COLUMN_U = 1; 2987 public static final int TRAIN_COLUMN = 2; 2988 public static final int TYPE_COLUMN = 3; 2989 public static final int STATUS_COLUMN = 4; 2990 public static final int MODE_COLUMN = 5; 2991 public static final int ALLOCATED_COLUMN = 6; 2992 public static final int ALLOCATED_COLUMN_U = 7; 2993 public static final int NEXTSECTION_COLUMN = 8; 2994 public static final int NEXTSECTION_COLUMN_U = 9; 2995 public static final int ALLOCATEBUTTON_COLUMN = 10; 2996 public static final int TERMINATEBUTTON_COLUMN = 11; 2997 public static final int RESTARTCHECKBOX_COLUMN = 12; 2998 public static final int ISAUTO_COLUMN = 13; 2999 public static final int CURRENTSIGNAL_COLUMN = 14; 3000 public static final int CURRENTSIGNAL_COLUMN_U = 15; 3001 public static final int DCC_ADDRESS = 16; 3002 public static final int MAX_COLUMN = 16; 3003 public ActiveTrainsTableModel() { 3004 super(); 3005 } 3006 3007 @Override 3008 public void propertyChange(java.beans.PropertyChangeEvent e) { 3009 if (e.getPropertyName().equals("length")) { 3010 fireTableDataChanged(); 3011 } 3012 } 3013 3014 @Override 3015 public Class<?> getColumnClass(int col) { 3016 switch (col) { 3017 case ALLOCATEBUTTON_COLUMN: 3018 case TERMINATEBUTTON_COLUMN: 3019 return JButton.class; 3020 case RESTARTCHECKBOX_COLUMN: 3021 case ISAUTO_COLUMN: 3022 return Boolean.class; 3023 default: 3024 return String.class; 3025 } 3026 } 3027 3028 @Override 3029 public int getColumnCount() { 3030 return MAX_COLUMN + 1; 3031 } 3032 3033 @Override 3034 public int getRowCount() { 3035 return (activeTrainsList.size()); 3036 } 3037 3038 @Override 3039 public boolean isCellEditable(int row, int col) { 3040 switch (col) { 3041 case ALLOCATEBUTTON_COLUMN: 3042 case TERMINATEBUTTON_COLUMN: 3043 case RESTARTCHECKBOX_COLUMN: 3044 return (true); 3045 default: 3046 return (false); 3047 } 3048 } 3049 3050 @Override 3051 public String getColumnName(int col) { 3052 switch (col) { 3053 case TRANSIT_COLUMN: 3054 return Bundle.getMessage("TransitColumnSysTitle"); 3055 case TRANSIT_COLUMN_U: 3056 return Bundle.getMessage("TransitColumnTitle"); 3057 case TRAIN_COLUMN: 3058 return Bundle.getMessage("TrainColumnTitle"); 3059 case TYPE_COLUMN: 3060 return Bundle.getMessage("TrainTypeColumnTitle"); 3061 case STATUS_COLUMN: 3062 return Bundle.getMessage("TrainStatusColumnTitle"); 3063 case MODE_COLUMN: 3064 return Bundle.getMessage("TrainModeColumnTitle"); 3065 case ALLOCATED_COLUMN: 3066 return Bundle.getMessage("AllocatedSectionColumnSysTitle"); 3067 case ALLOCATED_COLUMN_U: 3068 return Bundle.getMessage("AllocatedSectionColumnTitle"); 3069 case NEXTSECTION_COLUMN: 3070 return Bundle.getMessage("NextSectionColumnSysTitle"); 3071 case NEXTSECTION_COLUMN_U: 3072 return Bundle.getMessage("NextSectionColumnTitle"); 3073 case RESTARTCHECKBOX_COLUMN: 3074 return(Bundle.getMessage("AutoRestartColumnTitle")); 3075 case ALLOCATEBUTTON_COLUMN: 3076 return(Bundle.getMessage("AllocateButton")); 3077 case TERMINATEBUTTON_COLUMN: 3078 return(Bundle.getMessage("TerminateTrain")); 3079 case ISAUTO_COLUMN: 3080 return(Bundle.getMessage("AutoColumnTitle")); 3081 case CURRENTSIGNAL_COLUMN: 3082 return(Bundle.getMessage("CurrentSignalSysColumnTitle")); 3083 case CURRENTSIGNAL_COLUMN_U: 3084 return(Bundle.getMessage("CurrentSignalColumnTitle")); 3085 case DCC_ADDRESS: 3086 return(Bundle.getMessage("DccColumnTitleColumnTitle")); 3087 default: 3088 return ""; 3089 } 3090 } 3091 3092 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", 3093 justification="better to keep cases in column order rather than to combine") 3094 public int getPreferredWidth(int col) { 3095 switch (col) { 3096 case TRANSIT_COLUMN: 3097 case TRANSIT_COLUMN_U: 3098 case TRAIN_COLUMN: 3099 return new JTextField(17).getPreferredSize().width; 3100 case TYPE_COLUMN: 3101 return new JTextField(16).getPreferredSize().width; 3102 case STATUS_COLUMN: 3103 return new JTextField(8).getPreferredSize().width; 3104 case MODE_COLUMN: 3105 return new JTextField(11).getPreferredSize().width; 3106 case ALLOCATED_COLUMN: 3107 case ALLOCATED_COLUMN_U: 3108 return new JTextField(17).getPreferredSize().width; 3109 case NEXTSECTION_COLUMN: 3110 case NEXTSECTION_COLUMN_U: 3111 return new JTextField(17).getPreferredSize().width; 3112 case ALLOCATEBUTTON_COLUMN: 3113 case TERMINATEBUTTON_COLUMN: 3114 case RESTARTCHECKBOX_COLUMN: 3115 case ISAUTO_COLUMN: 3116 case CURRENTSIGNAL_COLUMN: 3117 case CURRENTSIGNAL_COLUMN_U: 3118 case DCC_ADDRESS: 3119 return new JTextField(5).getPreferredSize().width; 3120 default: 3121 // fall through 3122 break; 3123 } 3124 return new JTextField(5).getPreferredSize().width; 3125 } 3126 3127 @Override 3128 public Object getValueAt(int r, int c) { 3129 int rx = r; 3130 if (rx >= activeTrainsList.size()) { 3131 return null; 3132 } 3133 ActiveTrain at = activeTrainsList.get(rx); 3134 switch (c) { 3135 case TRANSIT_COLUMN: 3136 return (at.getTransit().getSystemName()); 3137 case TRANSIT_COLUMN_U: 3138 if (at.getTransit() != null && at.getTransit().getUserName() != null) { 3139 return (at.getTransit().getUserName()); 3140 } else { 3141 return ""; 3142 } 3143 case TRAIN_COLUMN: 3144 return (at.getTrainName()); 3145 case TYPE_COLUMN: 3146 return (at.getTrainTypeText()); 3147 case STATUS_COLUMN: 3148 return (at.getStatusText()); 3149 case MODE_COLUMN: 3150 return (at.getModeText()); 3151 case ALLOCATED_COLUMN: 3152 if (at.getLastAllocatedSection() != null) { 3153 return (at.getLastAllocatedSection().getSystemName()); 3154 } else { 3155 return "<none>"; 3156 } 3157 case ALLOCATED_COLUMN_U: 3158 if (at.getLastAllocatedSection() != null && at.getLastAllocatedSection().getUserName() != null) { 3159 return (at.getLastAllocatedSection().getUserName()); 3160 } else { 3161 return "<none>"; 3162 } 3163 case NEXTSECTION_COLUMN: 3164 if (at.getNextSectionToAllocate() != null) { 3165 return (at.getNextSectionToAllocate().getSystemName()); 3166 } else { 3167 return "<none>"; 3168 } 3169 case NEXTSECTION_COLUMN_U: 3170 if (at.getNextSectionToAllocate() != null && at.getNextSectionToAllocate().getUserName() != null) { 3171 return (at.getNextSectionToAllocate().getUserName()); 3172 } else { 3173 return "<none>"; 3174 } 3175 case ALLOCATEBUTTON_COLUMN: 3176 return Bundle.getMessage("AllocateButtonName"); 3177 case TERMINATEBUTTON_COLUMN: 3178 return Bundle.getMessage("TerminateTrain"); 3179 case RESTARTCHECKBOX_COLUMN: 3180 return at.getResetWhenDone(); 3181 case ISAUTO_COLUMN: 3182 return at.getAutoRun(); 3183 case CURRENTSIGNAL_COLUMN: 3184 if (at.getAutoRun()) { 3185 return(at.getAutoActiveTrain().getCurrentSignal()); 3186 } else { 3187 return("NA"); 3188 } 3189 case CURRENTSIGNAL_COLUMN_U: 3190 if (at.getAutoRun()) { 3191 return(at.getAutoActiveTrain().getCurrentSignalUserName()); 3192 } else { 3193 return("NA"); 3194 } 3195 case DCC_ADDRESS: 3196 if (at.getDccAddress() != null) { 3197 return(at.getDccAddress()); 3198 } else { 3199 return("NA"); 3200 } 3201 default: 3202 return (" "); 3203 } 3204 } 3205 3206 @Override 3207 public void setValueAt(Object value, int row, int col) { 3208 if (col == ALLOCATEBUTTON_COLUMN) { 3209 // open an allocate window 3210 allocateNextRequested(row); 3211 } 3212 if (col == TERMINATEBUTTON_COLUMN) { 3213 if (activeTrainsList.get(row) != null) { 3214 terminateActiveTrain(activeTrainsList.get(row),true,false); 3215 } 3216 } 3217 if (col == RESTARTCHECKBOX_COLUMN) { 3218 ActiveTrain at = null; 3219 at = activeTrainsList.get(row); 3220 if (activeTrainsList.get(row) != null) { 3221 if (!at.getResetWhenDone()) { 3222 at.setResetWhenDone(true); 3223 return; 3224 } 3225 at.setResetWhenDone(false); 3226 for (int j = restartingTrainsList.size(); j > 0; j--) { 3227 if (restartingTrainsList.get(j - 1) == at) { 3228 restartingTrainsList.remove(j - 1); 3229 return; 3230 } 3231 } 3232 } 3233 } 3234 } 3235 } 3236 3237 /** 3238 * Table model for Allocation Request Table in Dispatcher window 3239 */ 3240 public class AllocationRequestTableModel extends javax.swing.table.AbstractTableModel implements 3241 java.beans.PropertyChangeListener { 3242 3243 public static final int TRANSIT_COLUMN = 0; 3244 public static final int TRANSIT_COLUMN_U = 1; 3245 public static final int TRAIN_COLUMN = 2; 3246 public static final int PRIORITY_COLUMN = 3; 3247 public static final int TRAINTYPE_COLUMN = 4; 3248 public static final int SECTION_COLUMN = 5; 3249 public static final int SECTION_COLUMN_U = 6; 3250 public static final int STATUS_COLUMN = 7; 3251 public static final int OCCUPANCY_COLUMN = 8; 3252 public static final int SECTIONLENGTH_COLUMN = 9; 3253 public static final int ALLOCATEBUTTON_COLUMN = 10; 3254 public static final int CANCELBUTTON_COLUMN = 11; 3255 public static final int MAX_COLUMN = 11; 3256 3257 public AllocationRequestTableModel() { 3258 super(); 3259 } 3260 3261 @Override 3262 public void propertyChange(java.beans.PropertyChangeEvent e) { 3263 if (e.getPropertyName().equals("length")) { 3264 fireTableDataChanged(); 3265 } 3266 } 3267 3268 @Override 3269 public Class<?> getColumnClass(int c) { 3270 if (c == CANCELBUTTON_COLUMN) { 3271 return JButton.class; 3272 } 3273 if (c == ALLOCATEBUTTON_COLUMN) { 3274 return JButton.class; 3275 } 3276 //if (c == CANCELRESTART_COLUMN) { 3277 // return JButton.class; 3278 //} 3279 return String.class; 3280 } 3281 3282 @Override 3283 public int getColumnCount() { 3284 return MAX_COLUMN + 1; 3285 } 3286 3287 @Override 3288 public int getRowCount() { 3289 return (allocationRequests.size()); 3290 } 3291 3292 @Override 3293 public boolean isCellEditable(int r, int c) { 3294 if (c == CANCELBUTTON_COLUMN) { 3295 return (true); 3296 } 3297 if (c == ALLOCATEBUTTON_COLUMN) { 3298 return (true); 3299 } 3300 return (false); 3301 } 3302 3303 @Override 3304 public String getColumnName(int col) { 3305 switch (col) { 3306 case TRANSIT_COLUMN: 3307 return Bundle.getMessage("TransitColumnSysTitle"); 3308 case TRANSIT_COLUMN_U: 3309 return Bundle.getMessage("TransitColumnTitle"); 3310 case TRAIN_COLUMN: 3311 return Bundle.getMessage("TrainColumnTitle"); 3312 case PRIORITY_COLUMN: 3313 return Bundle.getMessage("PriorityLabel"); 3314 case TRAINTYPE_COLUMN: 3315 return Bundle.getMessage("TrainTypeColumnTitle"); 3316 case SECTION_COLUMN: 3317 return Bundle.getMessage("SectionColumnSysTitle"); 3318 case SECTION_COLUMN_U: 3319 return Bundle.getMessage("SectionColumnTitle"); 3320 case STATUS_COLUMN: 3321 return Bundle.getMessage("StatusColumnTitle"); 3322 case OCCUPANCY_COLUMN: 3323 return Bundle.getMessage("OccupancyColumnTitle"); 3324 case SECTIONLENGTH_COLUMN: 3325 return Bundle.getMessage("SectionLengthColumnTitle"); 3326 case ALLOCATEBUTTON_COLUMN: 3327 return Bundle.getMessage("AllocateButton"); 3328 case CANCELBUTTON_COLUMN: 3329 return Bundle.getMessage("ButtonCancel"); 3330 default: 3331 return ""; 3332 } 3333 } 3334 3335 public int getPreferredWidth(int col) { 3336 switch (col) { 3337 case TRANSIT_COLUMN: 3338 case TRANSIT_COLUMN_U: 3339 case TRAIN_COLUMN: 3340 return new JTextField(17).getPreferredSize().width; 3341 case PRIORITY_COLUMN: 3342 return new JTextField(8).getPreferredSize().width; 3343 case TRAINTYPE_COLUMN: 3344 return new JTextField(15).getPreferredSize().width; 3345 case SECTION_COLUMN: 3346 return new JTextField(25).getPreferredSize().width; 3347 case STATUS_COLUMN: 3348 return new JTextField(15).getPreferredSize().width; 3349 case OCCUPANCY_COLUMN: 3350 return new JTextField(10).getPreferredSize().width; 3351 case SECTIONLENGTH_COLUMN: 3352 return new JTextField(8).getPreferredSize().width; 3353 case ALLOCATEBUTTON_COLUMN: 3354 return new JTextField(12).getPreferredSize().width; 3355 case CANCELBUTTON_COLUMN: 3356 return new JTextField(10).getPreferredSize().width; 3357 default: 3358 // fall through 3359 break; 3360 } 3361 return new JTextField(5).getPreferredSize().width; 3362 } 3363 3364 @Override 3365 public Object getValueAt(int r, int c) { 3366 int rx = r; 3367 if (rx >= allocationRequests.size()) { 3368 return null; 3369 } 3370 AllocationRequest ar = allocationRequests.get(rx); 3371 switch (c) { 3372 case TRANSIT_COLUMN: 3373 return (ar.getActiveTrain().getTransit().getSystemName()); 3374 case TRANSIT_COLUMN_U: 3375 if (ar.getActiveTrain().getTransit() != null && ar.getActiveTrain().getTransit().getUserName() != null) { 3376 return (ar.getActiveTrain().getTransit().getUserName()); 3377 } else { 3378 return ""; 3379 } 3380 case TRAIN_COLUMN: 3381 return (ar.getActiveTrain().getTrainName()); 3382 case PRIORITY_COLUMN: 3383 return (" " + ar.getActiveTrain().getPriority()); 3384 case TRAINTYPE_COLUMN: 3385 return (ar.getActiveTrain().getTrainTypeText()); 3386 case SECTION_COLUMN: 3387 if (ar.getSection() != null) { 3388 return (ar.getSection().getSystemName()); 3389 } else { 3390 return "<none>"; 3391 } 3392 case SECTION_COLUMN_U: 3393 if (ar.getSection() != null && ar.getSection().getUserName() != null) { 3394 return (ar.getSection().getUserName()); 3395 } else { 3396 return "<none>"; 3397 } 3398 case STATUS_COLUMN: 3399 if (ar.getSection().getState() == Section.FREE) { 3400 return Bundle.getMessage("FREE"); 3401 } 3402 return Bundle.getMessage("ALLOCATED"); 3403 case OCCUPANCY_COLUMN: 3404 if (!_HasOccupancyDetection) { 3405 return Bundle.getMessage("UNKNOWN"); 3406 } 3407 if (ar.getSection().getOccupancy() == Section.OCCUPIED) { 3408 return Bundle.getMessage("OCCUPIED"); 3409 } 3410 return Bundle.getMessage("UNOCCUPIED"); 3411 case SECTIONLENGTH_COLUMN: 3412 return (" " + ar.getSection().getLengthI(_UseScaleMeters, _LayoutScale)); 3413 case ALLOCATEBUTTON_COLUMN: 3414 return Bundle.getMessage("AllocateButton"); 3415 case CANCELBUTTON_COLUMN: 3416 return Bundle.getMessage("ButtonCancel"); 3417 default: 3418 return (" "); 3419 } 3420 } 3421 3422 @Override 3423 public void setValueAt(Object value, int row, int col) { 3424 if (col == ALLOCATEBUTTON_COLUMN) { 3425 // open an allocate window 3426 allocateRequested(row); 3427 } 3428 if (col == CANCELBUTTON_COLUMN) { 3429 // open an allocate window 3430 cancelAllocationRequest(row); 3431 } 3432 } 3433 } 3434 3435 /** 3436 * Table model for Allocated Section Table 3437 */ 3438 public class AllocatedSectionTableModel extends javax.swing.table.AbstractTableModel implements 3439 java.beans.PropertyChangeListener { 3440 3441 public static final int TRANSIT_COLUMN = 0; 3442 public static final int TRANSIT_COLUMN_U = 1; 3443 public static final int TRAIN_COLUMN = 2; 3444 public static final int SECTION_COLUMN = 3; 3445 public static final int SECTION_COLUMN_U = 4; 3446 public static final int OCCUPANCY_COLUMN = 5; 3447 public static final int USESTATUS_COLUMN = 6; 3448 public static final int RELEASEBUTTON_COLUMN = 7; 3449 public static final int MAX_COLUMN = 7; 3450 3451 public AllocatedSectionTableModel() { 3452 super(); 3453 } 3454 3455 @Override 3456 public void propertyChange(java.beans.PropertyChangeEvent e) { 3457 if (e.getPropertyName().equals("length")) { 3458 fireTableDataChanged(); 3459 } 3460 } 3461 3462 @Override 3463 public Class<?> getColumnClass(int c) { 3464 if (c == RELEASEBUTTON_COLUMN) { 3465 return JButton.class; 3466 } 3467 return String.class; 3468 } 3469 3470 @Override 3471 public int getColumnCount() { 3472 return MAX_COLUMN + 1; 3473 } 3474 3475 @Override 3476 public int getRowCount() { 3477 return (allocatedSections.size()); 3478 } 3479 3480 @Override 3481 public boolean isCellEditable(int r, int c) { 3482 if (c == RELEASEBUTTON_COLUMN) { 3483 return (true); 3484 } 3485 return (false); 3486 } 3487 3488 @Override 3489 public String getColumnName(int col) { 3490 switch (col) { 3491 case TRANSIT_COLUMN: 3492 return Bundle.getMessage("TransitColumnSysTitle"); 3493 case TRANSIT_COLUMN_U: 3494 return Bundle.getMessage("TransitColumnTitle"); 3495 case TRAIN_COLUMN: 3496 return Bundle.getMessage("TrainColumnTitle"); 3497 case SECTION_COLUMN: 3498 return Bundle.getMessage("AllocatedSectionColumnSysTitle"); 3499 case SECTION_COLUMN_U: 3500 return Bundle.getMessage("AllocatedSectionColumnTitle"); 3501 case OCCUPANCY_COLUMN: 3502 return Bundle.getMessage("OccupancyColumnTitle"); 3503 case USESTATUS_COLUMN: 3504 return Bundle.getMessage("UseStatusColumnTitle"); 3505 case RELEASEBUTTON_COLUMN: 3506 return Bundle.getMessage("ReleaseButton"); 3507 default: 3508 return ""; 3509 } 3510 } 3511 3512 public int getPreferredWidth(int col) { 3513 switch (col) { 3514 case TRANSIT_COLUMN: 3515 case TRANSIT_COLUMN_U: 3516 case TRAIN_COLUMN: 3517 return new JTextField(17).getPreferredSize().width; 3518 case SECTION_COLUMN: 3519 case SECTION_COLUMN_U: 3520 return new JTextField(25).getPreferredSize().width; 3521 case OCCUPANCY_COLUMN: 3522 return new JTextField(10).getPreferredSize().width; 3523 case USESTATUS_COLUMN: 3524 return new JTextField(15).getPreferredSize().width; 3525 case RELEASEBUTTON_COLUMN: 3526 return new JTextField(12).getPreferredSize().width; 3527 default: 3528 // fall through 3529 break; 3530 } 3531 return new JTextField(5).getPreferredSize().width; 3532 } 3533 3534 @Override 3535 public Object getValueAt(int r, int c) { 3536 int rx = r; 3537 if (rx >= allocatedSections.size()) { 3538 return null; 3539 } 3540 AllocatedSection as = allocatedSections.get(rx); 3541 switch (c) { 3542 case TRANSIT_COLUMN: 3543 return (as.getActiveTrain().getTransit().getSystemName()); 3544 case TRANSIT_COLUMN_U: 3545 if (as.getActiveTrain().getTransit() != null && as.getActiveTrain().getTransit().getUserName() != null) { 3546 return (as.getActiveTrain().getTransit().getUserName()); 3547 } else { 3548 return ""; 3549 } 3550 case TRAIN_COLUMN: 3551 return (as.getActiveTrain().getTrainName()); 3552 case SECTION_COLUMN: 3553 if (as.getSection() != null) { 3554 return (as.getSection().getSystemName()); 3555 } else { 3556 return "<none>"; 3557 } 3558 case SECTION_COLUMN_U: 3559 if (as.getSection() != null && as.getSection().getUserName() != null) { 3560 return (as.getSection().getUserName()); 3561 } else { 3562 return "<none>"; 3563 } 3564 case OCCUPANCY_COLUMN: 3565 if (!_HasOccupancyDetection) { 3566 return Bundle.getMessage("UNKNOWN"); 3567 } 3568 if (as.getSection().getOccupancy() == Section.OCCUPIED) { 3569 return Bundle.getMessage("OCCUPIED"); 3570 } 3571 return Bundle.getMessage("UNOCCUPIED"); 3572 case USESTATUS_COLUMN: 3573 if (!as.getEntered()) { 3574 return Bundle.getMessage("NotEntered"); 3575 } 3576 if (as.getExited()) { 3577 return Bundle.getMessage("Exited"); 3578 } 3579 return Bundle.getMessage("Entered"); 3580 case RELEASEBUTTON_COLUMN: 3581 return Bundle.getMessage("ReleaseButton"); 3582 default: 3583 return (" "); 3584 } 3585 } 3586 3587 @Override 3588 public void setValueAt(Object value, int row, int col) { 3589 if (col == RELEASEBUTTON_COLUMN) { 3590 releaseAllocatedSectionFromTable(row); 3591 } 3592 } 3593 } 3594 3595 /* 3596 * Mouse popup stuff 3597 */ 3598 3599 /** 3600 * Process the column header click 3601 * @param e the evnt data 3602 * @param table the JTable 3603 */ 3604 protected void showTableHeaderPopup(JmriMouseEvent e, JTable table) { 3605 JPopupMenu popupMenu = new JPopupMenu(); 3606 XTableColumnModel tcm = (XTableColumnModel) table.getColumnModel(); 3607 for (int i = 0; i < tcm.getColumnCount(false); i++) { 3608 TableColumn tc = tcm.getColumnByModelIndex(i); 3609 String columnName = table.getModel().getColumnName(i); 3610 if (columnName != null && !columnName.equals("")) { 3611 JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(table.getModel().getColumnName(i), tcm.isColumnVisible(tc)); 3612 menuItem.addActionListener(new HeaderActionListener(tc, tcm)); 3613 popupMenu.add(menuItem); 3614 } 3615 3616 } 3617 popupMenu.show(e.getComponent(), e.getX(), e.getY()); 3618 } 3619 3620 /** 3621 * Adds the column header pop listener to a JTable using XTableColumnModel 3622 * @param table The JTable effected. 3623 */ 3624 protected void addMouseListenerToHeader(JTable table) { 3625 JmriMouseListener mouseHeaderListener = new TableHeaderListener(table); 3626 table.getTableHeader().addMouseListener(JmriMouseListener.adapt(mouseHeaderListener)); 3627 } 3628 3629 static protected class HeaderActionListener implements ActionListener { 3630 3631 TableColumn tc; 3632 XTableColumnModel tcm; 3633 3634 HeaderActionListener(TableColumn tc, XTableColumnModel tcm) { 3635 this.tc = tc; 3636 this.tcm = tcm; 3637 } 3638 3639 @Override 3640 public void actionPerformed(ActionEvent e) { 3641 JCheckBoxMenuItem check = (JCheckBoxMenuItem) e.getSource(); 3642 //Do not allow the last column to be hidden 3643 if (!check.isSelected() && tcm.getColumnCount(true) == 1) { 3644 return; 3645 } 3646 tcm.setColumnVisible(tc, check.isSelected()); 3647 } 3648 } 3649 3650 /** 3651 * Class to support Columnheader popup menu on XTableColum model. 3652 */ 3653 class TableHeaderListener extends JmriMouseAdapter { 3654 3655 JTable table; 3656 3657 TableHeaderListener(JTable tbl) { 3658 super(); 3659 table = tbl; 3660 } 3661 3662 /** 3663 * {@inheritDoc} 3664 */ 3665 @Override 3666 public void mousePressed(JmriMouseEvent e) { 3667 if (e.isPopupTrigger()) { 3668 showTableHeaderPopup(e, table); 3669 } 3670 } 3671 3672 /** 3673 * {@inheritDoc} 3674 */ 3675 @Override 3676 public void mouseReleased(JmriMouseEvent e) { 3677 if (e.isPopupTrigger()) { 3678 showTableHeaderPopup(e, table); 3679 } 3680 } 3681 3682 /** 3683 * {@inheritDoc} 3684 */ 3685 @Override 3686 public void mouseClicked(JmriMouseEvent e) { 3687 if (e.isPopupTrigger()) { 3688 showTableHeaderPopup(e, table); 3689 } 3690 } 3691 } 3692 3693 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DispatcherFrame.class); 3694 3695}