001package jmri.jmrit.entryexit; 002 003import java.awt.Color; 004import java.awt.event.MouseAdapter; 005import java.awt.event.MouseEvent; 006import java.beans.PropertyChangeEvent; 007import java.beans.PropertyChangeListener; 008import java.beans.PropertyVetoException; 009import java.util.*; 010import java.util.Map.Entry; 011 012import javax.annotation.CheckReturnValue; 013import javax.annotation.Nonnull; 014import javax.annotation.OverridingMethodsMustInvokeSuper; 015import javax.swing.JDialog; 016import javax.swing.JPanel; 017 018import jmri.*; 019import jmri.beans.VetoableChangeSupport; 020import jmri.jmrit.display.EditorManager; 021import jmri.jmrit.display.layoutEditor.LayoutBlock; 022import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools; 023import jmri.jmrit.display.layoutEditor.LayoutBlockManager; 024import jmri.jmrit.display.layoutEditor.LayoutEditor; 025import jmri.jmrix.internal.InternalSystemConnectionMemo; 026import jmri.util.swing.JmriJOptionPane; 027 028/** 029 * Implements an Entry Exit based method of setting turnouts, setting up signal 030 * logic and allocating blocks through a path based on the Layout Editor. 031 * <p> 032 * The route is based upon having a sensor assigned at a known location on the 033 * panel (set at the boundary of two different blocks) through to a sensor at a 034 * remote location on the same panel. Using the layout block routing, a path can 035 * then be set between the two sensors so long as one exists and no 036 * section of track is set occupied. If available an alternative route will be 037 * used when the direct path is occupied (blocked). 038 * <p> 039 * Initial implementation only handles the setting up of turnouts on a path. 040 * 041 * @author Kevin Dickerson Copyright (C) 2011 042 */ 043public class EntryExitPairs extends VetoableChangeSupport implements Manager<DestinationPoints>, jmri.InstanceManagerAutoDefault, 044 PropertyChangeListener { 045 046 public LayoutBlockConnectivityTools.Metric routingMethod = LayoutBlockConnectivityTools.Metric.METRIC; 047 048 public static final int NXBUTTONSELECTED = 0x08; 049 public static final int NXBUTTONACTIVE = Sensor.ACTIVE; 050 public static final int NXBUTTONINACTIVE = Sensor.INACTIVE; 051 private final SystemConnectionMemo memo; 052 private final Map<String, Boolean> silencedProperties = new HashMap<>(); 053 054 private int settingTimer = 2000; 055 056 public int getSettingTimer() { 057 return settingTimer; 058 } 059 060 public void setSettingTimer(int i) { 061 settingTimer = i; 062 } 063 064 private Color settingRouteColor = null; 065 066 public boolean useDifferentColorWhenSetting() { 067 return (settingRouteColor != null); 068 } 069 070 public Color getSettingRouteColor() { 071 return settingRouteColor; 072 } 073 074 public void setSettingRouteColor(Color col) { 075 settingRouteColor = col; 076 } 077 078 /** 079 * Constant value to represent that the entryExit will only set up the 080 * turnouts between two different points. 081 */ 082 public static final int SETUPTURNOUTSONLY = 0x00; 083 084 /** 085 * Constant value to represent that the entryExit will set up the turnouts 086 * between two different points and configure the Signal Mast Logic to use 087 * the correct blocks. 088 */ 089 public static final int SETUPSIGNALMASTLOGIC = 0x01; 090 091 /** 092 * Constant value to represent that the entryExit will do full interlocking. 093 * It will set the turnouts and "reserve" the blocks. 094 */ 095 public static final int FULLINTERLOCK = 0x02; 096 097 boolean allocateToDispatcher = false; 098 boolean absSignalMode = false; 099 boolean quietFail = false; // Suppress the dialog when a route allocation fails. 100 boolean skipGuiFix = false; // Skip the thread for setRoute. This prevents double-click chaining. 101 102 public static final int PROMPTUSER = 0x00; 103 public static final int AUTOCLEAR = 0x01; 104 public static final int AUTOCANCEL = 0x02; 105 public static final int AUTOSTACK = 0x03; 106 107 public static final int OVERLAP_CANCEL = 0x01; 108 public static final int OVERLAP_STACK = 0x02; 109 110 /** 111 * String constant for auto generate complete. 112 */ 113 public static final String PROPERTY_AUTO_GENERATE_COMPLETE = "autoGenerateComplete"; 114 115 /** 116 * String constant for active. 117 */ 118 public static final String PROPERTY_ACTIVE = "active"; 119 120 int routeClearOption = PROMPTUSER; 121 int routeOverlapOption = PROMPTUSER; 122 String memoryOption = ""; // Optional memory variable to receive allocation messages 123 int memoryClearDelay = 0; // Delay before clearing memory, 0 for clearing disabled 124 125 static JPanel glassPane = new JPanel(); 126 127 /** 128 * Delay between issuing Turnout commands 129 */ 130 public int turnoutSetDelay = 0; 131 132 /** 133 * Constructor for creating an EntryExitPairs object and create a transparent JPanel for it. 134 */ 135 public EntryExitPairs() { 136 memo = InstanceManager.getDefault(InternalSystemConnectionMemo.class); 137 InstanceManager.getOptionalDefault(ConfigureManager.class).ifPresent(cm -> cm.registerUser(this)); 138 InstanceManager.getDefault(LayoutBlockManager.class).addPropertyChangeListener(propertyBlockManagerListener); 139 140 glassPane.setOpaque(false); 141 glassPane.setLayout(null); 142 glassPane.addMouseListener(new MouseAdapter() { 143 @Override 144 public void mousePressed(MouseEvent e) { 145 e.consume(); 146 } 147 }); 148 } 149 150 public void setDispatcherIntegration(boolean boo) { 151 allocateToDispatcher = boo; 152 } 153 154 public boolean getDispatcherIntegration() { 155 return allocateToDispatcher; 156 } 157 158 public void setAbsSignalMode(boolean absMode) { 159 absSignalMode = absMode; 160 } 161 162 public boolean isAbsSignalMode() { 163 return absSignalMode; 164 } 165 166 public void setQuietFail(boolean quiet) { 167 quietFail = quiet; 168 } 169 170 public boolean isQuietFail() { 171 return quietFail; 172 } 173 174 public void setSkipGuiFix(boolean skip) { 175 skipGuiFix = skip; 176 } 177 178 public boolean isSkipGuiFix() { 179 return skipGuiFix; 180 } 181 182 /** 183 * Get the transparent JPanel for this EntryExitPairs. 184 * @return JPanel overlay 185 */ 186 public JPanel getGlassPane() { 187 return glassPane; 188 } 189 190 HashMap<PointDetails, Source> nxpair = new HashMap<>(); 191 192 public void addNXSourcePoint(LayoutBlock facing, List<LayoutBlock> protecting, NamedBean loc, LayoutEditor panel) { 193 PointDetails point = providePoint(facing, protecting, panel); 194 point.setRefObject(loc); 195 } 196 197 public void addNXSourcePoint(NamedBean source) { 198 PointDetails point = null; 199 for (LayoutEditor editor : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 200 point = providePoint(source, editor); 201 } 202 if (point == null) { 203 log.error("Unable to find a location on any panel for item {}", source.getDisplayName()); // NOI18N 204 } 205 } 206 207 public void addNXSourcePoint(NamedBean source, LayoutEditor panel) { 208 if (source == null) { 209 log.error("source bean supplied is null"); // NOI18N 210 return; 211 } 212 if (panel == null) { 213 log.error("panel supplied is null"); // NOI18N 214 return; 215 } 216 PointDetails point; 217 point = providePoint(source, panel); 218 if (point == null) { 219 log.error("Unable to find a location on the panel {} for item {}", 220 panel.getLayoutName(), source.getDisplayName()); 221 } 222 } 223 224 public Object getEndPointLocation(NamedBean source, LayoutEditor panel) { 225 if (source == null) { 226 log.error("Source bean past is null"); // NOI18N 227 return null; 228 } 229 if (panel == null) { 230 log.error("panel passed is null"); // NOI18N 231 return null; 232 } 233 PointDetails sourcePoint = getPointDetails(source, panel); 234 if (sourcePoint == null) { 235 log.error("Point is not located"); // NOI18N 236 return null; 237 } 238 return sourcePoint.getRefLocation(); 239 } 240 241 /** {@inheritDoc} */ 242 @Override 243 public int getXMLOrder() { 244 return ENTRYEXIT; 245 } 246 247 /** {@inheritDoc} */ 248 @Override 249 public DestinationPoints getBySystemName(String systemName) { 250 for (Source e : nxpair.values()) { 251 DestinationPoints pd = e.getByUniqueId(systemName); 252 if (pd != null) { 253 return pd; 254 } 255 } 256 return null; 257 } 258 259 /** {@inheritDoc} */ 260 @Override 261 public DestinationPoints getByUserName(@Nonnull String userName) { 262 for (Source e : nxpair.values()) { 263 DestinationPoints pd = e.getByUserName(userName); 264 if (pd != null) { 265 return pd; 266 } 267 } 268 return null; 269 } 270 271 /** {@inheritDoc} */ 272 @Override 273 public DestinationPoints getNamedBean(@Nonnull String name) { 274 DestinationPoints b = getByUserName(name); 275 if (b != null) { 276 return b; 277 } 278 return getBySystemName(name); 279 } 280 281 /** {@inheritDoc} */ 282 @Nonnull 283 @Override 284 public SystemConnectionMemo getMemo() { 285 return memo; 286 } 287 288 /** {@inheritDoc} */ 289 @Override 290 @Nonnull 291 public String getSystemPrefix() { 292 return memo.getSystemPrefix(); 293 } 294 295 /** {@inheritDoc} */ 296 @Override 297 public char typeLetter() { 298 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 299 } 300 301 /** {@inheritDoc} */ 302 @Override 303 @Nonnull 304 public String makeSystemName(@Nonnull String s) { 305 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 306 } 307 308 /** {@inheritDoc} */ 309 @Override 310 @CheckReturnValue 311 public int getObjectCount() { 312 return getNamedBeanSet().size(); 313 } 314 315 /** 316 * Implemented to support the Conditional combo box name list 317 * @since 4.9.3 318 * @return a list of Destination Point beans 319 */ 320 @Override 321 @Nonnull 322 public SortedSet<DestinationPoints> getNamedBeanSet() { 323 TreeSet<DestinationPoints> beanList = new TreeSet<>(new jmri.util.NamedBeanComparator<>()); 324 for (Source e : nxpair.values()) { 325 List<String> uidList = e.getDestinationUniqueId(); 326 for (String uid : uidList) { 327 beanList.add(e.getByUniqueId(uid)); 328 } 329 } 330 return beanList; 331 } 332 333 /** {@inheritDoc} */ 334 @Override 335 public void register(@Nonnull DestinationPoints n) { 336 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 337 } 338 339 /** {@inheritDoc} */ 340 @Override 341 public void deregister(@Nonnull DestinationPoints n) { 342 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 343 } 344 345 public void setClearDownOption(int i) { 346 routeClearOption = i; 347 } 348 349 public int getClearDownOption() { 350 return routeClearOption; 351 } 352 353 public void setOverlapOption(int i) { 354 routeOverlapOption = i; 355 } 356 357 public int getOverlapOption() { 358 return routeOverlapOption; 359 } 360 361 public void setMemoryOption(String memoryName) { 362 memoryOption = memoryName; 363 } 364 365 public String getMemoryOption() { 366 return memoryOption; 367 } 368 369 public void setMemoryClearDelay(int secs) { 370 memoryClearDelay = secs; 371 } 372 373 public int getMemoryClearDelay() { 374 return memoryClearDelay; 375 } 376 377 /** {@inheritDoc} */ 378 @Override 379 public void dispose() { 380 InstanceManager.getDefault(LayoutBlockManager.class).removePropertyChangeListener(propertyBlockManagerListener); 381 } 382 383 /** 384 * Generate the point details, given a known source and a 385 * Layout Editor panel. 386 * 387 * @param source Origin of movement 388 * @param panel A Layout Editor panel 389 * @return A PointDetails object 390 */ 391 public PointDetails providePoint(NamedBean source, LayoutEditor panel) { 392 PointDetails sourcePoint = getPointDetails(source, panel); 393 if (sourcePoint == null) { 394 LayoutBlock facing = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getFacingBlockByNamedBean(source, null); 395 List<LayoutBlock> protecting = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getProtectingBlocksByNamedBean(source, null); 396// log.info("facing = {}, protecting = {}", facing, protecting); 397 if (facing == null && protecting.isEmpty()) { 398 log.error("Unable to find facing and protecting blocks"); // NOI18N 399 return null; 400 } 401 sourcePoint = providePoint(facing, protecting, panel); 402 sourcePoint.setRefObject(source); 403 } 404 return sourcePoint; 405 } 406 407 /** 408 * Return a list of all source (origin) points on a given 409 * Layout Editor panel. 410 * 411 * @param panel A Layout Editor panel 412 * @return A list of source objects 413 */ 414 public List<Object> getSourceList(LayoutEditor panel) { 415 List<Object> list = new ArrayList<>(); 416 417 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 418 Object obj = (e.getKey()).getRefObject(); 419 LayoutEditor pan = (e.getKey()).getPanel(); 420 if (pan == panel && !list.contains(obj)) { 421 list.add(obj); 422 } 423 } 424 return list; 425 } 426 427 public Source getSourceForPoint(PointDetails pd) { 428 return nxpair.get(pd); 429 } 430 431 public int getNxPairNumbers(LayoutEditor panel) { 432 int total = 0; 433 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 434 PointDetails key = e.getKey(); 435 LayoutEditor pan = key.getPanel(); 436 if (pan == panel) { 437 total += e.getValue().getNumberOfDestinations(); 438 } // end while 439 } 440 441 return total; 442 } 443 444 /** 445 * Set a reversed route between two points. Special case to support a LogixNG action. 446 * @since 5.5.7 447 * @param nxPair The system or user name of the destination point. 448 */ 449 public void setReversedRoute(String nxPair) { 450 DestinationPoints dp = getNamedBean(nxPair); 451 if (dp != null) { 452 String destUUID = dp.getUniqueId(); 453 nxpair.forEach((pd, src) -> { 454 for (String srcUUID : src.getDestinationUniqueId()) { 455 if (destUUID.equals(srcUUID)) { 456 log.debug("Found the correct reverse route source: src = {}, dest = {}", 457 pd.getSensor().getDisplayName(), dp.getDestPoint().getSensor().getDisplayName()); 458 refCounter++; 459 routesToSet.add(new SourceToDest(src, dp, true, refCounter)); 460 processRoutesToSet(); 461 return; 462 } 463 } 464 }); 465 } 466 } 467 468 /** 469 * Set the route between the two points represented by the Destination Point name. 470 * 471 * @since 4.11.1 472 * @param nxPair The system or user name of the destination point. 473 */ 474 public void setSingleSegmentRoute(String nxPair) { 475 DestinationPoints dp = getNamedBean(nxPair); 476 if (dp != null) { 477 String destUUID = dp.getUniqueId(); 478 nxpair.forEach((pd, src) -> { 479 for (String srcUUID : src.getDestinationUniqueId()) { 480 if (destUUID.equals(srcUUID)) { 481 log.debug("Found the correct source: src = {}, dest = {}", 482 pd.getSensor().getDisplayName(), dp.getDestPoint().getSensor().getDisplayName()); 483 setMultiPointRoute(pd, dp.getDestPoint()); 484 return; 485 } 486 } 487 }); 488 } 489 } 490 491 public void setMultiPointRoute(PointDetails requestpd, LayoutEditor panel) { 492 for (PointDetails pd : pointDetails) { 493 if ( pd != requestpd && pd.getNXState() == NXBUTTONSELECTED ) { 494 setMultiPointRoute(pd, requestpd); 495 return; 496 } 497 } 498 } 499 500 private void setMultiPointRoute(PointDetails fromPd, PointDetails toPd) { 501 log.debug("[setMultiPointRoute] Start, from = {}, to = {}", fromPd.getSensor().getDisplayName(), toPd.getSensor().getDisplayName()); 502 boolean cleardown = false; 503 if (fromPd.isRouteFromPointSet() && toPd.isRouteToPointSet()) { 504 cleardown = true; 505 } 506 for (LayoutBlock pro : fromPd.getProtecting()) { 507 try { 508 jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault( 509 jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 510 LayoutBlock toProt = null; 511 if (!toPd.getProtecting().isEmpty()) { 512 toProt = toPd.getProtecting().get(0); 513 } 514 log.trace("Calling checkValidDest"); 515 boolean result = lbm.getLayoutBlockConnectivityTools().checkValidDest( 516 fromPd.getFacing(), pro, toPd.getFacing(), toProt, 517 LayoutBlockConnectivityTools.Routing.NONE); // Was SENSORTOSENSOR, needs to be consistent with three lines below 518 // See also around line 353 in LayoutBlockConnectivityTools 519 log.trace(" checkValidDest returned {}", result); 520 if (result) { 521 List<LayoutBlock> blkList = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(fromPd.getFacing(), toPd.getFacing(), pro, cleardown, LayoutBlockConnectivityTools.Routing.NONE); 522 if (!blkList.isEmpty()) { 523 if (log.isDebugEnabled()) { 524 log.debug("[setMultiPointRoute] blocks and sensors"); 525 for (LayoutBlock blk : blkList) { 526 log.debug(" blk = {}", blk.getDisplayName()); 527 } 528 } 529 List<jmri.NamedBean> beanList = lbm.getLayoutBlockConnectivityTools().getBeansInPath(blkList, null, jmri.Sensor.class); 530 PointDetails fromPoint = fromPd; 531 refCounter++; 532 if (!beanList.isEmpty()) { 533 if (log.isDebugEnabled()) { 534 for (NamedBean xnb : beanList) { 535 log.debug(" sensor = {}", xnb.getDisplayName()); 536 } 537 } 538 for (int i = 1; i < beanList.size(); i++) { 539 NamedBean nb = beanList.get(i); 540 PointDetails cur = getPointDetails(nb, fromPd.getPanel()); 541 Source s = nxpair.get(fromPoint); 542 if (s != null) { 543 routesToSet.add(new SourceToDest(s, s.getDestForPoint(cur), false, refCounter)); 544 } 545 fromPoint = cur; 546 } 547 } 548 Source s = nxpair.get(fromPoint); 549 if (s != null) { 550 if (s.getDestForPoint(toPd) != null) { 551 routesToSet.add(new SourceToDest(s, s.getDestForPoint(toPd), false, refCounter)); 552 } 553 } 554 log.debug("[setMultiPointRoute] Invoke processRoutesToSet"); 555 processRoutesToSet(); 556 log.debug("[setMultiPointRoute] processRoutesToSet is done"); 557 return; 558 } 559 } 560 } catch (jmri.JmriException e) { 561 log.debug("setMultiPointRoute catches exception", e); 562 // Can be considered normal if route is blocked 563 if (!isQuietFail()) { 564 JmriJOptionPane.showMessageDialog(null, 565 Bundle.getMessage("MultiPointBlocked"), // NOI18N 566 Bundle.getMessage("WarningTitle"), // NOI18N 567 JmriJOptionPane.WARNING_MESSAGE); 568 } 569 } 570 } 571 fromPd.setNXButtonState(NXBUTTONINACTIVE); 572 toPd.setNXButtonState(NXBUTTONINACTIVE); 573 log.debug("[setMultiPointRoute] Done, from = {}, to = {}", fromPd.getSensor().getDisplayName(), toPd.getSensor().getDisplayName()); 574 } 575 576 int refCounter = 0; 577 578 /** 579 * List holding SourceToDest sets of routes between two points. 580 */ 581 List<SourceToDest> routesToSet = new ArrayList<>(); 582 583 /** 584 * Class to store NX sets consisting of a source point, a destination point, 585 * a direction and a reference. 586 */ 587 private static class SourceToDest { 588 589 Source s = null; 590 DestinationPoints dp = null; 591 boolean direction = false; 592 int ref = -1; 593 594 /** 595 * Constructor for a SourceToDest element. 596 * 597 * @param s a source point 598 * @param dp a destination point 599 * @param dir a direction 600 * @param ref Integer used as reference 601 */ 602 SourceToDest(Source s, DestinationPoints dp, boolean dir, int ref) { 603 this.s = s; 604 this.dp = dp; 605 this.direction = dir; 606 this.ref = ref; 607 } 608 } 609 610 int currentDealing = 0; 611 612 /** 613 * Activate each SourceToDest set in routesToSet 614 */ 615 synchronized void processRoutesToSet() { 616 if (log.isDebugEnabled()) { 617 log.debug("[processRoutesToSet] Current routesToSet list"); 618 for (SourceToDest sd : routesToSet) { 619 String dpName = (sd.dp == null) ? "- null -" : sd.dp.getDestPoint().getSensor().getDisplayName(); 620 log.debug(" from = {}, to = {}, ref = {}", sd.s.getPoint().getSensor().getDisplayName(), dpName, sd.ref); 621 } 622 } 623 624 if (routesToSet.isEmpty()) { 625 return; 626 } 627 Source s = routesToSet.get(0).s; 628 DestinationPoints dp = routesToSet.get(0).dp; 629 boolean dir = routesToSet.get(0).direction; 630 currentDealing = routesToSet.get(0).ref; 631 routesToSet.remove(0); 632 633 dp.addPropertyChangeListener(propertyDestinationListener); 634 s.activeBean(dp, dir); 635 } 636 637 /** 638 * Remove remaining SourceToDest sets in routesToSet 639 */ 640 synchronized void removeRemainingRoute() { 641 List<SourceToDest> toRemove = new ArrayList<>(); 642 for (SourceToDest rts : routesToSet) { 643 if (rts.ref == currentDealing) { 644 toRemove.add(rts); 645 rts.dp.getDestPoint().setNXButtonState(NXBUTTONINACTIVE); 646 } 647 } 648 for (SourceToDest rts : toRemove) { 649 routesToSet.remove(rts); 650 } 651 } 652 653 protected PropertyChangeListener propertyDestinationListener = new PropertyChangeListener() { 654 @Override 655 public void propertyChange(PropertyChangeEvent e) { 656 ((DestinationPoints) e.getSource()).removePropertyChangeListener(this); 657 if ( DestinationPoints.PROPERTY_ACTIVE.equals(e.getPropertyName())) { 658 processRoutesToSet(); 659 } else if ( DestinationPoints.PROPERTY_STACKED.equals(e.getPropertyName()) 660 || DestinationPoints.PROPERTY_FAILED.equals(e.getPropertyName()) 661 || DestinationPoints.PROPERTY_NO_CHANGE.equals(e.getPropertyName())) { 662 removeRemainingRoute(); 663 } 664 } 665 }; 666 667 private List<Object> destinationList = new ArrayList<>(); 668 669 // Need to sort out the presentation of the name here rather than using the point ID. 670 // This is used for the creation and display of information in the table. 671 // The presentation of the name might have to be done at the table level. 672 public List<Object> getNxSource(LayoutEditor panel) { 673 List<Object> source = new ArrayList<>(); 674 destinationList = new ArrayList<>(); 675 676 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 677 PointDetails key = e.getKey(); 678 LayoutEditor pan = key.getPanel(); 679 if (pan == panel) { 680 List<PointDetails> dest = nxpair.get(key).getDestinationPoints(); 681 for (int i = 0; i < dest.size(); i++) { 682 destinationList.add(dest.get(i).getRefObject()); 683 source.add(key.getRefObject()); 684 } 685 } 686 } 687 return source; 688 } 689 690 public List<Object> getNxDestination() { 691 return destinationList; 692 } 693 694 public List<LayoutEditor> getSourcePanelList() { 695 List<LayoutEditor> list = new ArrayList<>(); 696 697 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 698 PointDetails key = e.getKey(); 699 LayoutEditor pan = key.getPanel(); 700 if (!list.contains(pan)) { 701 list.add(pan); 702 } 703 } 704 return list; 705 } 706 707 /** 708 * Return a point if it already exists, or create a new one if not. 709 */ 710 private PointDetails providePoint(LayoutBlock source, List<LayoutBlock> protecting, LayoutEditor panel) { 711 PointDetails sourcePoint = getPointDetails(source, protecting, panel); 712 if (sourcePoint == null) { 713 sourcePoint = new PointDetails(source, protecting); 714 sourcePoint.setPanel(panel); 715 } 716 return sourcePoint; 717 } 718 719 /** 720 * @since 4.17.4 721 */ 722 @Override 723 public void propertyChange(PropertyChangeEvent evt) { 724 firePropertyChange(PROPERTY_ACTIVE, evt.getOldValue(), evt.getNewValue()); 725 } 726 727 728 public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel) { 729 addNXDestination(source, destination, panel, null); 730 } 731 732 /** 733 * @since 4.17.4 734 * Register in Property Change Listener. 735 * @param source the source bean. 736 * @param destination the destination bean. 737 * @param panel the layout editor panel. 738 * @param id the points details id. 739 */ 740 public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel, String id) { 741 if (source == null) { 742 log.error("no source Object provided"); // NOI18N 743 return; 744 } 745 if (destination == null) { 746 log.error("no destination Object provided"); // NOI18N 747 return; 748 } 749 PointDetails sourcePoint = providePoint(source, panel); 750 if (sourcePoint == null) { 751 log.error("source point for {} not created addNXDes", source.getDisplayName()); // NOI18N 752 return; 753 } 754 755 sourcePoint.setPanel(panel); 756 sourcePoint.setRefObject(source); 757 PointDetails destPoint = providePoint(destination, panel); 758 if (destPoint != null) { 759 destPoint.setPanel(panel); 760 destPoint.setRefObject(destination); 761 destPoint.getSignal(); 762 if (!nxpair.containsKey(sourcePoint)) { 763 Source sp = new Source(sourcePoint); 764 nxpair.put(sourcePoint, sp); 765 sp.removePropertyChangeListener(this); 766 sp.addPropertyChangeListener(this); 767 } 768 nxpair.get(sourcePoint).addDestination(destPoint, id); 769 } 770 771 firePropertyChange(PROPERTY_LENGTH, null, null); 772 } 773 774 public List<Object> getDestinationList(Object obj, LayoutEditor panel) { 775 List<Object> list = new ArrayList<>(); 776 if (nxpair.containsKey(getPointDetails(obj, panel))) { 777 List<PointDetails> from = nxpair.get(getPointDetails(obj, panel)).getDestinationPoints(); 778 for (int i = 0; i < from.size(); i++) { 779 list.add(from.get(i).getRefObject()); 780 } 781 } 782 return list; 783 } 784 785 public void removeNXSensor(Sensor sensor) { 786 log.info("panel maintenance has resulting in the request to remove a sensor: {}", sensor.getDisplayName()); 787 } 788 789 // ============ NX Pair Delete Methods ============ 790 // The request will be for all NX Pairs containing a sensor or 791 // a specific entry and exit sensor pair. 792 793 /** 794 * Entry point to delete all of the NX pairs for a specific sensor. 795 * 1) Build a list of affected NX pairs. 796 * 2) Check for Conditional references. 797 * 3) If no references, do the delete process with user approval. 798 * @since 4.11.2 799 * @param sensor The sensor whose pairs should be deleted. 800 * @return true if the delete was successful. False if prevented by 801 * Conditional/LogixNG references or user choice. 802 */ 803 public boolean deleteNxPair(NamedBean sensor) { 804 if (sensor == null) { 805 log.error("deleteNxPair: sensor is null"); // NOI18N 806 return false; 807 } 808 createDeletePairList(sensor); 809 if (checkNxPairs() && checkLogixNG()) { 810 // No Conditional or LogixNG references. 811 if (confirmDeletePairs()) { 812 deleteNxPairs(); 813 return true; 814 } 815 } 816 return false; 817 } 818 819 /** 820 * Entry point to delete a specific NX pair. 821 * 822 * @since 4.11.2 823 * @param entrySensor The sensor that acts as the entry point. 824 * @param exitSensor The sensor that acts as the exit point. 825 * @param panel The layout editor panel that contains the entry sensor. 826 * @return true if the delete was successful. False if there are Conditional/LogixNG references. 827 */ 828 public boolean deleteNxPair(NamedBean entrySensor, NamedBean exitSensor, LayoutEditor panel) { 829 if (entrySensor == null || exitSensor == null || panel == null) { 830 log.error("deleteNxPair: One or more null inputs"); // NOI18N 831 return false; 832 } 833 834 deletePairList.clear(); 835 deletePairList.add(new DeletePair(entrySensor, exitSensor, panel)); 836 if (checkNxPairs() && checkLogixNG()) { 837 // No Conditional or LogixNG references. 838 deleteNxPairs(); // Delete with no prompt 839 return true; 840 } 841 842 return false; 843 } 844 845 /** 846 * Find Logix Conditionals that have Variables or Actions for the affected NX Pairs. 847 * If any are found, display a dialog box listing the Conditionals and return false. 848 * <p> 849 * @since 4.11.2 850 * @return true if there are no references. 851 */ 852 private boolean checkNxPairs() { 853 jmri.LogixManager mgr = InstanceManager.getDefault(jmri.LogixManager.class); 854 List<String> conditionalReferences = new ArrayList<>(); 855 for (DeletePair dPair : deletePairList) { 856 if (dPair.dp == null) { 857 continue; 858 } 859 for (jmri.Logix lgx : mgr.getNamedBeanSet()) { 860 for (int i = 0; i < lgx.getNumConditionals(); i++) { 861 String cdlName = lgx.getConditionalByNumberOrder(i); 862 jmri.implementation.DefaultConditional cdl = (jmri.implementation.DefaultConditional) lgx.getConditional(cdlName); 863 String cdlUserName = cdl.getUserName(); 864 if (cdlUserName == null) { 865 cdlUserName = ""; 866 } 867 for (jmri.ConditionalVariable var : cdl.getStateVariableList()) { 868 if (var.getBean() == dPair.dp) { 869 String refName = (cdlUserName.equals("")) ? cdlName : cdlName + " ( " + cdlUserName + " )"; 870 if (!conditionalReferences.contains(refName)) { 871 conditionalReferences.add(refName); 872 } 873 } 874 } 875 for (jmri.ConditionalAction act : cdl.getActionList()) { 876 if (act.getBean() == dPair.dp) { 877 String refName = (cdlUserName.equals("")) ? cdlName : cdlName + " ( " + cdlUserName + " )"; 878 if (!conditionalReferences.contains(refName)) { 879 conditionalReferences.add(refName); 880 } 881 } 882 } 883 } 884 } 885 } 886 if (conditionalReferences.isEmpty()) { 887 return true; 888 } 889 890 conditionalReferences.sort(null); 891 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteReferences")); 892 for (String ref : conditionalReferences) { 893 msg.append("\n " + ref); // NOI18N 894 } 895 JmriJOptionPane.showMessageDialog(null, 896 msg.toString(), 897 Bundle.getMessage("WarningTitle"), // NOI18N 898 JmriJOptionPane.WARNING_MESSAGE); 899 900 return false; 901 } 902 903 /** 904 * Find LogixNG ConditionalNGs that have Expressions or Actions for the affected NX Pairs. 905 * If any are found, display a dialog box listing the details and return false. 906 * <p> 907 * @since 5.5.7 908 * @return true if there are no references. 909 */ 910 private boolean checkLogixNG() { 911 List<String> conditionalReferences = new ArrayList<>(); 912 for (DeletePair dPair : deletePairList) { 913 if (dPair.dp == null) { 914 continue; 915 } 916 var usage = jmri.jmrit.logixng.util.WhereUsed.whereUsed(dPair.dp); 917 if (!usage.isEmpty()) { 918 conditionalReferences.add(usage); 919 } 920 } 921 if (conditionalReferences.isEmpty()) { 922 return true; 923 } 924 925 conditionalReferences.sort(null); 926 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteReferences")); 927 for (String ref : conditionalReferences) { 928 msg.append("\n" + ref); // NOI18N 929 } 930 JmriJOptionPane.showMessageDialog(null, 931 msg.toString(), 932 Bundle.getMessage("WarningTitle"), // NOI18N 933 JmriJOptionPane.WARNING_MESSAGE); 934 935 return false; 936 } 937 938 /** 939 * Display a list of pending deletes and ask for confirmation. 940 * @since 4.11.2 941 * @return true if deletion confirmation is Yes. 942 */ 943 private boolean confirmDeletePairs() { 944 if (!deletePairList.isEmpty()) { 945 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeletePairs")); // NOI18N 946 for (DeletePair dPair : deletePairList) { 947 if (dPair.dp != null) { 948 msg.append("\n ").append(dPair.dp.getDisplayName()); // NOI18N 949 } 950 } 951 msg.append("\n").append(Bundle.getMessage("DeleteContinue")); // NOI18N 952 int resp = JmriJOptionPane.showConfirmDialog(null, 953 msg.toString(), 954 Bundle.getMessage("WarningTitle"), // NOI18N 955 JmriJOptionPane.YES_NO_OPTION, 956 JmriJOptionPane.QUESTION_MESSAGE); 957 if (resp != JmriJOptionPane.YES_OPTION ) { 958 return false; 959 } 960 } 961 return true; 962 } 963 964 /** 965 * Delete the pairs in the delete pair list. 966 * @since 4.11.2 967 * @since 4.17.4 968 * Remove from Change Listener. 969 */ 970 private void deleteNxPairs() { 971 for (DeletePair dp : deletePairList) { 972 PointDetails sourcePoint = getPointDetails(dp.src, dp.pnl); 973 PointDetails destPoint = getPointDetails(dp.dest, dp.pnl); 974 nxpair.get(sourcePoint).removeDestination(destPoint); 975 firePropertyChange(PROPERTY_LENGTH, null, null); 976 if (nxpair.get(sourcePoint).getDestinationPoints().isEmpty()) { 977 nxpair.get(sourcePoint).removePropertyChangeListener(this); 978 nxpair.remove(sourcePoint); 979 } 980 } 981 } 982 983 /** 984 * List of NX pairs that are scheduled for deletion. 985 * @since 4.11.2 986 */ 987 List<DeletePair> deletePairList = new ArrayList<>(); 988 989 /** 990 * Class to store NX pair components. 991 * @since 4.11.2 992 */ 993 private class DeletePair { 994 NamedBean src = null; 995 NamedBean dest = null; 996 LayoutEditor pnl = null; 997 DestinationPoints dp = null; 998 999 /** 1000 * Constructor for a DeletePair row. 1001 * 1002 * @param src Source sensor bean 1003 * @param dest Ddestination sensor bean 1004 * @param pnl The LayoutEditor panel for the source bean 1005 */ 1006 DeletePair(NamedBean src, NamedBean dest, LayoutEditor pnl) { 1007 this.src = src; 1008 this.dest = dest; 1009 this.pnl = pnl; 1010 1011 // Get the actual destination point, if any. 1012 PointDetails sourcePoint = getPointDetails(src, pnl); 1013 PointDetails destPoint = getPointDetails(dest, pnl); 1014 if (sourcePoint != null && destPoint != null) { 1015 if (nxpair.containsKey(sourcePoint)) { 1016 this.dp = nxpair.get(sourcePoint).getDestForPoint(destPoint); 1017 } 1018 } 1019 } 1020 } 1021 1022 /** 1023 * Rebuild the delete pair list based on the supplied sensor. 1024 * Find all of the NX pairs that use this sensor as either a source or 1025 * destination. They will be candidates for deletion. 1026 * 1027 * @since 4.11.2 1028 * @param sensor The sensor being deleted, 1029 */ 1030 void createDeletePairList(NamedBean sensor) { 1031 deletePairList.clear(); 1032 nxpair.forEach((pdSrc, src) -> { 1033 Sensor sBean = pdSrc.getSensor(); 1034 LayoutEditor sPanel = pdSrc.getPanel(); 1035 for (PointDetails pdDest : src.getDestinationPoints()) { 1036 Sensor dBean = pdDest.getSensor(); 1037 if (sensor == sBean || sensor == dBean) { 1038 log.debug("Delete pair: {} to {}, panel = {}", // NOI18N 1039 sBean.getDisplayName(), dBean.getDisplayName(), sPanel.getLayoutName()); 1040 deletePairList.add(new DeletePair(sBean, dBean, sPanel)); 1041 } 1042 } 1043 }); 1044 } 1045 1046 // ============ End NX Pair Delete Methods ============ 1047 1048 /** 1049 * Create a list of sensors that have the layout block as either 1050 * facing or protecting. 1051 * Called by {@link jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTrackEditor#hasNxSensorPairs}. 1052 * @since 4.11.2 1053 * @param layoutBlock The layout block to be checked. 1054 * @return the a list of sensors affected by the layout block or an empty list. 1055 */ 1056 public List<String> layoutBlockSensors(@Nonnull LayoutBlock layoutBlock) { 1057 log.debug("layoutBlockSensors: {}", layoutBlock.getDisplayName()); 1058 List<String> blockSensors = new ArrayList<>(); 1059 nxpair.forEach((pdSrc, src) -> { 1060 Sensor sBean = pdSrc.getSensor(); 1061 for (LayoutBlock sProtect : pdSrc.getProtecting()) { 1062 if (layoutBlock == pdSrc.getFacing() || layoutBlock == sProtect) { 1063 log.debug(" Source = '{}', Facing = '{}', Protecting = '{}' ", 1064 sBean.getDisplayName(), pdSrc.getFacing().getDisplayName(), sProtect.getDisplayName()); 1065 blockSensors.add(sBean.getDisplayName()); 1066 } 1067 } 1068 1069 for (PointDetails pdDest : src.getDestinationPoints()) { 1070 Sensor dBean = pdDest.getSensor(); 1071 for (LayoutBlock dProtect : pdDest.getProtecting()) { 1072 if (layoutBlock == pdDest.getFacing() || layoutBlock == dProtect) { 1073 log.debug(" Destination = '{}', Facing = '{}', Protecting = '{}' ", 1074 dBean.getDisplayName(), pdDest.getFacing().getDisplayName(), dProtect.getDisplayName()); 1075 blockSensors.add(dBean.getDisplayName()); 1076 } 1077 } 1078 } 1079 }); 1080 return blockSensors; 1081 } 1082 1083 public boolean isDestinationValid(Object source, Object dest, LayoutEditor panel) { 1084 if (nxpair.containsKey(getPointDetails(source, panel))) { 1085 return nxpair.get(getPointDetails(source, panel)).isDestinationValid(getPointDetails(dest, panel)); 1086 } 1087 return false; 1088 } 1089 1090 public boolean isUniDirection(Object source, LayoutEditor panel, Object dest) { 1091 if (nxpair.containsKey(getPointDetails(source, panel))) { 1092 return nxpair.get(getPointDetails(source, panel)).getUniDirection(dest, panel); 1093 } 1094 return false; 1095 } 1096 1097 public void setUniDirection(Object source, LayoutEditor panel, Object dest, boolean set) { 1098 if (nxpair.containsKey(getPointDetails(source, panel))) { 1099 nxpair.get(getPointDetails(source, panel)).setUniDirection(dest, panel, set); 1100 } 1101 } 1102 1103 public boolean canBeBiDirectional(Object source, LayoutEditor panel, Object dest) { 1104 if (nxpair.containsKey(getPointDetails(source, panel))) { 1105 return nxpair.get(getPointDetails(source, panel)).canBeBiDirection(dest, panel); 1106 } 1107 return false; 1108 } 1109 1110 public boolean isEnabled(Object source, LayoutEditor panel, Object dest) { 1111 if (nxpair.containsKey(getPointDetails(source, panel))) { 1112 return nxpair.get(getPointDetails(source, panel)).isEnabled(dest, panel); 1113 } 1114 return false; 1115 } 1116 1117 public void setEnabled(Object source, LayoutEditor panel, Object dest, boolean set) { 1118 if (nxpair.containsKey(getPointDetails(source, panel))) { 1119 nxpair.get(getPointDetails(source, panel)).setEnabled(dest, panel, set); 1120 } 1121 } 1122 1123 public void setEntryExitType(Object source, LayoutEditor panel, Object dest, int set) { 1124 if (nxpair.containsKey(getPointDetails(source, panel))) { 1125 nxpair.get(getPointDetails(source, panel)).setEntryExitType(dest, panel, set); 1126 } 1127 } 1128 1129 public int getEntryExitType(Object source, LayoutEditor panel, Object dest) { 1130 if (nxpair.containsKey(getPointDetails(source, panel))) { 1131 return nxpair.get(getPointDetails(source, panel)).getEntryExitType(dest, panel); 1132 } 1133 return 0x00; 1134 } 1135 1136 public String getUniqueId(Object source, LayoutEditor panel, Object dest) { 1137 if (nxpair.containsKey(getPointDetails(source, panel))) { 1138 return nxpair.get(getPointDetails(source, panel)).getUniqueId(dest, panel); 1139 } 1140 return null; 1141 } 1142 1143 public List<String> getEntryExitList() { 1144 List<String> destlist = new ArrayList<>(); 1145 for (Source e : nxpair.values()) { 1146 destlist.addAll(e.getDestinationUniqueId()); 1147 } 1148 return destlist; 1149 } 1150 1151 // protecting helps us to determine which direction we are going. 1152 // validateOnly flag is used, if all we are doing is simply checking to see if the source/destpoints are valid 1153 // when creating the pairs in the user GUI 1154 public boolean isPathActive(Object sourceObj, Object destObj, LayoutEditor panel) { 1155 PointDetails pd = getPointDetails(sourceObj, panel); 1156 if (nxpair.containsKey(pd)) { 1157 Source source = nxpair.get(pd); 1158 return source.isRouteActive(getPointDetails(destObj, panel)); 1159 } 1160 return false; 1161 } 1162 1163 public void cancelInterlock(Object source, LayoutEditor panel, Object dest) { 1164 if (nxpair.containsKey(getPointDetails(source, panel))) { 1165 nxpair.get(getPointDetails(source, panel)).cancelInterlock(dest, panel); 1166 } 1167 1168 } 1169 1170 jmri.SignalMastLogicManager smlm = InstanceManager.getDefault(jmri.SignalMastLogicManager.class); 1171 1172 public final static int CANCELROUTE = 0; 1173 public final static int CLEARROUTE = 1; 1174 public final static int EXITROUTE = 2; 1175 public final static int STACKROUTE = 4; 1176 1177 /** 1178 * Return a point from a given LE Panel. 1179 * 1180 * @param obj The point object 1181 * @param panel The Layout Editor panel on which the point was placed 1182 * @return the point object, null if the point is not found 1183 */ 1184 public PointDetails getPointDetails(Object obj, LayoutEditor panel) { 1185 for (int i = 0; i < pointDetails.size(); i++) { 1186 if ((pointDetails.get(i).getRefObject() == obj)) { 1187 return pointDetails.get(i); 1188 1189 } 1190 } 1191 return null; 1192 } 1193 1194 /** 1195 * Return either an existing point stored in pointDetails, or create a new one as required. 1196 * 1197 * @param source The Layout Block functioning as the source (origin) 1198 * @param destination A (list of) Layout Blocks functioning as destinations 1199 * @param panel The Layout Editor panel on which the point is to be placed 1200 * @return the point object 1201 */ 1202 PointDetails getPointDetails(LayoutBlock source, List<LayoutBlock> destination, LayoutEditor panel) { 1203 PointDetails newPoint = new PointDetails(source, destination); 1204 newPoint.setPanel(panel); 1205 for (int i = 0; i < pointDetails.size(); i++) { 1206 if (pointDetails.get(i).equals(newPoint)) { 1207 return pointDetails.get(i); 1208 } 1209 } 1210 //Not found so will add 1211 pointDetails.add(newPoint); 1212 return newPoint; 1213 } 1214 1215 //No point can have multiple copies of what is the same thing. 1216 static List<PointDetails> pointDetails = new ArrayList<PointDetails>(); 1217 1218 /** 1219 * Get the name of a destinationPoint on a LE Panel. 1220 * 1221 * @param obj the point object 1222 * @param panel The Layout Editor panel on which it is expected to be placed 1223 * @return the name of the point 1224 */ 1225 public String getPointAsString(NamedBean obj, LayoutEditor panel) { 1226 if (obj == null) { 1227 return "null"; // NOI18N 1228 } 1229 PointDetails valid = getPointDetails(obj, panel); //was just plain getPoint 1230 if (valid != null) { 1231 return valid.getDisplayName(); 1232 } 1233 return "empty"; // NOI18N 1234 } 1235 1236 List<StackDetails> stackList = new ArrayList<>(); 1237 1238 /** 1239 * If a route is requested but is currently blocked, ask user 1240 * if it should be added to stackList. 1241 * 1242 * @param dp DestinationPoints object 1243 * @param reverse true for a reversed running direction, mostly false 1244 */ 1245 synchronized public void stackNXRoute(DestinationPoints dp, boolean reverse) { 1246 if (isRouteStacked(dp, reverse)) { 1247 return; 1248 } 1249 stackList.add(new StackDetails(dp, reverse)); 1250 checkTimer.start(); 1251 if (stackPanel == null) { 1252 stackPanel = new StackNXPanel(); 1253 } 1254 if (stackDialog == null) { 1255 stackDialog = new JDialog(); 1256 stackDialog.setTitle(Bundle.getMessage("WindowTitleStackRoutes")); // NOI18N 1257 stackDialog.add(stackPanel); 1258 } 1259 stackPanel.updateGUI(); 1260 1261 stackDialog.pack(); 1262 stackDialog.setModal(false); 1263 stackDialog.setVisible(true); 1264 } 1265 1266 StackNXPanel stackPanel = null; 1267 JDialog stackDialog = null; 1268 1269 /** 1270 * Get a list of all stacked routes from stackList. 1271 * 1272 * @return an List containing destinationPoint elements 1273 */ 1274 public List<DestinationPoints> getStackedInterlocks() { 1275 List<DestinationPoints> dpList = new ArrayList<>(); 1276 for (StackDetails st : stackList) { 1277 dpList.add(st.getDestinationPoint()); 1278 } 1279 return dpList; 1280 } 1281 1282 /** 1283 * Query if a stacked route is in stackList. 1284 * 1285 * @param dp DestinationPoints object 1286 * @param reverse true for a reversed running direction, mostly false 1287 * @return true if dp is in stackList 1288 */ 1289 public boolean isRouteStacked(DestinationPoints dp, boolean reverse) { 1290 Iterator<StackDetails> iter = stackList.iterator(); 1291 while (iter.hasNext()) { 1292 StackDetails st = iter.next(); 1293 if (st.getDestinationPoint() == dp && st.getReverse() == reverse) { 1294 return true; 1295 } 1296 } 1297 return false; 1298 } 1299 1300 /** 1301 * Remove a stacked route from stackList. 1302 * 1303 * @param dp DestinationPoints object 1304 * @param reverse true for a reversed running direction, mostly false 1305 */ 1306 synchronized public void cancelStackedRoute(DestinationPoints dp, boolean reverse) { 1307 Iterator<StackDetails> iter = stackList.iterator(); 1308 while (iter.hasNext()) { 1309 StackDetails st = iter.next(); 1310 if (st.getDestinationPoint() == dp && st.getReverse() == reverse) { 1311 iter.remove(); 1312 } 1313 } 1314 stackPanel.updateGUI(); 1315 if (stackList.isEmpty()) { 1316 stackDialog.setVisible(false); 1317 checkTimer.stop(); 1318 } 1319 } 1320 1321 /** 1322 * Class to collect (stack) routes when they are requested but blocked. 1323 */ 1324 static class StackDetails { 1325 1326 DestinationPoints dp; 1327 boolean reverse; 1328 1329 StackDetails(DestinationPoints dp, boolean reverse) { 1330 this.dp = dp; 1331 this.reverse = reverse; 1332 } 1333 1334 boolean getReverse() { 1335 return reverse; 1336 } 1337 1338 DestinationPoints getDestinationPoint() { 1339 return dp; 1340 } 1341 } 1342 1343 javax.swing.Timer checkTimer = new javax.swing.Timer(10000, (java.awt.event.ActionEvent e) -> { 1344 checkRoute(); 1345 }); 1346 1347 /** 1348 * Step through stackList and activate the first stacked route in line 1349 * if it is no longer blocked. 1350 */ 1351 synchronized void checkRoute() { 1352 checkTimer.stop(); 1353 StackDetails[] tmp = new StackDetails[stackList.size()]; 1354 stackList.toArray(tmp); 1355 1356 for (StackDetails st : tmp) { 1357 if (!st.getDestinationPoint().isActive()) { 1358 // If the route is not already active, then check. 1359 // If the route does get set, then the setting process will remove the route from the stack. 1360 st.getDestinationPoint().setInterlockRoute(st.getReverse()); 1361 } 1362 } 1363 1364 if (!stackList.isEmpty()) { 1365 checkTimer.start(); 1366 } else { 1367 stackDialog.setVisible(false); 1368 } 1369 } 1370 1371 public void removePropertyChangeListener(PropertyChangeListener list, NamedBean obj, LayoutEditor panel) { 1372 if (obj == null) { 1373 return; 1374 } 1375 PointDetails valid = getPointDetails(obj, panel); 1376 if (valid != null) { 1377 valid.removePropertyChangeListener(list); 1378 } 1379 } 1380 1381 boolean runWhenStabilised = false; 1382 LayoutEditor toUseWhenStable; 1383 int interlockTypeToUseWhenStable; 1384 1385 /** 1386 * Discover all possible valid source and destination Signal Mast Logic pairs 1387 * on all Layout Editor panels. 1388 * 1389 * @param editor The Layout Editor panel 1390 * @param interlockType Integer value representing the type of interlocking, one of 1391 * SETUPTURNOUTSONLY, SETUPSIGNALMASTLOGIC or FULLINTERLOCK 1392 * @throws JmriException when an error occurs during discovery 1393 */ 1394 public void automaticallyDiscoverEntryExitPairs(LayoutEditor editor, int interlockType) throws JmriException { 1395 //This is almost a duplicate of that in the DefaultSignalMastLogicManager 1396 runWhenStabilised = false; 1397 jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 1398 if (!lbm.isAdvancedRoutingEnabled()) { 1399 throw new JmriException("advanced routing not enabled"); // NOI18N 1400 } 1401 if (!lbm.routingStablised()) { 1402 runWhenStabilised = true; 1403 toUseWhenStable = editor; 1404 interlockTypeToUseWhenStable = interlockType; 1405 log.debug("Layout block routing has not yet stabilised, discovery will happen once it has"); // NOI18N 1406 return; 1407 } 1408 HashMap<NamedBean, List<NamedBean>> validPaths = lbm.getLayoutBlockConnectivityTools(). 1409 discoverValidBeanPairs(null, Sensor.class, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR); 1410 EntryExitPairs eep = this; 1411 for (Entry<NamedBean, List<NamedBean>> entry : validPaths.entrySet()) { 1412 NamedBean key = entry.getKey(); 1413 List<NamedBean> validDestMast = validPaths.get(key); 1414 if (!validDestMast.isEmpty()) { 1415 eep.addNXSourcePoint(key, editor); 1416 for (int i = 0; i < validDestMast.size(); i++) { 1417 if (!eep.isDestinationValid(key, validDestMast.get(i), editor)) { 1418 eep.addNXDestination(key, validDestMast.get(i), editor); 1419 eep.setEntryExitType(key, editor, validDestMast.get(i), interlockType); 1420 } 1421 } 1422 } 1423 } 1424 1425 firePropertyChange(PROPERTY_AUTO_GENERATE_COMPLETE, null, null); 1426 } 1427 1428 protected PropertyChangeListener propertyBlockManagerListener = new PropertyChangeListener() { 1429 @Override 1430 public void propertyChange(PropertyChangeEvent e) { 1431 if ( LayoutBlockManager.PROPERTY_TOPOLOGY.equals(e.getPropertyName())) { 1432 boolean newValue = (Boolean) e.getNewValue(); 1433 if (newValue) { 1434 if (runWhenStabilised) { 1435 try { 1436 automaticallyDiscoverEntryExitPairs(toUseWhenStable, interlockTypeToUseWhenStable); 1437 } catch (JmriException je) { 1438 //Considered normal if routing not enabled 1439 } 1440 } 1441 } 1442 } 1443 } 1444 }; 1445 1446 public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 1447 1448 } 1449 1450 @Override 1451 public void deleteBean(@Nonnull DestinationPoints bean, @Nonnull String property) throws PropertyVetoException { 1452 1453 } 1454 1455 @Override 1456 @Nonnull 1457 public String getBeanTypeHandled(boolean plural) { 1458 return Bundle.getMessage(plural ? "BeanNameEntryExits" : "BeanNameEntryExit"); // NOI18N 1459 } 1460 1461 /** 1462 * {@inheritDoc} 1463 */ 1464 @Override 1465 public Class<DestinationPoints> getNamedBeanClass() { 1466 return DestinationPoints.class; 1467 } 1468 1469 /** 1470 * {@inheritDoc} 1471 */ 1472 @Override 1473 @OverridingMethodsMustInvokeSuper 1474 public void setPropertyChangesSilenced(@Nonnull String propertyName, boolean silenced) { 1475 if (!Manager.PROPERTY_BEANS.equals(propertyName)) { 1476 throw new IllegalArgumentException("Property " + propertyName + " cannot be silenced."); 1477 } 1478 silencedProperties.put(propertyName, silenced); 1479 if (propertyName.equals(Manager.PROPERTY_BEANS) && !silenced) { 1480 fireIndexedPropertyChange(Manager.PROPERTY_BEANS, getNamedBeanSet().size(), null, null); 1481 } 1482 } 1483 1484 /** {@inheritDoc} */ 1485 @Override 1486 public void addDataListener(ManagerDataListener<DestinationPoints> e) { 1487 if (e != null) listeners.add(e); 1488 } 1489 1490 /** {@inheritDoc} */ 1491 @Override 1492 public void removeDataListener(ManagerDataListener<DestinationPoints> e) { 1493 if (e != null) listeners.remove(e); 1494 } 1495 1496 final List<ManagerDataListener<DestinationPoints>> listeners = new ArrayList<>(); 1497 1498 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EntryExitPairs.class); 1499 1500}