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