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