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