001package jmri.jmrit.entryexit; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.awt.BorderLayout; 005import java.awt.Color; 006import java.awt.Container; 007import java.beans.PropertyChangeEvent; 008import java.beans.PropertyChangeListener; 009import java.util.ArrayList; 010import java.util.Hashtable; 011import java.util.LinkedHashMap; 012import java.util.List; 013import java.util.UUID; 014 015import javax.swing.JButton; 016import javax.swing.JFrame; 017import javax.swing.JLabel; 018import javax.swing.JPanel; 019 020import jmri.*; 021import jmri.jmrit.dispatcher.ActiveTrain; 022import jmri.jmrit.dispatcher.DispatcherFrame; 023import jmri.jmrit.display.layoutEditor.ConnectivityUtil; 024import jmri.jmrit.display.layoutEditor.LayoutBlock; 025import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools; 026import jmri.jmrit.display.layoutEditor.LayoutBlockManager; 027import jmri.jmrit.display.layoutEditor.LayoutSlip; 028import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState; 029import jmri.jmrit.display.layoutEditor.LayoutTurnout; 030import jmri.util.swing.JmriJOptionPane; 031import jmri.util.ThreadingUtil; 032 033public class DestinationPoints extends jmri.implementation.AbstractNamedBean { 034 035 /** 036 * String constant for active. 037 */ 038 public static final String PROPERTY_ACTIVE = "active"; 039 040 /** 041 * String constant for no change. 042 */ 043 public static final String PROPERTY_NO_CHANGE = "noChange"; 044 045 /** 046 * String constant for stacked. 047 */ 048 public static final String PROPERTY_STACKED = "stacked"; 049 050 /** 051 * String constant for failed. 052 */ 053 public static final String PROPERTY_FAILED = "failed"; 054 055 @Override 056 public String getBeanType() { 057 return Bundle.getMessage("BeanNameDestination"); // NOI18N 058 } 059 060 transient PointDetails point = null; 061 private boolean uniDirection = true; 062 int entryExitType = EntryExitPairs.SETUPTURNOUTSONLY;//SETUPSIGNALMASTLOGIC; 063 private boolean enabled = true; 064 private boolean activeEntryExit = false; 065 private boolean activeEntryExitReversed = false; 066 private List<LayoutBlock> routeDetails = new ArrayList<>(); 067 private LayoutBlock destination; 068 private boolean disposed = false; 069 070 transient EntryExitPairs manager = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class); 071 072 transient SignalMastLogic sml; 073 074 static final int NXMESSAGEBOXCLEARTIMEOUT = 30; 075 076 /** 077 * public for testing purposes. 078 * @return true if enabled, else false. 079 */ 080 public boolean isEnabled() { 081 return enabled; 082 } 083 084 public void setEnabled(boolean boo) { 085 enabled = boo; 086 087 // Modify source signal mast held state 088 Sensor sourceSensor = src.getPoint().getSensor(); 089 if (sourceSensor == null) { 090 return; 091 } 092 SignalMast sourceMast = src.getPoint().getSignalMast(); 093 if (sourceMast == null) { 094 return; 095 } 096 if (enabled) { 097 if (!manager.isAbsSignalMode()) { 098 sourceMast.setHeld(true); 099 } 100 } else { 101 // All destinations for the source must be disabled before the mast hold can be released 102 for (PointDetails pd : src.getDestinationPoints()) { 103 if (src.getDestForPoint(pd).isEnabled()) { 104 return; 105 } 106 } 107 sourceMast.setHeld(false); 108 } 109 } 110 111 transient Source src = null; 112 113 protected DestinationPoints(PointDetails point, String id, Source src) { 114 super(id != null ? id : "IN:" + UUID.randomUUID().toString()); 115 this.src = src; 116 this.point = point; 117 setUserName(src.getPoint().getDisplayName() + " to " + this.point.getDisplayName()); 118 119 propertyBlockListener = this::blockStateUpdated; 120 } 121 122 String getUniqueId() { 123 return getSystemName(); 124 } 125 126 public PointDetails getDestPoint() { 127 return point; 128 } 129 130 /** 131 * @since 4.17.4 132 * Making the source object available for scripting in Jython. 133 * @return source. 134 */ 135 public Source getSource() { 136 return src ; 137 } 138 139 boolean getUniDirection() { 140 return uniDirection; 141 } 142 143 void setUniDirection(boolean uni) { 144 uniDirection = uni; 145 } 146 147 NamedBean getSignal() { 148 return point.getSignal(); 149 } 150 151 void setRouteTo(boolean set) { 152 if (set && getEntryExitType() == EntryExitPairs.FULLINTERLOCK) { 153 point.setRouteTo(true); 154 point.setNXButtonState(EntryExitPairs.NXBUTTONACTIVE); 155 } else { 156 point.setRouteTo(false); 157 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 158 } 159 } 160 161 void setRouteFrom(boolean set) { 162 if (set && getEntryExitType() == EntryExitPairs.FULLINTERLOCK) { 163 src.pd.setRouteFrom(true); 164 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONACTIVE); 165 } else { 166 src.pd.setRouteFrom(false); 167 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 168 } 169 } 170 171 boolean isRouteToPointSet() { 172 return point.isRouteToPointSet(); 173 } 174 175 LayoutBlock getFacing() { 176 return point.getFacing(); 177 } 178 179 List<LayoutBlock> getProtecting() { 180 return point.getProtecting(); 181 } 182 183 int getEntryExitType() { 184 return entryExitType; 185 } 186 187 void setEntryExitType(int type) { 188 entryExitType = type; 189 if ((type != EntryExitPairs.SETUPTURNOUTSONLY) && (getSignal() != null) && point.getSignal() != null) { 190 uniDirection = true; 191 } 192 } 193 194 @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", 195 justification = "No auto serialization") 196 transient protected PropertyChangeListener propertyBlockListener; 197 198 protected void blockStateUpdated(PropertyChangeEvent e) { 199 Block blk = (Block) e.getSource(); 200 if ( Block.PROPERTY_STATE.equals(e.getPropertyName())) { 201 if (log.isDebugEnabled()) { 202 log.debug("{} We have a change of state on the block {}", getUserName(), blk.getDisplayName()); // NOI18N 203 } 204 int now = ((Integer) e.getNewValue()); 205 206 LayoutBlock lBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(blk); 207 if (lBlock == null){ 208 log.error("Unable to get layout block from block {}",blk); 209 return; 210 } 211 212 if (now == Block.OCCUPIED) { 213 //If the block was previously active or inactive then we will 214 //reset the useExtraColor, but not if it was previously unknown or inconsistent. 215 lBlock.setUseExtraColor(false); 216 blk.removePropertyChangeListener(propertyBlockListener); //was this 217 removeBlockFromRoute(lBlock); 218 } else { 219 if (src.getStart() == lBlock) { 220 // Remove listener when the start block becomes unoccupied. 221 // When the start block is occupied when the route is created, the normal 222 // removal does not occur. 223 lBlock.getBlock().removePropertyChangeListener(propertyBlockListener); 224 log.debug("Remove listener from start block {} for {}", lBlock.getDisplayName(), this.getDisplayName()); 225 } else { 226 log.debug("state was {} and did not go through reset",now); // NOI18N 227 } 228 } 229 } 230 } 231 232 Object lastSeenActiveBlockObject; 233 234 synchronized void removeBlockFromRoute(LayoutBlock lBlock) { 235 236 if (routeDetails != null) { 237 if (routeDetails.indexOf(lBlock) == -1) { 238 if (src.getStart() == lBlock) { 239 log.debug("Start block went active"); // NOI18N 240 lastSeenActiveBlockObject = src.getStart().getBlock().getValue(); 241 lBlock.getBlock().removePropertyChangeListener(propertyBlockListener); 242 return; 243 } else { 244 log.error("Block {} went active but it is not part of our NX path", lBlock.getDisplayName()); // NOI18N 245 return; 246 } 247 } 248 if (routeDetails.indexOf(lBlock) != 0) { 249 log.debug("A block has been skipped will set the value of the active block to that of the original one"); // NOI18N 250 // Only give a value to a block that has none, and never clear one 251 if (lastSeenActiveBlockObject != null && lBlock.getBlock().getValue() == null) { 252 lBlock.getBlock().setValue(lastSeenActiveBlockObject); 253 } 254 if (routeDetails.indexOf(lBlock) != -1) { 255 while (routeDetails.indexOf(lBlock) != 0) { 256 LayoutBlock tbr = routeDetails.get(0); 257 log.debug("Block skipped {} and removed from list", tbr.getDisplayName()); // NOI18N 258 tbr.getBlock().removePropertyChangeListener(propertyBlockListener); 259 tbr.setUseExtraColor(false); 260 routeDetails.remove(0); 261 } 262 } 263 } 264 if (routeDetails.contains(lBlock)) { 265 routeDetails.remove(lBlock); 266 setRouteFrom(false); 267 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 268 if (sml != null && getEntryExitType() == EntryExitPairs.FULLINTERLOCK) { 269 if (!manager.isAbsSignalMode()) { 270 sml.getSourceMast().setHeld(true); 271 } 272 SignalMast mast = (SignalMast) getSignal(); 273 if (sml.getStoreState(mast) == SignalMastLogic.STORENONE) { 274 sml.removeDestination(mast); 275 } 276 } 277 } else { 278 log.error("Block {} that went Occupied was not in the routeDetails list", lBlock.getDisplayName()); // NOI18N 279 } 280 if (log.isDebugEnabled()) { 281 log.debug("Route details contents {}", routeDetails); // NOI18N 282 for (int i = 0; i < routeDetails.size(); i++) { 283 log.debug(" name: {}", routeDetails.get(i).getDisplayName()); 284 } 285 } 286 if ((routeDetails.size() == 1) && (routeDetails.contains(destination))) { 287 routeDetails.get(0).getBlock().removePropertyChangeListener(propertyBlockListener); // was set against block sensor 288 routeDetails.remove(destination); 289 } 290 } 291 lastSeenActiveBlockObject = lBlock.getBlock().getValue(); 292 293 if ((routeDetails == null) || (routeDetails.isEmpty())) { 294 //At this point the route has cleared down/the last remaining block are now active. 295 routeDetails = null; 296 setRouteTo(false); 297 setRouteFrom(false); 298 setActiveEntryExit(false); 299 lastSeenActiveBlockObject = null; 300 } 301 } 302 303 //For a clear down we need to add a message, if it is a cancel, manual clear down or I didn't mean it. 304 // For creating routes, the route is then set in steps on the layout thread. 305 void setRoute(boolean state) { 306 log.debug("[setRoute] Start, dp = {}", getUserName()); 307 308 if (disposed) { 309 log.error("Set route called even though interlock has been disposed of"); // NOI18N 310 manager.routeSettingDone(this, false); 311 return; 312 } 313 314 if (routeDetails == null) { 315 log.error("No route to set or clear down"); // NOI18N 316 // Nothing is set, but there is nothing left to clear down either 317 manager.routeSettingDone(this, !state); 318 setActiveEntryExit(false); 319 setRouteTo(false); 320 setRouteFrom(false); 321 if ((getSignal() instanceof SignalMast) && (getEntryExitType() != EntryExitPairs.FULLINTERLOCK)) { 322 SignalMast mast = (SignalMast) getSignal(); 323 mast.setHeld(false); 324 } 325 synchronized (this) { 326 destination = null; 327 } 328 return; 329 } 330 if (!state) { 331 switch (manager.getClearDownOption()) { 332 case EntryExitPairs.PROMPTUSER: 333 cancelClearOptionBox(); 334 break; 335 case EntryExitPairs.AUTOCANCEL: 336 cancelClearInterlock(EntryExitPairs.CANCELROUTE); 337 manager.routeSettingDone(this, true); 338 break; 339 case EntryExitPairs.AUTOCLEAR: 340 cancelClearInterlock(EntryExitPairs.CLEARROUTE); 341 manager.routeSettingDone(this, true); 342 break; 343 case EntryExitPairs.AUTOSTACK: 344 cancelClearInterlock(EntryExitPairs.STACKROUTE); 345 manager.routeSettingDone(this, false); 346 break; 347 default: 348 cancelClearOptionBox(); 349 break; 350 } 351 log.debug("[setRoute] Cancel/Clear/Stack route, dp = {}", getUserName()); 352 return; 353 } 354 if (manager.isRouteStacked(this, false)) { 355 manager.cancelStackedRoute(this, false); 356 } 357 ActiveTrain tmpat = null; 358 if (manager.getDispatcherIntegration() && InstanceManager.getNullableDefault(DispatcherFrame.class) != null) { 359 DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class); 360 for (ActiveTrain atl : df.getActiveTrainsList()) { 361 if (atl.getEndBlock() == src.getStart().getBlock()) { 362 if (atl.getLastAllocatedSection() == atl.getEndBlockSection()) { 363 if (!atl.getReverseAtEnd() && !atl.getResetWhenDone()) { 364 tmpat = atl; 365 break; 366 } 367 log.warn("Interlock will not be added to existing Active Train as it is set for back and forth operation"); // NOI18N 368 } 369 } 370 } 371 } 372 final ActiveTrain at = tmpat; 373 // The start block is needed to find the turnouts of the first block, but when signal masts 374 // protect the route it is not part of the route. Remove it before any block listener is 375 // added, so that a train entering the start block now cannot remove a block of the route. 376 final List<LayoutBlock> blocks; 377 synchronized (this) { 378 blocks = new ArrayList<>(routeDetails); 379 if (getEntryExitType() != EntryExitPairs.SETUPTURNOUTSONLY 380 && (src.sourceSignal instanceof SignalMast) && (getSignal() instanceof SignalMast) 381 && !routeDetails.isEmpty()) { 382 routeDetails.remove(0); 383 } 384 } 385 /* The route is set in steps on the layout thread. Each step sets one turnout and schedules 386 the next one, so nothing waits while the turnouts move and the panel, including the 387 flashing of the NX buttons, carries on as normal. */ 388 RouteSetting setting = new RouteSetting(blocks, at); 389 routeSetting = setting; 390 manager.routeSettingStarted(); 391 ThreadingUtil.runOnLayoutEventually(setting::start); 392 log.debug("[setRoute] Route setting started, dp = {}", getUserName()); 393 } 394 395 /** 396 * The route setting in progress, or the last one. 397 */ 398 private volatile RouteSetting routeSetting = null; 399 400 /** 401 * Delay in milliseconds after which the hold on the source mast is removed 402 * even if the turnouts have not reported their new state. 403 */ 404 int mastReleaseTimeout = 10000; 405 406 /** 407 * Sets the route of this destination point on the layout thread. 408 * <p> 409 * The turnouts are set one at a time, 250 ms plus the turnout set delay 410 * apart. Each step schedules the next one, and the last step completes the 411 * route: signal mast logic, colours, Dispatcher and NX buttons. The result 412 * is reported to the manager once, when the route has been set or setting 413 * it has failed. If the route is cleared down, set again or disposed of 414 * while its turnouts are being set, the remaining steps are not run. 415 */ 416 private class RouteSetting { 417 418 private final List<LayoutBlock> blocks; 419 private final ActiveTrain at; 420 private final Hashtable<Turnout, Integer> turnoutSettings = new Hashtable<>(); 421 private final List<Turnout> turnoutsToSet = new ArrayList<>(); 422 private final List<Color> realColorStd = new ArrayList<>(); 423 private final List<Color> realColorXtra = new ArrayList<>(); 424 private final List<LayoutBlock> routeBlocks = new ArrayList<>(); 425 private boolean ended = false; 426 427 RouteSetting(List<LayoutBlock> blocks, ActiveTrain at) { 428 this.blocks = blocks; 429 this.at = at; 430 } 431 432 private boolean isCancelled() { 433 return disposed || routeSetting != this || routeDetails == null; 434 } 435 436 /** 437 * First step: show that the route is being set, add the block 438 * listeners and find the turnouts to set. 439 */ 440 void start() { 441 if (isCancelled()) { 442 cancelled(); 443 return; 444 } 445 try { 446 if (manager.useDifferentColorWhenSetting()) { 447 boolean first = true; 448 for (LayoutBlock lbk : blocks) { 449 if (first) { 450 // Don't change the color for the facing block 451 first = false; 452 continue; 453 } 454 routeBlocks.add(lbk); 455 realColorXtra.add(lbk.getBlockExtraColor()); 456 realColorStd.add(lbk.getBlockTrackColor()); 457 lbk.setBlockExtraColor(manager.getSettingRouteColor()); 458 lbk.setBlockTrackColor(manager.getSettingRouteColor()); 459 } 460 //Force a redraw, to reflect color change 461 src.getPoint().getPanel().redrawPanel(); 462 } 463 if (!manager.isSkipGuiFix()) { 464 // With the old GUI behavior clicks are held back instead, so that a second 465 // click on the destination can start the next route 466 src.getPoint().getPanel().getGlassPane().setVisible(true); 467 } 468 469 ConnectivityUtil connection = new ConnectivityUtil(point.getPanel()); 470 471 // Last block in the route is the one that we are protecting at the last sensor/signalmast 472 for (int i = 0; i < blocks.size(); i++) { 473 //if we are not using the dispatcher and the signal logic is dynamic, then set the turnouts 474 if (at == null && isSignalLogicDynamic()) { 475 if (i > 0) { 476 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutlist; 477 int nxtBlk = i + 1; 478 int preBlk = i - 1; 479 if (i < blocks.size() - 1) { 480 turnoutlist = connection.getTurnoutList(blocks.get(i).getBlock(), blocks.get(preBlk).getBlock(), blocks.get(nxtBlk).getBlock()); 481 for (int x = 0; x < turnoutlist.size(); x++) { 482 if (turnoutlist.get(x).getObject() instanceof LayoutSlip) { 483 int slipState = turnoutlist.get(x).getExpectedState(); 484 LayoutSlip ls = (LayoutSlip) turnoutlist.get(x).getObject(); 485 int taState = ls.getTurnoutState(slipState); 486 Turnout t = ls.getTurnout(); 487 if (t==null) { 488 log.warn("Found unexpected Turnout reference at {}: {}",i,ls); 489 continue; // not sure what else do to here 490 } 491 turnoutSettings.put(t, taState); 492 493 int tbState = ls.getTurnoutBState(slipState); 494 ls.getTurnoutB().setCommandedState(tbState); 495 turnoutSettings.put(ls.getTurnoutB(), tbState); 496 } else { 497 String t = turnoutlist.get(x).getObject().getTurnoutName(); 498 Turnout turnout = InstanceManager.turnoutManagerInstance().getTurnout(t); 499 if (turnout != null) { 500 turnoutSettings.put(turnout, turnoutlist.get(x).getExpectedState()); 501 if (turnoutlist.get(x).getObject().getSecondTurnout() != null) { 502 turnoutSettings.put(turnoutlist.get(x).getObject().getSecondTurnout(), 503 turnoutlist.get(x).getExpectedState()); 504 } 505 } 506 } 507 } 508 } 509 } 510 } 511 if ((getEntryExitType() == EntryExitPairs.FULLINTERLOCK)) { 512 blocks.get(i).getBlock().addPropertyChangeListener(propertyBlockListener); // was set against occupancy sensor 513 if (i > 0) { 514 blocks.get(i).setUseExtraColor(true); 515 } 516 } else { 517 blocks.get(i).getBlock().removePropertyChangeListener(propertyBlockListener); // was set against occupancy sensor 518 } 519 } 520 if (at == null) { 521 if (!isSignalLogicDynamic()) { 522 SignalMastLogic tmSml = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic((SignalMast) src.sourceSignal); 523 for (Turnout t : tmSml.getAutoTurnouts((SignalMast) getSignal())) { 524 turnoutSettings.put(t, tmSml.getAutoTurnoutState(t, (SignalMast) getSignal())); 525 } 526 } 527 turnoutsToSet.addAll(turnoutSettings.keySet()); 528 } 529 } catch (RuntimeException ex) { 530 failed(ex); 531 return; 532 } 533 setTurnout(0); 534 } 535 536 /** 537 * Set one turnout and schedule the next step after the turnout delay. 538 * When all turnouts are set, complete the route. 539 * 540 * @param index the index of the turnout to set 541 */ 542 private void setTurnout(int index) { 543 if (index >= turnoutsToSet.size()) { 544 complete(); 545 return; 546 } 547 if (isCancelled()) { 548 cancelled(); 549 return; 550 } 551 try { 552 Turnout turnout = turnoutsToSet.get(index); 553 turnout.setCommandedState(turnoutSettings.get(turnout)); 554 } catch (RuntimeException ex) { 555 failed(ex); 556 return; 557 } 558 ThreadingUtil.runOnLayoutDelayed(() -> setTurnout(index + 1), 250 + manager.turnoutSetDelay); 559 } 560 561 /** 562 * Last step: set up the signal mast logic and the Dispatcher, and 563 * update the colours and NX buttons. 564 */ 565 private void complete() { 566 if (isCancelled()) { 567 cancelled(); 568 return; 569 } 570 try { 571 src.getPoint().getPanel().redrawPanel(); 572 if (getEntryExitType() != EntryExitPairs.SETUPTURNOUTSONLY) { 573 if (getEntryExitType() == EntryExitPairs.FULLINTERLOCK) { 574 //If our start block is already active we will set it as our lastSeenActiveBlock. 575 if (src.getStart().getState() == Block.OCCUPIED) { 576 src.getStart().getBlock().removePropertyChangeListener(propertyBlockListener); 577 lastSeenActiveBlockObject = src.getStart().getBlock().getValue(); 578 log.debug("Last seen value {}", lastSeenActiveBlockObject); 579 } 580 } 581 if ((src.sourceSignal instanceof SignalMast) && (getSignal() instanceof SignalMast)) { 582 SignalMast smSource = (SignalMast) src.sourceSignal; 583 SignalMast smDest = (SignalMast) getSignal(); 584 synchronized (DestinationPoints.this) { 585 sml = InstanceManager.getDefault(SignalMastLogicManager.class).newSignalMastLogic(smSource); 586 if (!sml.isDestinationValid(smDest)) { 587 //if no signalmastlogic existed then created it, but set it not to be stored. 588 sml.setDestinationMast(smDest); 589 sml.setStore(SignalMastLogic.STORENONE, smDest); 590 } 591 } 592 593 releaseMast(smSource, turnoutSettings); 594 //Only change the block and turnout details if this a temp signalmast logic 595 if (sml.getStoreState(smDest) == SignalMastLogic.STORENONE) { 596 LinkedHashMap<Block, Integer> blks = new LinkedHashMap<>(); 597 for (int i = 0; i < routeDetails.size(); i++) { 598 if (routeDetails.get(i).getBlock().getState() == Block.UNKNOWN) { 599 routeDetails.get(i).getBlock().setState(Block.UNOCCUPIED); 600 } 601 blks.put(routeDetails.get(i).getBlock(), Block.UNOCCUPIED); 602 } 603 sml.setAutoBlocks(blks, smDest); 604 sml.setAutoTurnouts(turnoutSettings, smDest); 605 sml.initialise(smDest); 606 } 607 smSource.addPropertyChangeListener(new PropertyChangeListener() { 608 @Override 609 public void propertyChange(PropertyChangeEvent e) { 610 SignalMast source = (SignalMast) e.getSource(); 611 source.removePropertyChangeListener(this); 612 setRouteFrom(true); 613 setRouteTo(true); 614 } 615 }); 616 src.pd.extendedtime = true; 617 point.extendedtime = true; 618 } else { 619 if (src.sourceSignal instanceof SignalMast) { 620 SignalMast mast = (SignalMast) src.sourceSignal; 621 releaseMast(mast, turnoutSettings); 622 } else if (src.sourceSignal instanceof SignalHead) { 623 SignalHead head = (SignalHead) src.sourceSignal; 624 head.setHeld(false); 625 } 626 setRouteFrom(true); 627 setRouteTo(true); 628 } 629 } 630 if (manager.useDifferentColorWhenSetting()) { 631 javax.swing.Timer resetColorBack = new javax.swing.Timer(manager.getSettingTimer(), new java.awt.event.ActionListener() { 632 @Override 633 public void actionPerformed(java.awt.event.ActionEvent e) { 634 restoreColors(); 635 src.getPoint().getPanel().redrawPanel(); 636 } 637 }); 638 resetColorBack.setRepeats(false); 639 resetColorBack.start(); 640 } 641 642 if (at != null) { 643 Section sec; 644 if (sml != null && sml.getAssociatedSection((SignalMast) getSignal()) != null) { 645 sec = sml.getAssociatedSection((SignalMast) getSignal()); 646 } else { 647 String secUserName = src.getPoint().getDisplayName() + ":" + point.getDisplayName(); 648 sec = InstanceManager.getDefault(SectionManager.class).getSection(secUserName); 649 if (sec == null) { 650 sec = InstanceManager.getDefault(SectionManager.class).createNewSection(secUserName); 651 sec.setSectionType(Section.DYNAMICADHOC); 652 } 653 if (sec.getSectionType() == Section.DYNAMICADHOC) { 654 sec.removeAllBlocksFromSection(); 655 for (LayoutBlock key : routeDetails) { 656 if (key != src.getStart()) { 657 sec.addBlock(key.getBlock()); 658 } 659 } 660 String dir = Path.decodeDirection(src.getStart().getNeighbourDirection(routeDetails.get(0).getBlock())); 661 EntryPoint ep = new EntryPoint(routeDetails.get(0).getBlock(), src.getStart().getBlock(), dir); 662 ep.setTypeForward(); 663 sec.addToForwardList(ep); 664 665 LayoutBlock proDestLBlock = point.getProtecting().get(0); 666 if (proDestLBlock != null) { 667 dir = Path.decodeDirection(proDestLBlock.getNeighbourDirection(point.getFacing())); 668 ep = new EntryPoint(point.getFacing().getBlock(), proDestLBlock.getBlock(), dir); 669 ep.setTypeReverse(); 670 sec.addToReverseList(ep); 671 } 672 } 673 } 674 InstanceManager.getDefault(DispatcherFrame.class).extendActiveTrainsPath(sec, at, src.getPoint().getPanel()); 675 } 676 677 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 678 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 679 } catch (RuntimeException ex) { 680 failed(ex); 681 return; 682 } 683 try { 684 src.getPoint().getPanel().getGlassPane().setVisible(false); 685 log.debug("[setRoute] Done, dp = {}", getUserName()); 686 } finally { 687 end(true, true); 688 } 689 } 690 691 private void failed(RuntimeException ex) { 692 try { 693 log.error("An error occurred while setting the route", ex); // NOI18N 694 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 695 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 696 restoreColors(); 697 src.getPoint().getPanel().redrawPanel(); 698 src.getPoint().getPanel().getGlassPane().setVisible(false); 699 } finally { 700 // Always report, so that a route over several pairs and held clicks are not left waiting 701 end(false, true); 702 } 703 } 704 705 private void cancelled() { 706 // When the route has been set again, the new route setting reports the result 707 boolean current = routeSetting == this; 708 try { 709 log.debug("[setRoute] Route setting stopped, dp = {}", getUserName()); 710 if (current) { 711 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 712 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 713 } 714 restoreColors(); 715 src.getPoint().getPanel().redrawPanel(); 716 src.getPoint().getPanel().getGlassPane().setVisible(false); 717 } finally { 718 end(false, current); 719 } 720 } 721 722 private void restoreColors() { 723 for (int i = 0; i < routeBlocks.size(); i++) { 724 LayoutBlock lbk = routeBlocks.get(i); 725 lbk.setBlockExtraColor(realColorXtra.get(i)); 726 lbk.setBlockTrackColor(realColorStd.get(i)); 727 } 728 } 729 730 private void end(boolean success, boolean report) { 731 if (ended) { 732 return; 733 } 734 ended = true; 735 if (report) { 736 manager.routeSettingDone(DestinationPoints.this, success); 737 } 738 manager.routeSettingFinished(); 739 } 740 } 741 742 /** 743 * Remove the hold on the mast when all of the turnouts have completed moving. 744 * This only applies to turnouts using ONESENSOR feedback. TWOSENSOR has an 745 * intermediate inconsistent state which prevents erroneous signal aspects. 746 * The maximum wait time is 10 seconds. 747 * <p> 748 * The turnouts are watched with a listener and the hold is removed on the 749 * layout thread. 750 * 751 * @since 4.11.1 752 * @param mast The signal mast that will be released. 753 * @param turnoutSettings The turnouts that are being set for the current NX route. 754 */ 755 private void releaseMast(SignalMast mast, Hashtable<Turnout, Integer> turnoutSettings) { 756 new MastRelease(mast, turnoutSettings).start(); 757 } 758 759 /** 760 * Removes the hold on a signal mast when the ONESENSOR turnouts of a route 761 * have reached their commanded state, or after the time out. 762 */ 763 private class MastRelease implements PropertyChangeListener { 764 765 private final SignalMast mast; 766 private final List<Turnout> turnouts = new ArrayList<>(); 767 private boolean released = false; 768 769 MastRelease(SignalMast mast, Hashtable<Turnout, Integer> turnoutSettings) { 770 this.mast = mast; 771 for (Turnout tout : turnoutSettings.keySet()) { 772 if (tout.getFeedbackMode() == Turnout.ONESENSOR) { 773 turnouts.add(tout); 774 } 775 } 776 } 777 778 void start() { 779 for (Turnout tout : turnouts) { 780 tout.addPropertyChangeListener(this); 781 } 782 ThreadingUtil.runOnLayoutDelayed(this::release, mastReleaseTimeout); 783 ThreadingUtil.runOnLayoutEventually(this::check); 784 } 785 786 @Override 787 public void propertyChange(PropertyChangeEvent e) { 788 if (Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName()) 789 || Turnout.PROPERTY_COMMANDED_STATE.equals(e.getPropertyName())) { 790 ThreadingUtil.runOnLayoutEventually(this::check); 791 } 792 } 793 794 private void check() { 795 for (Turnout tout : turnouts) { 796 if (tout.getKnownState() != tout.getCommandedState()) { 797 return; 798 } 799 } 800 release(); 801 } 802 803 private void release() { 804 if (released) { 805 return; 806 } 807 released = true; 808 for (Turnout tout : turnouts) { 809 tout.removePropertyChangeListener(this); 810 } 811 log.debug("[releaseMast] mast = {}", mast.getDisplayName()); 812 mast.setHeld(false); 813 } 814 } 815 816 private boolean isSignalLogicDynamic() { 817 if ((src.sourceSignal instanceof SignalMast) && (getSignal() instanceof SignalMast)) { 818 SignalMast smSource = (SignalMast) src.sourceSignal; 819 SignalMast smDest = (SignalMast) getSignal(); 820 if (InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(smSource) != null 821 && InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(smSource).getStoreState(smDest) != SignalMastLogic.STORENONE) { 822 return false; 823 } 824 } 825 return true; 826 827 } 828 829 private JFrame cancelClearFrame; 830 transient private Thread threadAutoClearFrame = null; 831 JButton jButton_Stack = new JButton(Bundle.getMessage("Stack")); // NOI18N 832 833 void cancelClearOptionBox() { 834 if (cancelClearFrame == null) { 835 JButton jButton_Clear = new JButton(Bundle.getMessage("ClearDown")); // NOI18N 836 JButton jButton_Cancel = new JButton(Bundle.getMessage("ButtonCancel")); // NOI18N 837 838 JButton jButton_Exit = new JButton(Bundle.getMessage("Exit")); // NOI18N 839 JLabel jLabel = new JLabel(Bundle.getMessage("InterlockPrompt")); // NOI18N 840 JLabel jIcon = new JLabel(javax.swing.UIManager.getIcon("OptionPane.questionIcon")); // NOI18N 841 cancelClearFrame = new JFrame(Bundle.getMessage("Interlock")); // NOI18N 842 Container cont = cancelClearFrame.getContentPane(); 843 JPanel qPanel = new JPanel(); 844 qPanel.add(jIcon); 845 qPanel.add(jLabel); 846 cont.add(qPanel, BorderLayout.CENTER); 847 JPanel buttonsPanel = new JPanel(); 848 buttonsPanel.add(jButton_Cancel); 849 buttonsPanel.add(jButton_Clear); 850 buttonsPanel.add(jButton_Stack); 851 buttonsPanel.add(jButton_Exit); 852 cont.add(buttonsPanel, BorderLayout.SOUTH); 853 cancelClearFrame.pack(); 854 855 jButton_Clear.addActionListener( e -> { 856 cancelClearFrame.setVisible(false); 857 threadAutoClearFrame.interrupt(); 858 cancelClearInterlock(EntryExitPairs.CLEARROUTE); 859 manager.routeSettingDone(this, true); 860 }); 861 jButton_Cancel.addActionListener( e -> { 862 cancelClearFrame.setVisible(false); 863 threadAutoClearFrame.interrupt(); 864 cancelClearInterlock(EntryExitPairs.CANCELROUTE); 865 manager.routeSettingDone(this, true); 866 }); 867 jButton_Stack.addActionListener( e -> { 868 cancelClearFrame.setVisible(false); 869 threadAutoClearFrame.interrupt(); 870 cancelClearInterlock(EntryExitPairs.STACKROUTE); 871 manager.routeSettingDone(this, false); 872 }); 873 jButton_Exit.addActionListener( e -> { 874 cancelClearFrame.setVisible(false); 875 threadAutoClearFrame.interrupt(); 876 cancelClearInterlock(EntryExitPairs.EXITROUTE); 877 manager.routeSettingDone(this, false); 878 firePropertyChange(PROPERTY_NO_CHANGE, null, null); 879 }); 880 src.getPoint().getPanel().setGlassPane(manager.getGlassPane()); 881 882 } 883 cancelClearFrame.setTitle(getUserName()); 884 jButton_Stack.setEnabled(!(manager.isRouteStacked(this, false))); 885 886 if (cancelClearFrame.isVisible()) { 887 return; 888 } 889 src.pd.extendedtime = true; 890 point.extendedtime = true; 891 892 class MessageTimeOut implements Runnable { 893 894 MessageTimeOut() { 895 } 896 897 @Override 898 public void run() { 899 try { 900 //Set a timmer before this window is automatically closed to 30 seconds 901 Thread.sleep(NXMESSAGEBOXCLEARTIMEOUT * 1000); 902 cancelClearFrame.setVisible(false); 903 cancelClearInterlock(EntryExitPairs.EXITROUTE); 904 manager.routeSettingDone(DestinationPoints.this, false); 905 } catch (InterruptedException ex) { 906 log.debug("Flash timer cancelled"); // NOI18N 907 } 908 } 909 } 910 MessageTimeOut mt = new MessageTimeOut(); 911 threadAutoClearFrame = ThreadingUtil.newThread(mt, "NX Button Clear Message Timeout "); // NOI18N 912 threadAutoClearFrame.start(); 913 cancelClearFrame.setAlwaysOnTop(true); 914 src.getPoint().getPanel().getGlassPane().setVisible(true); 915 int w = cancelClearFrame.getSize().width; 916 int h = cancelClearFrame.getSize().height; 917 int x = (int) src.getPoint().getPanel().getLocation().getX() + ((src.getPoint().getPanel().getSize().width - w) / 2); 918 int y = (int) src.getPoint().getPanel().getLocation().getY() + ((src.getPoint().getPanel().getSize().height - h) / 2); 919 cancelClearFrame.setLocation(x, y); 920 cancelClearFrame.setVisible(true); 921 } 922 923 void cancelClearInterlock(int cancelClear) { 924 if ((cancelClear == EntryExitPairs.EXITROUTE) || (cancelClear == EntryExitPairs.STACKROUTE)) { 925 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 926 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 927 src.getPoint().getPanel().getGlassPane().setVisible(false); 928 if (cancelClear == EntryExitPairs.STACKROUTE) { 929 manager.stackNXRoute(this, false); 930 } 931 return; 932 } 933 934 if (cancelClear == EntryExitPairs.CANCELROUTE) { 935 if (manager.getDispatcherIntegration() && InstanceManager.getNullableDefault(DispatcherFrame.class) != null) { 936 DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class); 937 ActiveTrain at = null; 938 for (ActiveTrain atl : df.getActiveTrainsList()) { 939 if (atl.getEndBlock() == point.getFacing().getBlock()) { 940 if (atl.getLastAllocatedSection() == atl.getEndBlockSection()) { 941 at = atl; 942 break; 943 } 944 } 945 } 946 if (at != null) { 947 Section sec; 948 synchronized (this) { 949 if (sml != null && sml.getAssociatedSection((SignalMast) getSignal()) != null) { 950 sec = sml.getAssociatedSection((SignalMast) getSignal()); 951 } else { 952 sec = InstanceManager.getDefault(SectionManager.class).getSection(src.getPoint().getDisplayName() + ":" + point.getDisplayName()); 953 } 954 } 955 if (sec != null) { 956 if (!df.removeFromActiveTrainPath(sec, at, src.getPoint().getPanel())) { 957 log.error("Unable to remove allocation from dispathcer, leave interlock in place"); // NOI18N 958 src.pd.cancelNXButtonTimeOut(); 959 point.cancelNXButtonTimeOut(); 960 src.getPoint().getPanel().getGlassPane().setVisible(false); 961 return; 962 } 963 if (sec.getSectionType() == Section.DYNAMICADHOC) { 964 sec.removeAllBlocksFromSection(); 965 } 966 } 967 } 968 } 969 } 970 src.setMenuEnabled(false); 971 if (src.sourceSignal instanceof SignalMast) { 972 SignalMast mast = (SignalMast) src.sourceSignal; 973 mast.setAspect(mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER)); 974 if (!manager.isAbsSignalMode()) { 975 mast.setHeld(true); 976 } 977 } else if (src.sourceSignal instanceof SignalHead) { 978 SignalHead head = (SignalHead) src.sourceSignal; 979 if (!manager.isAbsSignalMode()) { 980 head.setHeld(true); 981 } 982 } else { 983 log.debug("No signal found"); // NOI18N 984 } 985 986 //Get rid of the signal mast logic to the destination mast. 987 synchronized (this) { 988 if ((getSignal() instanceof SignalMast) && (sml != null)) { 989 SignalMast mast = (SignalMast) getSignal(); 990 if (sml.getStoreState(mast) == SignalMastLogic.STORENONE) { 991 sml.removeDestination(mast); 992 } 993 } 994 sml = null; 995 } 996 997 if (routeDetails == null) { 998 return; 999 } 1000 1001 // The block list for an interlocking NX still has the facing block if there are no signals. 1002 LayoutBlock facing = getSource().getStart(); 1003 for (LayoutBlock blk : routeDetails) { 1004 if (blk == facing) { 1005 // Skip the facing block if it is still in the block list. 1006 continue; 1007 } 1008 if ((getEntryExitType() == EntryExitPairs.FULLINTERLOCK)) { 1009 blk.setUseExtraColor(false); 1010 } 1011 blk.getBlock().removePropertyChangeListener(propertyBlockListener); // was set against occupancy sensor 1012 } 1013 1014 if (cancelClear == EntryExitPairs.CLEARROUTE) { 1015 if (routeDetails.isEmpty()) { 1016 if (log.isDebugEnabled()) { 1017 log.debug("{} all blocks have automatically been cleared down", getUserName()); // NOI18N 1018 } 1019 } else { 1020 if (log.isDebugEnabled()) { 1021 log.debug("{} No blocks were cleared down {}", getUserName(), routeDetails.size()); // NOI18N 1022 } 1023 try { 1024 if (log.isDebugEnabled()) { 1025 log.debug("{} set first block as active so that we can manually clear this down {}", getUserName(), routeDetails.get(0).getBlock().getUserName()); // NOI18N 1026 } 1027 if (routeDetails.get(0).getOccupancySensor() != null) { 1028 routeDetails.get(0).getOccupancySensor().setState(Sensor.ACTIVE); 1029 } else { 1030 routeDetails.get(0).getBlock().goingActive(); 1031 } 1032 1033 if (src.getStart().getOccupancySensor() != null) { 1034 src.getStart().getOccupancySensor().setState(Sensor.INACTIVE); 1035 } else { 1036 src.getStart().getBlock().goingInactive(); 1037 } 1038 } catch (java.lang.NullPointerException e) { 1039 log.error("error in clear route A", e); // NOI18N 1040 } catch (JmriException e) { 1041 log.error("error in clear route A", e); // NOI18N 1042 } 1043 if (log.isDebugEnabled()) { 1044 log.debug("{} Going to clear routeDetails down {}", getUserName(), routeDetails.size()); // NOI18N 1045 for (int i = 0; i < routeDetails.size(); i++) { 1046 log.debug("Block at {} {}", i, routeDetails.get(i).getDisplayName()); 1047 } 1048 } 1049 if (routeDetails.size() > 1) { 1050 //We will remove the propertychange listeners on the sensors as we will now manually clear things down. 1051 //Should we just be usrc.pdating the block status and not the sensor 1052 for (int i = 1; i < routeDetails.size() - 1; i++) { 1053 if (log.isDebugEnabled()) { 1054 log.debug("{} in loop Set active {} {}", getUserName(), routeDetails.get(i).getDisplayName(), routeDetails.get(i).getBlock().getSystemName()); // NOI18N 1055 } 1056 try { 1057 if (routeDetails.get(i).getOccupancySensor() != null) { 1058 routeDetails.get(i).getOccupancySensor().setState(Sensor.ACTIVE); 1059 } else { 1060 routeDetails.get(i).getBlock().goingActive(); 1061 } 1062 1063 if (log.isDebugEnabled()) { 1064 log.debug("{} in loop Set inactive {} {}", getUserName(), routeDetails.get(i - 1).getDisplayName(), routeDetails.get(i - 1).getBlock().getSystemName()); // NOI18N 1065 } 1066 if (routeDetails.get(i - 1).getOccupancySensor() != null) { 1067 routeDetails.get(i - 1).getOccupancySensor().setState(Sensor.INACTIVE); 1068 } else { 1069 routeDetails.get(i - 1).getBlock().goingInactive(); 1070 } 1071 } catch (NullPointerException | JmriException e) { 1072 log.error("error in clear route b ", e); // NOI18N 1073 } 1074 // NOI18N 1075 1076 } 1077 try { 1078 if (log.isDebugEnabled()) { 1079 log.debug("{} out of loop Set active {} {}", getUserName(), routeDetails.get(routeDetails.size() - 1).getDisplayName(), routeDetails.get(routeDetails.size() - 1).getBlock().getSystemName()); // NOI18N 1080 } 1081 //Get the last block an set it active. 1082 if (routeDetails.get(routeDetails.size() - 1).getOccupancySensor() != null) { 1083 routeDetails.get(routeDetails.size() - 1).getOccupancySensor().setState(Sensor.ACTIVE); 1084 } else { 1085 routeDetails.get(routeDetails.size() - 1).getBlock().goingActive(); 1086 } 1087 if (log.isDebugEnabled()) { 1088 log.debug("{} out of loop Set inactive {} {}", getUserName(), routeDetails.get(routeDetails.size() - 2).getUserName(), routeDetails.get(routeDetails.size() - 2).getBlock().getSystemName()); // NOI18N 1089 } 1090 if (routeDetails.get(routeDetails.size() - 2).getOccupancySensor() != null) { 1091 routeDetails.get(routeDetails.size() - 2).getOccupancySensor().setState(Sensor.INACTIVE); 1092 } else { 1093 routeDetails.get(routeDetails.size() - 2).getBlock().goingInactive(); 1094 } 1095 } catch (java.lang.NullPointerException e) { 1096 log.error("error in clear route c", e); // NOI18N 1097 } catch (java.lang.ArrayIndexOutOfBoundsException e) { 1098 log.error("error in clear route c", e); // NOI18N 1099 } catch (JmriException e) { 1100 log.error("error in clear route c", e); // NOI18N 1101 } 1102 } 1103 } 1104 } 1105 setActiveEntryExit(false); 1106 setRouteFrom(false); 1107 setRouteTo(false); 1108 synchronized (this) { 1109 routeDetails = null; 1110 lastSeenActiveBlockObject = null; 1111 } 1112 src.pd.cancelNXButtonTimeOut(); 1113 point.cancelNXButtonTimeOut(); 1114 src.getPoint().getPanel().getGlassPane().setVisible(false); 1115 1116 } 1117 1118 public void setInterlockRoute(boolean reverseDirection) { 1119 if (activeEntryExit) { 1120 return; 1121 } 1122 activeBean(reverseDirection, false); 1123 } 1124 1125 void activeBean(boolean reverseDirection) { 1126 activeBean(reverseDirection, true); 1127 } 1128 1129 synchronized void activeBean(boolean reverseDirection, boolean showMessage) { 1130 routeChangeStarted = false; 1131 setOrClearRoute(reverseDirection, showMessage); 1132 if (!routeChangeStarted) { 1133 // The route is neither being set nor cleared down, so a route over several pairs 1134 // must not go on to its next pair 1135 manager.routeSettingDone(this, false); 1136 } 1137 } 1138 1139 /** 1140 * True when the route is being set or cleared down. The result is then 1141 * reported to the manager when that has finished. 1142 */ 1143 private boolean routeChangeStarted = false; 1144 1145 private void setOrClearRoute(boolean reverseDirection, boolean showMessage) { 1146 // Clear any previous memory message 1147 MemoryManager mgr = InstanceManager.getDefault(MemoryManager.class); 1148 Memory nxMem = mgr.getMemory(manager.getMemoryOption()); 1149 if (nxMem != null) { 1150 nxMem.setValue(""); 1151 } 1152 1153 if (!isEnabled()) { 1154 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("RouteDisabled", getDisplayName())); // NOI18N 1155 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1156 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1157 return; 1158 } 1159 if (activeEntryExit) { 1160 // log.debug(getUserName() + " Our route is active so this would go for a clear down but we need to check that the we can clear it down" + activeEndPoint); 1161 if (!isEnabled()) { 1162 log.debug("A disabled entry exit has been called will bomb out"); // NOI18N 1163 return; 1164 } 1165 log.debug("{} We have a valid match on our end point so we can clear down", getUserName()); // NOI18N 1166 //setRouteTo(false); 1167 //src.pd.setRouteFrom(false); 1168 routeChangeStarted = true; 1169 setRoute(false); 1170 } else { 1171 if (isRouteToPointSet()) { 1172 log.debug("{} route to this point is set therefore can not set another to it ", getUserName()); // NOI18N 1173 if (showMessage && !manager.isRouteStacked(this, false)) { 1174 handleNoCurrentRoute(reverseDirection, "Route already set to the destination point"); // NOI18N 1175 } 1176 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1177 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1178 return; 1179 } else { 1180 LayoutBlock startlBlock = src.getStart(); 1181 1182 List<BestPath> pathList = new ArrayList<>(2); 1183 LayoutBlock protectLBlock; 1184 LayoutBlock destinationLBlock; 1185 //Need to work out around here the best one. 1186 for (LayoutBlock srcProLBlock : src.getSourceProtecting()) { 1187 protectLBlock = srcProLBlock; 1188 if (!reverseDirection) { 1189 //We have a problem, the destination point is already setup with a route, therefore we would need to 1190 //check some how that a route hasn't been set to it. 1191 destinationLBlock = getFacing(); 1192 List<LayoutBlock> blocks = new ArrayList<>(); 1193 String errorMessage = null; 1194 try { 1195 blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR); 1196 } catch (Exception e) { 1197 errorMessage = e.getMessage(); 1198 //can be considered normal if no free route is found 1199 } 1200 BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks); 1201 toadd.setErrorMessage(errorMessage); 1202 pathList.add(toadd); 1203 } else { 1204 // Handle reversed direction - Only used when Both Way is enabled. 1205 // The controlling block references are flipped 1206 startlBlock = point.getProtecting().get(0); 1207 protectLBlock = point.getFacing(); 1208 1209 destinationLBlock = src.getSourceProtecting().get(0); 1210 if (log.isDebugEnabled()) { 1211 log.debug("reverse set destination is set going for {} {} {}", startlBlock.getDisplayName(), destinationLBlock.getDisplayName(), protectLBlock.getDisplayName()); // NOI18N 1212 } 1213 try { 1214 LayoutBlock srcPro = src.getSourceProtecting().get(0); //Don't care what block the facing is protecting 1215 //Need to add a check for the lengths of the returned lists, then choose the most appropriate 1216 if (!InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(startlBlock, protectLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) { 1217 startlBlock = getFacing(); 1218 protectLBlock = srcProLBlock; 1219 if (log.isDebugEnabled()) { 1220 log.debug("That didn't work so try {} {} {}", startlBlock.getDisplayName(), destinationLBlock.getDisplayName(), protectLBlock.getDisplayName()); // NOI18N 1221 } 1222 if (!InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(startlBlock, protectLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) { 1223 log.error("No route found"); // NOI18N 1224 JmriJOptionPane.showMessageDialog(null, "No Valid path found"); // NOI18N 1225 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1226 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1227 return; 1228 } else { 1229 List<LayoutBlock> blocks = new ArrayList<>(); 1230 String errorMessage = null; 1231 try { 1232 blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR); 1233 } catch (Exception e) { 1234 errorMessage = e.getMessage(); 1235 //can be considered normal if no free route is found 1236 } 1237 BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks); 1238 toadd.setErrorMessage(errorMessage); 1239 pathList.add(toadd); 1240 } 1241 } else if (InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(getFacing(), srcProLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) { 1242 //Both paths are valid, so will go for setting the shortest 1243 int distance = startlBlock.getBlockHopCount(destinationLBlock.getBlock(), protectLBlock.getBlock()); 1244 int distance2 = getFacing().getBlockHopCount(destinationLBlock.getBlock(), srcProLBlock.getBlock()); 1245 if (distance > distance2) { 1246 //The alternative route is shorter we shall use that 1247 startlBlock = getFacing(); 1248 protectLBlock = srcProLBlock; 1249 } 1250 List<LayoutBlock> blocks = new ArrayList<>(); 1251 String errorMessage = ""; 1252 try { 1253 blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.NONE); 1254 } catch (Exception e) { 1255 //can be considered normal if no free route is found 1256 errorMessage = e.getMessage(); 1257 } 1258 BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks); 1259 toadd.setErrorMessage(errorMessage); 1260 pathList.add(toadd); 1261 } else { 1262 List<LayoutBlock> blocks = new ArrayList<>(); 1263 String errorMessage = ""; 1264 try { 1265 blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.NONE); 1266 } catch (Exception e) { 1267 //can be considered normal if no free route is found 1268 errorMessage = e.getMessage(); 1269 } 1270 BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks); 1271 toadd.setErrorMessage(errorMessage); 1272 pathList.add(toadd); 1273 } 1274 } catch (JmriException ex) { 1275 log.error("Exception {}", ex.getMessage()); // NOI18N 1276 if (showMessage) { 1277 JmriJOptionPane.showMessageDialog(null, ex.getMessage()); 1278 } 1279 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1280 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1281 return; 1282 } 1283 } 1284 } 1285 if (pathList.isEmpty()) { 1286 log.debug("Path list empty so exiting"); // NOI18N 1287 return; 1288 } 1289 BestPath pathToUse = null; 1290 if (pathList.size() == 1) { 1291 if (!pathList.get(0).getListOfBlocks().isEmpty()) { 1292 pathToUse = pathList.get(0); 1293 } 1294 } else { 1295 /*Need to filter out the remaining routes, in theory this should only ever be two. 1296 We simply pick at this stage the one with the least number of blocks as being preferred. 1297 This could be expanded at some stage to look at either the length or the metric*/ 1298 int noOfBlocks = 0; 1299 for (BestPath bp : pathList) { 1300 if (!bp.getListOfBlocks().isEmpty()) { 1301 if (noOfBlocks == 0 || bp.getListOfBlocks().size() < noOfBlocks) { 1302 noOfBlocks = bp.getListOfBlocks().size(); 1303 pathToUse = bp; 1304 } 1305 } 1306 } 1307 } 1308 if (pathToUse == null) { 1309 //No valid paths found so will quit 1310 if (pathList.get(0).getListOfBlocks().isEmpty()) { 1311 if (showMessage) { 1312 //Considered normal if not a valid through path, provide an option to stack 1313 handleNoCurrentRoute(reverseDirection, pathList.get(0).getErrorMessage()); 1314 src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1315 point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE); 1316 } 1317 return; 1318 } 1319 pathToUse = pathList.get(0); 1320 } 1321 startlBlock = pathToUse.getStartBlock(); 1322 protectLBlock = pathToUse.getProtectingBlock(); 1323 destinationLBlock = pathToUse.getDestinationBlock(); 1324 routeDetails = pathToUse.getListOfBlocks(); 1325 1326 synchronized (this) { 1327 destination = destinationLBlock; 1328 } 1329 1330 if (log.isDebugEnabled()) { 1331 log.debug("[activeBean] Path chosen start = {}, dest = {}, protect = {}", startlBlock.getDisplayName(), // NOI18N 1332 destinationLBlock.getDisplayName(), protectLBlock.getDisplayName()); 1333 for (LayoutBlock blk : routeDetails) { 1334 log.debug(" block {}", blk.getDisplayName()); 1335 } 1336 } 1337 1338 if (getEntryExitType() == EntryExitPairs.FULLINTERLOCK) { 1339 setActiveEntryExit(true, reverseDirection); 1340 } 1341 1342 routeChangeStarted = true; 1343 // The route is set in steps on the layout thread, so this does not wait for the turnouts 1344 log.debug("[activeBean] Start setRoute, dp = {}", getUserName()); 1345 try { 1346 setRoute(true); 1347 } catch (Exception e) { 1348 log.error("[activeBean] setRoute exception: {}", e.getMessage()); 1349 manager.routeSettingDone(this, false); 1350 } 1351 } 1352 } 1353 } 1354 1355 private static class BestPath { 1356 1357 LayoutBlock srcProtecting = null; 1358 LayoutBlock srcStart = null; 1359 LayoutBlock destination = null; 1360 1361 BestPath(LayoutBlock startPro, LayoutBlock sourceProtecting, LayoutBlock destinationBlock, List<LayoutBlock> blocks) { 1362 srcStart = startPro; 1363 srcProtecting = sourceProtecting; 1364 destination = destinationBlock; 1365 listOfBlocks = blocks; 1366 } 1367 1368 LayoutBlock getStartBlock() { 1369 return srcStart; 1370 } 1371 1372 LayoutBlock getProtectingBlock() { 1373 return srcProtecting; 1374 } 1375 1376 LayoutBlock getDestinationBlock() { 1377 return destination; 1378 } 1379 1380 List<LayoutBlock> listOfBlocks = new ArrayList<>(0); 1381 String errorMessage = ""; 1382 1383 List<LayoutBlock> getListOfBlocks() { 1384 return listOfBlocks; 1385 } 1386 1387 void setErrorMessage(String msg) { 1388 errorMessage = msg; 1389 } 1390 1391 String getErrorMessage() { 1392 return errorMessage; 1393 } 1394 } 1395 1396 void handleNoCurrentRoute(boolean reverse, String message) { 1397 int opt = manager.getOverlapOption(); 1398 1399 if (opt == EntryExitPairs.PROMPTUSER) { 1400 Object[] options = { 1401 Bundle.getMessage("ButtonYes"), // NOI18N 1402 Bundle.getMessage("ButtonNo")}; // NOI18N 1403 int ans = JmriJOptionPane.showOptionDialog(null, 1404 message + "\n" + Bundle.getMessage("StackRouteAsk"), Bundle.getMessage("RouteNotClear"), // NOI18N 1405 JmriJOptionPane.DEFAULT_OPTION, 1406 JmriJOptionPane.QUESTION_MESSAGE, 1407 null, 1408 options, 1409 options[1]); 1410 if (ans == 0) { // array position 0 Yes 1411 opt = EntryExitPairs.OVERLAP_STACK; 1412 } else { // array position 1 or Dialog closed 1413 opt = EntryExitPairs.OVERLAP_CANCEL; 1414 } 1415 } 1416 1417 if (opt == EntryExitPairs.OVERLAP_STACK) { 1418 manager.stackNXRoute(this, reverse); 1419 firePropertyChange(PROPERTY_STACKED, null, null); 1420 } else { 1421 firePropertyChange(PROPERTY_FAILED, null, null); 1422 } 1423 1424 // Set memory value if requested 1425 MemoryManager mgr = InstanceManager.getDefault(MemoryManager.class); 1426 Memory nxMem = mgr.getMemory(manager.getMemoryOption()); 1427 if (nxMem != null) { 1428 String optString = (opt == EntryExitPairs.OVERLAP_STACK) 1429 ? Bundle.getMessage("StackRoute") // NOI18N 1430 : Bundle.getMessage("CancelRoute"); // NOI18N 1431 nxMem.setValue(Bundle.getMessage("MemoryMessage", message, optString)); // NOI18N 1432 1433 // Check for auto memory clear delay 1434 int delay = manager.getMemoryClearDelay() * 1000; 1435 if (delay > 0) { 1436 javax.swing.Timer memoryClear = new javax.swing.Timer(delay, new java.awt.event.ActionListener() { 1437 @Override 1438 public void actionPerformed(java.awt.event.ActionEvent e) { 1439 nxMem.setValue(""); 1440 } 1441 }); 1442 memoryClear.setRepeats(false); 1443 memoryClear.start(); 1444 } 1445 } 1446 } 1447 1448 @Override 1449 public void dispose() { 1450 enabled = false; 1451 setActiveEntryExit(false); 1452 cancelClearInterlock(EntryExitPairs.CANCELROUTE); 1453 setRouteFrom(false); 1454 setRouteTo(false); 1455 point.removeDestination(this); 1456 synchronized (this) { 1457 lastSeenActiveBlockObject = null; 1458 } 1459 disposed = true; 1460 super.dispose(); 1461 } 1462 1463 @Override 1464 public int getState() { 1465 if (activeEntryExit) { 1466 return 0x02; 1467 } 1468 return 0x04; 1469 } 1470 1471 public boolean isActive() { 1472 return activeEntryExit; 1473 } 1474 1475 public boolean isReversed() { 1476 return activeEntryExitReversed; 1477 } 1478 1479 public boolean isUniDirection() { 1480 return uniDirection; 1481 } 1482 1483 @Override 1484 public void setState(int state) { 1485 } 1486 1487 protected void setActiveEntryExit(boolean boo) { 1488 setActiveEntryExit(boo, false); 1489 } 1490 1491 protected void setActiveEntryExit(boolean boo, boolean reversed) { 1492 int oldvalue = getState(); 1493 activeEntryExit = boo; 1494 activeEntryExitReversed = reversed; 1495 src.setMenuEnabled(boo); 1496 firePropertyChange(PROPERTY_ACTIVE, oldvalue, getState()); 1497 } 1498 1499 @Override 1500 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 1501 List<NamedBeanUsageReport> report = new ArrayList<>(); 1502 if (bean != null) { 1503 if (bean.equals(getSource().getPoint().getSensor())) { 1504 report.add(new NamedBeanUsageReport("EntryExitSourceSensor")); // NOI18N 1505 } 1506 if (bean.equals(getSource().getPoint().getSignal())) { 1507 report.add(new NamedBeanUsageReport("EntryExitSourceSignal")); // NOI18N 1508 } 1509 if (bean.equals(getDestPoint().getSensor())) { 1510 report.add(new NamedBeanUsageReport("EntryExitDestinationSensor")); // NOI18N 1511 } 1512 if (bean.equals(getDestPoint().getSignal())) { 1513 report.add(new NamedBeanUsageReport("EntryExitDesinationSignal")); // NOI18N 1514 } 1515 } 1516 return report; 1517 } 1518 1519 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DestinationPoints.class); 1520 1521}