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