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 boolean result = lbm.getLayoutBlockConnectivityTools().checkValidDest( 497 fromPd.getFacing(), pro, toPd.getFacing(), toProt, 498 LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR); 499 if (result) { 500 List<LayoutBlock> blkList = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(fromPd.getFacing(), toPd.getFacing(), pro, cleardown, LayoutBlockConnectivityTools.Routing.NONE); 501 if (!blkList.isEmpty()) { 502 if (log.isDebugEnabled()) { 503 log.debug("[setMultiPointRoute] blocks and sensors"); 504 for (LayoutBlock blk : blkList) { 505 log.debug(" blk = {}", blk.getDisplayName()); 506 } 507 } 508 List<jmri.NamedBean> beanList = lbm.getLayoutBlockConnectivityTools().getBeansInPath(blkList, null, jmri.Sensor.class); 509 PointDetails fromPoint = fromPd; 510 refCounter++; 511 if (!beanList.isEmpty()) { 512 if (log.isDebugEnabled()) { 513 for (NamedBean xnb : beanList) { 514 log.debug(" sensor = {}", xnb.getDisplayName()); 515 } 516 } 517 for (int i = 1; i < beanList.size(); i++) { 518 NamedBean nb = beanList.get(i); 519 PointDetails cur = getPointDetails(nb, fromPd.getPanel()); 520 Source s = nxpair.get(fromPoint); 521 if (s != null) { 522 routesToSet.add(new SourceToDest(s, s.getDestForPoint(cur), false, refCounter)); 523 } 524 fromPoint = cur; 525 } 526 } 527 Source s = nxpair.get(fromPoint); 528 if (s != null) { 529 if (s.getDestForPoint(toPd) != null) { 530 routesToSet.add(new SourceToDest(s, s.getDestForPoint(toPd), false, refCounter)); 531 } 532 } 533 log.debug("[setMultiPointRoute] Invoke processRoutesToSet"); 534 processRoutesToSet(); 535 log.debug("[setMultiPointRoute] processRoutesToSet is done"); 536 return; 537 } 538 } 539 } catch (jmri.JmriException e) { 540 // Can be considered normal if route is blocked 541 JmriJOptionPane.showMessageDialog(null, 542 Bundle.getMessage("MultiPointBlocked"), // NOI18N 543 Bundle.getMessage("WarningTitle"), // NOI18N 544 JmriJOptionPane.WARNING_MESSAGE); 545 } 546 } 547 fromPd.setNXButtonState(NXBUTTONINACTIVE); 548 toPd.setNXButtonState(NXBUTTONINACTIVE); 549 log.debug("[setMultiPointRoute] Done, from = {}, to = {}", fromPd.getSensor().getDisplayName(), toPd.getSensor().getDisplayName()); 550 } 551 552 int refCounter = 0; 553 554 /** 555 * List holding SourceToDest sets of routes between two points. 556 */ 557 List<SourceToDest> routesToSet = new ArrayList<>(); 558 559 /** 560 * Class to store NX sets consisting of a source point, a destination point, 561 * a direction and a reference. 562 */ 563 private static class SourceToDest { 564 565 Source s = null; 566 DestinationPoints dp = null; 567 boolean direction = false; 568 int ref = -1; 569 570 /** 571 * Constructor for a SourceToDest element. 572 * 573 * @param s a source point 574 * @param dp a destination point 575 * @param dir a direction 576 * @param ref Integer used as reference 577 */ 578 SourceToDest(Source s, DestinationPoints dp, boolean dir, int ref) { 579 this.s = s; 580 this.dp = dp; 581 this.direction = dir; 582 this.ref = ref; 583 } 584 } 585 586 int currentDealing = 0; 587 588 /** 589 * Activate each SourceToDest set in routesToSet 590 */ 591 synchronized void processRoutesToSet() { 592 if (log.isDebugEnabled()) { 593 log.debug("[processRoutesToSet] Current routesToSet list"); 594 for (SourceToDest sd : routesToSet) { 595 String dpName = (sd.dp == null) ? "- null -" : sd.dp.getDestPoint().getSensor().getDisplayName(); 596 log.debug(" from = {}, to = {}, ref = {}", sd.s.getPoint().getSensor().getDisplayName(), dpName, sd.ref); 597 } 598 } 599 600 if (routesToSet.isEmpty()) { 601 return; 602 } 603 Source s = routesToSet.get(0).s; 604 DestinationPoints dp = routesToSet.get(0).dp; 605 boolean dir = routesToSet.get(0).direction; 606 currentDealing = routesToSet.get(0).ref; 607 routesToSet.remove(0); 608 609 dp.addPropertyChangeListener(propertyDestinationListener); 610 s.activeBean(dp, dir); 611 } 612 613 /** 614 * Remove remaining SourceToDest sets in routesToSet 615 */ 616 synchronized void removeRemainingRoute() { 617 List<SourceToDest> toRemove = new ArrayList<>(); 618 for (SourceToDest rts : routesToSet) { 619 if (rts.ref == currentDealing) { 620 toRemove.add(rts); 621 rts.dp.getDestPoint().setNXButtonState(NXBUTTONINACTIVE); 622 } 623 } 624 for (SourceToDest rts : toRemove) { 625 routesToSet.remove(rts); 626 } 627 } 628 629 protected PropertyChangeListener propertyDestinationListener = new PropertyChangeListener() { 630 @Override 631 public void propertyChange(PropertyChangeEvent e) { 632 ((DestinationPoints) e.getSource()).removePropertyChangeListener(this); 633 if ( DestinationPoints.PROPERTY_ACTIVE.equals(e.getPropertyName())) { 634 processRoutesToSet(); 635 } else if ( DestinationPoints.PROPERTY_STACKED.equals(e.getPropertyName()) 636 || DestinationPoints.PROPERTY_FAILED.equals(e.getPropertyName()) 637 || DestinationPoints.PROPERTY_NO_CHANGE.equals(e.getPropertyName())) { 638 removeRemainingRoute(); 639 } 640 } 641 }; 642 643 private List<Object> destinationList = new ArrayList<>(); 644 645 // Need to sort out the presentation of the name here rather than using the point ID. 646 // This is used for the creation and display of information in the table. 647 // The presentation of the name might have to be done at the table level. 648 public List<Object> getNxSource(LayoutEditor panel) { 649 List<Object> source = new ArrayList<>(); 650 destinationList = new ArrayList<>(); 651 652 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 653 PointDetails key = e.getKey(); 654 LayoutEditor pan = key.getPanel(); 655 if (pan == panel) { 656 List<PointDetails> dest = nxpair.get(key).getDestinationPoints(); 657 for (int i = 0; i < dest.size(); i++) { 658 destinationList.add(dest.get(i).getRefObject()); 659 source.add(key.getRefObject()); 660 } 661 } 662 } 663 return source; 664 } 665 666 public List<Object> getNxDestination() { 667 return destinationList; 668 } 669 670 public List<LayoutEditor> getSourcePanelList() { 671 List<LayoutEditor> list = new ArrayList<>(); 672 673 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 674 PointDetails key = e.getKey(); 675 LayoutEditor pan = key.getPanel(); 676 if (!list.contains(pan)) { 677 list.add(pan); 678 } 679 } 680 return list; 681 } 682 683 /** 684 * Return a point if it already exists, or create a new one if not. 685 */ 686 private PointDetails providePoint(LayoutBlock source, List<LayoutBlock> protecting, LayoutEditor panel) { 687 PointDetails sourcePoint = getPointDetails(source, protecting, panel); 688 if (sourcePoint == null) { 689 sourcePoint = new PointDetails(source, protecting); 690 sourcePoint.setPanel(panel); 691 } 692 return sourcePoint; 693 } 694 695 /** 696 * @since 4.17.4 697 */ 698 @Override 699 public void propertyChange(PropertyChangeEvent evt) { 700 firePropertyChange(PROPERTY_ACTIVE, evt.getOldValue(), evt.getNewValue()); 701 } 702 703 704 public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel) { 705 addNXDestination(source, destination, panel, null); 706 } 707 708 /** 709 * @since 4.17.4 710 * Register in Property Change Listener. 711 * @param source the source bean. 712 * @param destination the destination bean. 713 * @param panel the layout editor panel. 714 * @param id the points details id. 715 */ 716 public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel, String id) { 717 if (source == null) { 718 log.error("no source Object provided"); // NOI18N 719 return; 720 } 721 if (destination == null) { 722 log.error("no destination Object provided"); // NOI18N 723 return; 724 } 725 PointDetails sourcePoint = providePoint(source, panel); 726 if (sourcePoint == null) { 727 log.error("source point for {} not created addNXDes", source.getDisplayName()); // NOI18N 728 return; 729 } 730 731 sourcePoint.setPanel(panel); 732 sourcePoint.setRefObject(source); 733 PointDetails destPoint = providePoint(destination, panel); 734 if (destPoint != null) { 735 destPoint.setPanel(panel); 736 destPoint.setRefObject(destination); 737 destPoint.getSignal(); 738 if (!nxpair.containsKey(sourcePoint)) { 739 Source sp = new Source(sourcePoint); 740 nxpair.put(sourcePoint, sp); 741 sp.removePropertyChangeListener(this); 742 sp.addPropertyChangeListener(this); 743 } 744 nxpair.get(sourcePoint).addDestination(destPoint, id); 745 } 746 747 firePropertyChange(PROPERTY_LENGTH, null, null); 748 } 749 750 public List<Object> getDestinationList(Object obj, LayoutEditor panel) { 751 List<Object> list = new ArrayList<>(); 752 if (nxpair.containsKey(getPointDetails(obj, panel))) { 753 List<PointDetails> from = nxpair.get(getPointDetails(obj, panel)).getDestinationPoints(); 754 for (int i = 0; i < from.size(); i++) { 755 list.add(from.get(i).getRefObject()); 756 } 757 } 758 return list; 759 } 760 761 public void removeNXSensor(Sensor sensor) { 762 log.info("panel maintenance has resulting in the request to remove a sensor: {}", sensor.getDisplayName()); 763 } 764 765 // ============ NX Pair Delete Methods ============ 766 // The request will be for all NX Pairs containing a sensor or 767 // a specific entry and exit sensor pair. 768 769 /** 770 * Entry point to delete all of the NX pairs for a specific sensor. 771 * 1) Build a list of affected NX pairs. 772 * 2) Check for Conditional references. 773 * 3) If no references, do the delete process with user approval. 774 * @since 4.11.2 775 * @param sensor The sensor whose pairs should be deleted. 776 * @return true if the delete was successful. False if prevented by 777 * Conditional/LogixNG references or user choice. 778 */ 779 public boolean deleteNxPair(NamedBean sensor) { 780 if (sensor == null) { 781 log.error("deleteNxPair: sensor is null"); // NOI18N 782 return false; 783 } 784 createDeletePairList(sensor); 785 if (checkNxPairs() && checkLogixNG()) { 786 // No Conditional or LogixNG references. 787 if (confirmDeletePairs()) { 788 deleteNxPairs(); 789 return true; 790 } 791 } 792 return false; 793 } 794 795 /** 796 * Entry point to delete a specific NX pair. 797 * 798 * @since 4.11.2 799 * @param entrySensor The sensor that acts as the entry point. 800 * @param exitSensor The sensor that acts as the exit point. 801 * @param panel The layout editor panel that contains the entry sensor. 802 * @return true if the delete was successful. False if there are Conditional/LogixNG references. 803 */ 804 public boolean deleteNxPair(NamedBean entrySensor, NamedBean exitSensor, LayoutEditor panel) { 805 if (entrySensor == null || exitSensor == null || panel == null) { 806 log.error("deleteNxPair: One or more null inputs"); // NOI18N 807 return false; 808 } 809 810 deletePairList.clear(); 811 deletePairList.add(new DeletePair(entrySensor, exitSensor, panel)); 812 if (checkNxPairs() && checkLogixNG()) { 813 // No Conditional or LogixNG references. 814 deleteNxPairs(); // Delete with no prompt 815 return true; 816 } 817 818 return false; 819 } 820 821 /** 822 * Find Logix Conditionals that have Variables or Actions for the affected NX Pairs. 823 * If any are found, display a dialog box listing the Conditionals and return false. 824 * <p> 825 * @since 4.11.2 826 * @return true if there are no references. 827 */ 828 private boolean checkNxPairs() { 829 jmri.LogixManager mgr = InstanceManager.getDefault(jmri.LogixManager.class); 830 List<String> conditionalReferences = new ArrayList<>(); 831 for (DeletePair dPair : deletePairList) { 832 if (dPair.dp == null) { 833 continue; 834 } 835 for (jmri.Logix lgx : mgr.getNamedBeanSet()) { 836 for (int i = 0; i < lgx.getNumConditionals(); i++) { 837 String cdlName = lgx.getConditionalByNumberOrder(i); 838 jmri.implementation.DefaultConditional cdl = (jmri.implementation.DefaultConditional) lgx.getConditional(cdlName); 839 String cdlUserName = cdl.getUserName(); 840 if (cdlUserName == null) { 841 cdlUserName = ""; 842 } 843 for (jmri.ConditionalVariable var : cdl.getStateVariableList()) { 844 if (var.getBean() == dPair.dp) { 845 String refName = (cdlUserName.equals("")) ? cdlName : cdlName + " ( " + cdlUserName + " )"; 846 if (!conditionalReferences.contains(refName)) { 847 conditionalReferences.add(refName); 848 } 849 } 850 } 851 for (jmri.ConditionalAction act : cdl.getActionList()) { 852 if (act.getBean() == dPair.dp) { 853 String refName = (cdlUserName.equals("")) ? cdlName : cdlName + " ( " + cdlUserName + " )"; 854 if (!conditionalReferences.contains(refName)) { 855 conditionalReferences.add(refName); 856 } 857 } 858 } 859 } 860 } 861 } 862 if (conditionalReferences.isEmpty()) { 863 return true; 864 } 865 866 conditionalReferences.sort(null); 867 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteReferences")); 868 for (String ref : conditionalReferences) { 869 msg.append("\n " + ref); // NOI18N 870 } 871 JmriJOptionPane.showMessageDialog(null, 872 msg.toString(), 873 Bundle.getMessage("WarningTitle"), // NOI18N 874 JmriJOptionPane.WARNING_MESSAGE); 875 876 return false; 877 } 878 879 /** 880 * Find LogixNG ConditionalNGs that have Expressions or Actions for the affected NX Pairs. 881 * If any are found, display a dialog box listing the details and return false. 882 * <p> 883 * @since 5.5.7 884 * @return true if there are no references. 885 */ 886 private boolean checkLogixNG() { 887 List<String> conditionalReferences = new ArrayList<>(); 888 for (DeletePair dPair : deletePairList) { 889 if (dPair.dp == null) { 890 continue; 891 } 892 var usage = jmri.jmrit.logixng.util.WhereUsed.whereUsed(dPair.dp); 893 if (!usage.isEmpty()) { 894 conditionalReferences.add(usage); 895 } 896 } 897 if (conditionalReferences.isEmpty()) { 898 return true; 899 } 900 901 conditionalReferences.sort(null); 902 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteReferences")); 903 for (String ref : conditionalReferences) { 904 msg.append("\n" + ref); // NOI18N 905 } 906 JmriJOptionPane.showMessageDialog(null, 907 msg.toString(), 908 Bundle.getMessage("WarningTitle"), // NOI18N 909 JmriJOptionPane.WARNING_MESSAGE); 910 911 return false; 912 } 913 914 /** 915 * Display a list of pending deletes and ask for confirmation. 916 * @since 4.11.2 917 * @return true if deletion confirmation is Yes. 918 */ 919 private boolean confirmDeletePairs() { 920 if (!deletePairList.isEmpty()) { 921 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeletePairs")); // NOI18N 922 for (DeletePair dPair : deletePairList) { 923 if (dPair.dp != null) { 924 msg.append("\n ").append(dPair.dp.getDisplayName()); // NOI18N 925 } 926 } 927 msg.append("\n").append(Bundle.getMessage("DeleteContinue")); // NOI18N 928 int resp = JmriJOptionPane.showConfirmDialog(null, 929 msg.toString(), 930 Bundle.getMessage("WarningTitle"), // NOI18N 931 JmriJOptionPane.YES_NO_OPTION, 932 JmriJOptionPane.QUESTION_MESSAGE); 933 if (resp != JmriJOptionPane.YES_OPTION ) { 934 return false; 935 } 936 } 937 return true; 938 } 939 940 /** 941 * Delete the pairs in the delete pair list. 942 * @since 4.11.2 943 * @since 4.17.4 944 * Remove from Change Listener. 945 */ 946 private void deleteNxPairs() { 947 for (DeletePair dp : deletePairList) { 948 PointDetails sourcePoint = getPointDetails(dp.src, dp.pnl); 949 PointDetails destPoint = getPointDetails(dp.dest, dp.pnl); 950 nxpair.get(sourcePoint).removeDestination(destPoint); 951 firePropertyChange(PROPERTY_LENGTH, null, null); 952 if (nxpair.get(sourcePoint).getDestinationPoints().isEmpty()) { 953 nxpair.get(sourcePoint).removePropertyChangeListener(this); 954 nxpair.remove(sourcePoint); 955 } 956 } 957 } 958 959 /** 960 * List of NX pairs that are scheduled for deletion. 961 * @since 4.11.2 962 */ 963 List<DeletePair> deletePairList = new ArrayList<>(); 964 965 /** 966 * Class to store NX pair components. 967 * @since 4.11.2 968 */ 969 private class DeletePair { 970 NamedBean src = null; 971 NamedBean dest = null; 972 LayoutEditor pnl = null; 973 DestinationPoints dp = null; 974 975 /** 976 * Constructor for a DeletePair row. 977 * 978 * @param src Source sensor bean 979 * @param dest Ddestination sensor bean 980 * @param pnl The LayoutEditor panel for the source bean 981 */ 982 DeletePair(NamedBean src, NamedBean dest, LayoutEditor pnl) { 983 this.src = src; 984 this.dest = dest; 985 this.pnl = pnl; 986 987 // Get the actual destination point, if any. 988 PointDetails sourcePoint = getPointDetails(src, pnl); 989 PointDetails destPoint = getPointDetails(dest, pnl); 990 if (sourcePoint != null && destPoint != null) { 991 if (nxpair.containsKey(sourcePoint)) { 992 this.dp = nxpair.get(sourcePoint).getDestForPoint(destPoint); 993 } 994 } 995 } 996 } 997 998 /** 999 * Rebuild the delete pair list based on the supplied sensor. 1000 * Find all of the NX pairs that use this sensor as either a source or 1001 * destination. They will be candidates for deletion. 1002 * 1003 * @since 4.11.2 1004 * @param sensor The sensor being deleted, 1005 */ 1006 void createDeletePairList(NamedBean sensor) { 1007 deletePairList.clear(); 1008 nxpair.forEach((pdSrc, src) -> { 1009 Sensor sBean = pdSrc.getSensor(); 1010 LayoutEditor sPanel = pdSrc.getPanel(); 1011 for (PointDetails pdDest : src.getDestinationPoints()) { 1012 Sensor dBean = pdDest.getSensor(); 1013 if (sensor == sBean || sensor == dBean) { 1014 log.debug("Delete pair: {} to {}, panel = {}", // NOI18N 1015 sBean.getDisplayName(), dBean.getDisplayName(), sPanel.getLayoutName()); 1016 deletePairList.add(new DeletePair(sBean, dBean, sPanel)); 1017 } 1018 } 1019 }); 1020 } 1021 1022 // ============ End NX Pair Delete Methods ============ 1023 1024 /** 1025 * Create a list of sensors that have the layout block as either 1026 * facing or protecting. 1027 * Called by {@link jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTrackEditor#hasNxSensorPairs}. 1028 * @since 4.11.2 1029 * @param layoutBlock The layout block to be checked. 1030 * @return the a list of sensors affected by the layout block or an empty list. 1031 */ 1032 public List<String> layoutBlockSensors(@Nonnull LayoutBlock layoutBlock) { 1033 log.debug("layoutBlockSensors: {}", layoutBlock.getDisplayName()); 1034 List<String> blockSensors = new ArrayList<>(); 1035 nxpair.forEach((pdSrc, src) -> { 1036 Sensor sBean = pdSrc.getSensor(); 1037 for (LayoutBlock sProtect : pdSrc.getProtecting()) { 1038 if (layoutBlock == pdSrc.getFacing() || layoutBlock == sProtect) { 1039 log.debug(" Source = '{}', Facing = '{}', Protecting = '{}' ", 1040 sBean.getDisplayName(), pdSrc.getFacing().getDisplayName(), sProtect.getDisplayName()); 1041 blockSensors.add(sBean.getDisplayName()); 1042 } 1043 } 1044 1045 for (PointDetails pdDest : src.getDestinationPoints()) { 1046 Sensor dBean = pdDest.getSensor(); 1047 for (LayoutBlock dProtect : pdDest.getProtecting()) { 1048 if (layoutBlock == pdDest.getFacing() || layoutBlock == dProtect) { 1049 log.debug(" Destination = '{}', Facing = '{}', Protecting = '{}' ", 1050 dBean.getDisplayName(), pdDest.getFacing().getDisplayName(), dProtect.getDisplayName()); 1051 blockSensors.add(dBean.getDisplayName()); 1052 } 1053 } 1054 } 1055 }); 1056 return blockSensors; 1057 } 1058 1059 public boolean isDestinationValid(Object source, Object dest, LayoutEditor panel) { 1060 if (nxpair.containsKey(getPointDetails(source, panel))) { 1061 return nxpair.get(getPointDetails(source, panel)).isDestinationValid(getPointDetails(dest, panel)); 1062 } 1063 return false; 1064 } 1065 1066 public boolean isUniDirection(Object source, LayoutEditor panel, Object dest) { 1067 if (nxpair.containsKey(getPointDetails(source, panel))) { 1068 return nxpair.get(getPointDetails(source, panel)).getUniDirection(dest, panel); 1069 } 1070 return false; 1071 } 1072 1073 public void setUniDirection(Object source, LayoutEditor panel, Object dest, boolean set) { 1074 if (nxpair.containsKey(getPointDetails(source, panel))) { 1075 nxpair.get(getPointDetails(source, panel)).setUniDirection(dest, panel, set); 1076 } 1077 } 1078 1079 public boolean canBeBiDirectional(Object source, LayoutEditor panel, Object dest) { 1080 if (nxpair.containsKey(getPointDetails(source, panel))) { 1081 return nxpair.get(getPointDetails(source, panel)).canBeBiDirection(dest, panel); 1082 } 1083 return false; 1084 } 1085 1086 public boolean isEnabled(Object source, LayoutEditor panel, Object dest) { 1087 if (nxpair.containsKey(getPointDetails(source, panel))) { 1088 return nxpair.get(getPointDetails(source, panel)).isEnabled(dest, panel); 1089 } 1090 return false; 1091 } 1092 1093 public void setEnabled(Object source, LayoutEditor panel, Object dest, boolean set) { 1094 if (nxpair.containsKey(getPointDetails(source, panel))) { 1095 nxpair.get(getPointDetails(source, panel)).setEnabled(dest, panel, set); 1096 } 1097 } 1098 1099 public void setEntryExitType(Object source, LayoutEditor panel, Object dest, int set) { 1100 if (nxpair.containsKey(getPointDetails(source, panel))) { 1101 nxpair.get(getPointDetails(source, panel)).setEntryExitType(dest, panel, set); 1102 } 1103 } 1104 1105 public int getEntryExitType(Object source, LayoutEditor panel, Object dest) { 1106 if (nxpair.containsKey(getPointDetails(source, panel))) { 1107 return nxpair.get(getPointDetails(source, panel)).getEntryExitType(dest, panel); 1108 } 1109 return 0x00; 1110 } 1111 1112 public String getUniqueId(Object source, LayoutEditor panel, Object dest) { 1113 if (nxpair.containsKey(getPointDetails(source, panel))) { 1114 return nxpair.get(getPointDetails(source, panel)).getUniqueId(dest, panel); 1115 } 1116 return null; 1117 } 1118 1119 public List<String> getEntryExitList() { 1120 List<String> destlist = new ArrayList<>(); 1121 for (Source e : nxpair.values()) { 1122 destlist.addAll(e.getDestinationUniqueId()); 1123 } 1124 return destlist; 1125 } 1126 1127 // protecting helps us to determine which direction we are going. 1128 // validateOnly flag is used, if all we are doing is simply checking to see if the source/destpoints are valid 1129 // when creating the pairs in the user GUI 1130 public boolean isPathActive(Object sourceObj, Object destObj, LayoutEditor panel) { 1131 PointDetails pd = getPointDetails(sourceObj, panel); 1132 if (nxpair.containsKey(pd)) { 1133 Source source = nxpair.get(pd); 1134 return source.isRouteActive(getPointDetails(destObj, panel)); 1135 } 1136 return false; 1137 } 1138 1139 public void cancelInterlock(Object source, LayoutEditor panel, Object dest) { 1140 if (nxpair.containsKey(getPointDetails(source, panel))) { 1141 nxpair.get(getPointDetails(source, panel)).cancelInterlock(dest, panel); 1142 } 1143 1144 } 1145 1146 jmri.SignalMastLogicManager smlm = InstanceManager.getDefault(jmri.SignalMastLogicManager.class); 1147 1148 public final static int CANCELROUTE = 0; 1149 public final static int CLEARROUTE = 1; 1150 public final static int EXITROUTE = 2; 1151 public final static int STACKROUTE = 4; 1152 1153 /** 1154 * Return a point from a given LE Panel. 1155 * 1156 * @param obj The point object 1157 * @param panel The Layout Editor panel on which the point was placed 1158 * @return the point object, null if the point is not found 1159 */ 1160 public PointDetails getPointDetails(Object obj, LayoutEditor panel) { 1161 for (int i = 0; i < pointDetails.size(); i++) { 1162 if ((pointDetails.get(i).getRefObject() == obj)) { 1163 return pointDetails.get(i); 1164 1165 } 1166 } 1167 return null; 1168 } 1169 1170 /** 1171 * Return either an existing point stored in pointDetails, or create a new one as required. 1172 * 1173 * @param source The Layout Block functioning as the source (origin) 1174 * @param destination A (list of) Layout Blocks functioning as destinations 1175 * @param panel The Layout Editor panel on which the point is to be placed 1176 * @return the point object 1177 */ 1178 PointDetails getPointDetails(LayoutBlock source, List<LayoutBlock> destination, LayoutEditor panel) { 1179 PointDetails newPoint = new PointDetails(source, destination); 1180 newPoint.setPanel(panel); 1181 for (int i = 0; i < pointDetails.size(); i++) { 1182 if (pointDetails.get(i).equals(newPoint)) { 1183 return pointDetails.get(i); 1184 } 1185 } 1186 //Not found so will add 1187 pointDetails.add(newPoint); 1188 return newPoint; 1189 } 1190 1191 //No point can have multiple copies of what is the same thing. 1192 static List<PointDetails> pointDetails = new ArrayList<PointDetails>(); 1193 1194 /** 1195 * Get the name of a destinationPoint on a LE Panel. 1196 * 1197 * @param obj the point object 1198 * @param panel The Layout Editor panel on which it is expected to be placed 1199 * @return the name of the point 1200 */ 1201 public String getPointAsString(NamedBean obj, LayoutEditor panel) { 1202 if (obj == null) { 1203 return "null"; // NOI18N 1204 } 1205 PointDetails valid = getPointDetails(obj, panel); //was just plain getPoint 1206 if (valid != null) { 1207 return valid.getDisplayName(); 1208 } 1209 return "empty"; // NOI18N 1210 } 1211 1212 List<StackDetails> stackList = new ArrayList<>(); 1213 1214 /** 1215 * If a route is requested but is currently blocked, ask user 1216 * if it should be added to stackList. 1217 * 1218 * @param dp DestinationPoints object 1219 * @param reverse true for a reversed running direction, mostly false 1220 */ 1221 synchronized public void stackNXRoute(DestinationPoints dp, boolean reverse) { 1222 if (isRouteStacked(dp, reverse)) { 1223 return; 1224 } 1225 stackList.add(new StackDetails(dp, reverse)); 1226 checkTimer.start(); 1227 if (stackPanel == null) { 1228 stackPanel = new StackNXPanel(); 1229 } 1230 if (stackDialog == null) { 1231 stackDialog = new JDialog(); 1232 stackDialog.setTitle(Bundle.getMessage("WindowTitleStackRoutes")); // NOI18N 1233 stackDialog.add(stackPanel); 1234 } 1235 stackPanel.updateGUI(); 1236 1237 stackDialog.pack(); 1238 stackDialog.setModal(false); 1239 stackDialog.setVisible(true); 1240 } 1241 1242 StackNXPanel stackPanel = null; 1243 JDialog stackDialog = null; 1244 1245 /** 1246 * Get a list of all stacked routes from stackList. 1247 * 1248 * @return an List containing destinationPoint elements 1249 */ 1250 public List<DestinationPoints> getStackedInterlocks() { 1251 List<DestinationPoints> dpList = new ArrayList<>(); 1252 for (StackDetails st : stackList) { 1253 dpList.add(st.getDestinationPoint()); 1254 } 1255 return dpList; 1256 } 1257 1258 /** 1259 * Query if a stacked route is in stackList. 1260 * 1261 * @param dp DestinationPoints object 1262 * @param reverse true for a reversed running direction, mostly false 1263 * @return true if dp is in stackList 1264 */ 1265 public boolean isRouteStacked(DestinationPoints dp, boolean reverse) { 1266 Iterator<StackDetails> iter = stackList.iterator(); 1267 while (iter.hasNext()) { 1268 StackDetails st = iter.next(); 1269 if (st.getDestinationPoint() == dp && st.getReverse() == reverse) { 1270 return true; 1271 } 1272 } 1273 return false; 1274 } 1275 1276 /** 1277 * Remove a stacked route from stackList. 1278 * 1279 * @param dp DestinationPoints object 1280 * @param reverse true for a reversed running direction, mostly false 1281 */ 1282 synchronized public void cancelStackedRoute(DestinationPoints dp, boolean reverse) { 1283 Iterator<StackDetails> iter = stackList.iterator(); 1284 while (iter.hasNext()) { 1285 StackDetails st = iter.next(); 1286 if (st.getDestinationPoint() == dp && st.getReverse() == reverse) { 1287 iter.remove(); 1288 } 1289 } 1290 stackPanel.updateGUI(); 1291 if (stackList.isEmpty()) { 1292 stackDialog.setVisible(false); 1293 checkTimer.stop(); 1294 } 1295 } 1296 1297 /** 1298 * Class to collect (stack) routes when they are requested but blocked. 1299 */ 1300 static class StackDetails { 1301 1302 DestinationPoints dp; 1303 boolean reverse; 1304 1305 StackDetails(DestinationPoints dp, boolean reverse) { 1306 this.dp = dp; 1307 this.reverse = reverse; 1308 } 1309 1310 boolean getReverse() { 1311 return reverse; 1312 } 1313 1314 DestinationPoints getDestinationPoint() { 1315 return dp; 1316 } 1317 } 1318 1319 javax.swing.Timer checkTimer = new javax.swing.Timer(10000, (java.awt.event.ActionEvent e) -> { 1320 checkRoute(); 1321 }); 1322 1323 /** 1324 * Step through stackList and activate the first stacked route in line 1325 * if it is no longer blocked. 1326 */ 1327 synchronized void checkRoute() { 1328 checkTimer.stop(); 1329 StackDetails[] tmp = new StackDetails[stackList.size()]; 1330 stackList.toArray(tmp); 1331 1332 for (StackDetails st : tmp) { 1333 if (!st.getDestinationPoint().isActive()) { 1334 // If the route is not already active, then check. 1335 // If the route does get set, then the setting process will remove the route from the stack. 1336 st.getDestinationPoint().setInterlockRoute(st.getReverse()); 1337 } 1338 } 1339 1340 if (!stackList.isEmpty()) { 1341 checkTimer.start(); 1342 } else { 1343 stackDialog.setVisible(false); 1344 } 1345 } 1346 1347 public void removePropertyChangeListener(PropertyChangeListener list, NamedBean obj, LayoutEditor panel) { 1348 if (obj == null) { 1349 return; 1350 } 1351 PointDetails valid = getPointDetails(obj, panel); 1352 if (valid != null) { 1353 valid.removePropertyChangeListener(list); 1354 } 1355 } 1356 1357 boolean runWhenStabilised = false; 1358 LayoutEditor toUseWhenStable; 1359 int interlockTypeToUseWhenStable; 1360 1361 /** 1362 * Discover all possible valid source and destination Signal Mast Logic pairs 1363 * on all Layout Editor panels. 1364 * 1365 * @param editor The Layout Editor panel 1366 * @param interlockType Integer value representing the type of interlocking, one of 1367 * SETUPTURNOUTSONLY, SETUPSIGNALMASTLOGIC or FULLINTERLOCK 1368 * @throws JmriException when an error occurs during discovery 1369 */ 1370 public void automaticallyDiscoverEntryExitPairs(LayoutEditor editor, int interlockType) throws JmriException { 1371 //This is almost a duplicate of that in the DefaultSignalMastLogicManager 1372 runWhenStabilised = false; 1373 jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 1374 if (!lbm.isAdvancedRoutingEnabled()) { 1375 throw new JmriException("advanced routing not enabled"); // NOI18N 1376 } 1377 if (!lbm.routingStablised()) { 1378 runWhenStabilised = true; 1379 toUseWhenStable = editor; 1380 interlockTypeToUseWhenStable = interlockType; 1381 log.debug("Layout block routing has not yet stabilised, discovery will happen once it has"); // NOI18N 1382 return; 1383 } 1384 HashMap<NamedBean, List<NamedBean>> validPaths = lbm.getLayoutBlockConnectivityTools(). 1385 discoverValidBeanPairs(null, Sensor.class, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR); 1386 EntryExitPairs eep = this; 1387 for (Entry<NamedBean, List<NamedBean>> entry : validPaths.entrySet()) { 1388 NamedBean key = entry.getKey(); 1389 List<NamedBean> validDestMast = validPaths.get(key); 1390 if (!validDestMast.isEmpty()) { 1391 eep.addNXSourcePoint(key, editor); 1392 for (int i = 0; i < validDestMast.size(); i++) { 1393 if (!eep.isDestinationValid(key, validDestMast.get(i), editor)) { 1394 eep.addNXDestination(key, validDestMast.get(i), editor); 1395 eep.setEntryExitType(key, editor, validDestMast.get(i), interlockType); 1396 } 1397 } 1398 } 1399 } 1400 1401 firePropertyChange(PROPERTY_AUTO_GENERATE_COMPLETE, null, null); 1402 } 1403 1404 protected PropertyChangeListener propertyBlockManagerListener = new PropertyChangeListener() { 1405 @Override 1406 public void propertyChange(PropertyChangeEvent e) { 1407 if ( LayoutBlockManager.PROPERTY_TOPOLOGY.equals(e.getPropertyName())) { 1408 boolean newValue = (Boolean) e.getNewValue(); 1409 if (newValue) { 1410 if (runWhenStabilised) { 1411 try { 1412 automaticallyDiscoverEntryExitPairs(toUseWhenStable, interlockTypeToUseWhenStable); 1413 } catch (JmriException je) { 1414 //Considered normal if routing not enabled 1415 } 1416 } 1417 } 1418 } 1419 } 1420 }; 1421 1422 public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 1423 1424 } 1425 1426 @Override 1427 public void deleteBean(@Nonnull DestinationPoints bean, @Nonnull String property) throws PropertyVetoException { 1428 1429 } 1430 1431 @Override 1432 @Nonnull 1433 public String getBeanTypeHandled(boolean plural) { 1434 return Bundle.getMessage(plural ? "BeanNameEntryExits" : "BeanNameEntryExit"); // NOI18N 1435 } 1436 1437 /** 1438 * {@inheritDoc} 1439 */ 1440 @Override 1441 public Class<DestinationPoints> getNamedBeanClass() { 1442 return DestinationPoints.class; 1443 } 1444 1445 /** 1446 * {@inheritDoc} 1447 */ 1448 @Override 1449 @OverridingMethodsMustInvokeSuper 1450 public void setPropertyChangesSilenced(@Nonnull String propertyName, boolean silenced) { 1451 if (!Manager.PROPERTY_BEANS.equals(propertyName)) { 1452 throw new IllegalArgumentException("Property " + propertyName + " cannot be silenced."); 1453 } 1454 silencedProperties.put(propertyName, silenced); 1455 if (propertyName.equals(Manager.PROPERTY_BEANS) && !silenced) { 1456 fireIndexedPropertyChange(Manager.PROPERTY_BEANS, getNamedBeanSet().size(), null, null); 1457 } 1458 } 1459 1460 /** {@inheritDoc} */ 1461 @Override 1462 public void addDataListener(ManagerDataListener<DestinationPoints> e) { 1463 if (e != null) listeners.add(e); 1464 } 1465 1466 /** {@inheritDoc} */ 1467 @Override 1468 public void removeDataListener(ManagerDataListener<DestinationPoints> e) { 1469 if (e != null) listeners.remove(e); 1470 } 1471 1472 final List<ManagerDataListener<DestinationPoints>> listeners = new ArrayList<>(); 1473 1474 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EntryExitPairs.class); 1475 1476}