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