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