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