001package jmri.jmrit.display.layoutEditor; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.text.MessageFormat; 006import java.util.*; 007 008import javax.annotation.CheckForNull; 009import javax.annotation.Nonnull; 010 011import jmri.*; 012import jmri.util.swing.JmriJOptionPane; 013 014/** 015 * A LayoutTraverser is a representation used by LayoutEditor to display a 016 * traverser. 017 * <p> 018 * A LayoutTraverser has a variable number of connection points, called 019 * SlotTracks. Each of these points should be connected to a TrackSegment. 020 * <p> 021 * Each slot gets its Block information from its 022 * connected track segment. 023 * <p> 024 * Each slot has a unique connection index. The 025 * connection index is set when the SlotTrack is created, and cannot be changed. 026 * This connection index is used to maintain the identity of the radiating 027 * segment to its connected Track Segment as slots are added and deleted by 028 * the user. 029 * <p> 030 * The length and width of the traverser is variable by the user. 031 * 032 * @author Dave Duchamp Copyright (c) 2007 033 * @author George Warner Copyright (c) 2017-2018 034 * @author Dave Sand Copyright (c) 2024 035 */ 036public class LayoutTraverser extends LayoutTrack { 037 038 public LayoutTraverser(@Nonnull String id, @Nonnull LayoutEditor models) { 039 super(id, models); 040 recalculateDimensions(); 041 } 042 043 // defined constants 044 public static final int HORIZONTAL = 0; 045 public static final int VERTICAL = 1; 046 private double slotOffset = 25.0; 047 048 // operational instance variables (not saved between sessions) 049 private NamedBeanHandle<LayoutBlock> namedLayoutBlock = null; 050 051 private boolean dispatcherManaged = false; 052 private boolean turnoutControlled = false; 053 private double deckLength = 100.0; 054 private double deckWidth = 50.0; 055 private int orientation = HORIZONTAL; 056 private boolean mainline = false; 057 private int lastKnownIndex = -1; 058 059 private int signalIconPlacement = 0; // 0: Do Not Place, 1: Left, 2: Right 060 061 private NamedBeanHandle<SignalMast> bufferSignalMast; 062 private NamedBeanHandle<SignalMast> exitSignalMast; 063 064 // persistent instance variables (saved between sessions) 065 066 public final List<SlotTrack> slotList = new ArrayList<>(); // list of Slot Track objects 067 068 /** 069 * Get a string that represents this object. This should only be used for 070 * debugging. 071 * 072 * @return the string 073 */ 074 @Override 075 @Nonnull 076 public String toString() { 077 return "LayoutTraverser " + getName(); 078 } 079 080 // 081 // Accessor methods 082 // 083 public double getSlotOffset() { return slotOffset; } 084 public void setSlotOffset(double offset) { 085 if (!jmri.util.MathUtil.equals(this.slotOffset, offset)) { 086 this.slotOffset = offset; 087 renumberSlots(); 088 } 089 } 090 091 public double getDeckLength() { return deckLength; } 092 private void setDeckLength(double l) { deckLength = l; } 093 public double getDeckWidth() { return deckWidth; } 094 public void setDeckWidth(double w) { 095 if (!jmri.util.MathUtil.equals(deckWidth, w)) { 096 deckWidth = w; 097 recalculateDimensions(); 098 models.redrawPanel(); 099 models.setDirty(); 100 } 101 } 102 public int getOrientation() { return orientation; } 103 public void setOrientation(int o) { 104 if (orientation != o) { 105 orientation = o; 106 } 107 } 108 109 public boolean isDispatcherManaged() { 110 return dispatcherManaged; 111 } 112 113 public void setDispatcherManaged(boolean managed) { 114 if (isDispatcherManaged() && !managed) { 115 if (!isRemoveAllowed()) { 116 return; 117 } 118 } 119 dispatcherManaged = managed; 120 } 121 122 public int getSignalIconPlacement() { 123 return signalIconPlacement; 124 } 125 126 public void setSignalIconPlacement(int placement) { 127 this.signalIconPlacement = placement; 128 } 129 130 public SignalMast getBufferMast() { 131 if (bufferSignalMast == null) { 132 return null; 133 } 134 return bufferSignalMast.getBean(); 135 } 136 137 public String getBufferSignalMastName() { 138 if (bufferSignalMast == null) { 139 return ""; 140 } 141 return bufferSignalMast.getName(); 142 } 143 144 public void setBufferSignalMast(String name) { 145 if (name == null || name.isEmpty()) { 146 bufferSignalMast = null; 147 return; 148 } 149 SignalMast mast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(name); 150 if (mast != null) { 151 bufferSignalMast = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(name, mast); 152 } else { 153 bufferSignalMast = null; 154 } 155 } 156 157 public SignalMast getExitSignalMast() { 158 if (exitSignalMast == null) { 159 return null; 160 } 161 return exitSignalMast.getBean(); 162 } 163 164 public String getExitSignalMastName() { 165 if (exitSignalMast == null) { 166 return ""; 167 } 168 return exitSignalMast.getName(); 169 } 170 171 public void setExitSignalMast(String name) { 172 if (name == null || name.isEmpty()) { 173 exitSignalMast = null; 174 return; 175 } 176 SignalMast mast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(name); 177 if (mast != null) { 178 exitSignalMast = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(name, mast); 179 } else { 180 exitSignalMast = null; 181 } 182 } 183 184 /** 185 * @return the layout block name 186 */ 187 @Nonnull 188 public String getBlockName() { 189 String result = null; 190 if (namedLayoutBlock != null) { 191 result = namedLayoutBlock.getName(); 192 } 193 return ((result == null) ? "" : result); 194 } 195 196 /** 197 * @return the layout block 198 */ 199 @CheckForNull 200 public LayoutBlock getLayoutBlock() { 201 return (namedLayoutBlock != null) ? namedLayoutBlock.getBean() : null; 202 } 203 204 /** 205 * Set up a LayoutBlock for this LayoutTraverser. 206 * 207 * @param newLayoutBlock the LayoutBlock to set 208 */ 209 public void setLayoutBlock(@CheckForNull LayoutBlock newLayoutBlock) { 210 LayoutBlock layoutBlock = getLayoutBlock(); // This is for the traverser itself 211 if (layoutBlock != newLayoutBlock) { 212 /// block has changed, if old block exists, decrement use 213 if (layoutBlock != null) { 214 layoutBlock.decrementUse(); 215 } 216 if (newLayoutBlock != null) { 217 String newName = newLayoutBlock.getUserName(); 218 if ((newName != null) && !newName.isEmpty()) { 219 namedLayoutBlock = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(newName, newLayoutBlock); 220 } else { 221 namedLayoutBlock = null; 222 } 223 } else { 224 namedLayoutBlock = null; 225 } 226 } 227 } 228 229 /** 230 * Set up a LayoutBlock for this LayoutTraverser. 231 * 232 * @param name the name of the new LayoutBlock 233 */ 234 public void setLayoutBlockByName(@CheckForNull String name) { 235 if ((name != null) && !name.isEmpty()) { 236 setLayoutBlock(models.provideLayoutBlock(name)); 237 } 238 } 239 240 public void addSlotPair() { 241 addSlot(0); // Side A 242 addSlot(0); // Side B 243 renumberSlots(); 244 } 245 246 private SlotTrack addSlot(double offset) { 247 SlotTrack rt = new SlotTrack(offset, getNewIndex()); 248 slotList.add(rt); 249 return rt; 250 } 251 252 private int getNewIndex() { 253 int index = -1; 254 if (slotList.isEmpty()) { 255 return 0; 256 } 257 258 boolean found = true; 259 while (found) { 260 index++; 261 found = false; // assume failure (pessimist!) 262 for (SlotTrack rt : slotList) { 263 if (index == rt.getConnectionIndex()) { 264 found = true; 265 } 266 } 267 } 268 return index; 269 } 270 271 // the following method is only for use in loading layout traversers 272 public void addSlotTrack(double offset, int index, String name) { // Note: This was likely private or package-private 273 SlotTrack rt = new SlotTrack(offset, index); 274 slotList.add(rt); 275 rt.connectName = name; 276 } 277 278 /** 279 * Get the connection for the slot with this index. 280 * 281 * @param index the index 282 * @return the connection for the slot with this value of getConnectionIndex 283 */ 284 @CheckForNull 285 public TrackSegment getSlotConnectIndexed(int index) { 286 TrackSegment result = null; 287 for (SlotTrack rt : slotList) { 288 if (rt.getConnectionIndex() == index) { 289 result = rt.getConnect(); 290 break; 291 } 292 } 293 return result; 294 } 295 296 /** 297 * Get the connection for the slot at the index in the slotList. 298 * 299 * @param i the index in the slotList 300 * @return the connection for the slot at that index in the slotList or null 301 */ 302 @CheckForNull 303 public TrackSegment getSlotConnectOrdered(int i) { 304 TrackSegment result = null; 305 306 if (i < slotList.size()) { 307 SlotTrack rt = slotList.get(i); 308 if (rt != null) { 309 result = rt.getConnect(); 310 } 311 } 312 return result; 313 } 314 315 /** 316 * Set the connection for the slot at the index in the slotList. 317 * 318 * @param ts the connection 319 * @param index the index in the slotList 320 */ 321 public void setSlotConnect(@CheckForNull TrackSegment ts, int index) { 322 for (SlotTrack rt : slotList) { 323 if (rt.getConnectionIndex() == index) { 324 rt.setConnect(ts); 325 break; 326 } 327 } 328 } 329 330 // should only be used by xml save code 331 @Nonnull 332 public List<SlotTrack> getSlotList() { 333 return slotList; 334 } 335 336 /** 337 * Get the number of slots on traverser. 338 * 339 * @return the number of slots 340 */ 341 public int getNumberSlots() { 342 return slotList.size(); 343 } 344 345 /** 346 * Get the index for the slot at this position in the slotList. 347 * 348 * @param i the position in the slotList 349 * @return the index 350 */ 351 public int getSlotIndex(int i) { 352 int result = 0; 353 if (i < slotList.size()) { 354 SlotTrack rt = slotList.get(i); 355 result = rt.getConnectionIndex(); 356 } 357 return result; 358 } 359 360 /** 361 * Get the offset for the slot at this position in the slotList. 362 * 363 * @param i the position in the slotList 364 * @return the offset 365 */ 366 public double getSlotOffsetValue(int i) { 367 double result = 0.0; 368 if (i < slotList.size()) { 369 SlotTrack rt = slotList.get(i); 370 result = rt.getOffset(); 371 } 372 return result; 373 } 374 375 /** 376 * Set the turnout and state for the slot with this index. 377 * 378 * @param index the index 379 * @param turnoutName the turnout name 380 * @param state the state 381 */ 382 public void setSlotTurnout(int index, @CheckForNull String turnoutName, int state) { 383 boolean found = false; // assume failure (pessimist!) 384 for (SlotTrack rt : slotList) { 385 if (rt.getConnectionIndex() == index) { 386 rt.setTurnout(turnoutName, state); 387 found = true; 388 break; 389 } 390 } 391 if (!found) { 392 log.error("{}.setSlotTurnout({}, {}, {}); Attempt to add Turnout control to a non-existant slot track", 393 getName(), index, turnoutName, state); 394 } 395 } 396 397 /** 398 * Get the name of the turnout for the slot at this index. 399 * 400 * @param i the index 401 * @return name of the turnout for the slot at this index 402 */ 403 @CheckForNull 404 public String getSlotTurnoutName(int i) { 405 String result = null; 406 if (i < slotList.size()) { 407 SlotTrack rt = slotList.get(i); 408 result = rt.getTurnoutName(); 409 } 410 return result; 411 } 412 413 /** 414 * Get the turnout for the slot at this index. 415 * 416 * @param i the index 417 * @return the turnout for the slot at this index 418 */ 419 @CheckForNull 420 public Turnout getSlotTurnout(int i) { 421 Turnout result = null; 422 if (i < slotList.size()) { 423 SlotTrack rt = slotList.get(i); 424 result = rt.getTurnout(); 425 } 426 return result; 427 } 428 429 /** 430 * Get the state of the turnout for the slot at this index. 431 * 432 * @param i the index 433 * @return state of the turnout for the slot at this index 434 */ 435 public int getSlotTurnoutState(int i) { 436 int result = 0; 437 if (i < slotList.size()) { 438 SlotTrack rt = slotList.get(i); 439 result = rt.getTurnoutState(); 440 } 441 return result; 442 } 443 444 /** 445 * Get if the slot at this index is disabled. 446 * 447 * @param i the index 448 * @return true if disabled 449 */ 450 public boolean isSlotDisabled(int i) { 451 boolean result = false; // assume not disabled 452 if (i < slotList.size()) { 453 SlotTrack rt = slotList.get(i); 454 result = rt.isDisabled(); 455 } 456 return result; 457 } 458 459 /** 460 * Set the disabled state of the slot at this index. 461 * 462 * @param i the index 463 * @param boo the state 464 */ 465 public void setSlotDisabled(int i, boolean boo) { 466 if (i < slotList.size()) { 467 SlotTrack rt = slotList.get(i); 468 rt.setDisabled(boo); 469 } 470 } 471 472 /** 473 * Get the disabled when occupied state of the slot at this index. 474 * 475 * @param i the index 476 * @return the state 477 */ 478 public boolean isSlotDisabledWhenOccupied(int i) { 479 boolean result = false; // assume not disabled when occupied 480 if (i < slotList.size()) { 481 SlotTrack rt = slotList.get(i); 482 result = rt.isDisabledWhenOccupied(); 483 } 484 return result; 485 } 486 487 /** 488 * Set the disabled when occupied state of the slot at this index. 489 * 490 * @param i the index 491 * @param boo the state 492 */ 493 public void setSlotDisabledWhenOccupied(int i, boolean boo) { 494 if (i < slotList.size()) { 495 SlotTrack rt = slotList.get(i); 496 rt.setDisabledWhenOccupied(boo); 497 } 498 } 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override 504 public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException { 505 LayoutTrack result = null; 506 if (HitPointType.isTraverserSlotHitType(connectionType)) { 507 result = getSlotConnectIndexed(connectionType.traverserTrackIndex()); 508 } else { 509 String errString = MessageFormat.format("{0}.getCoordsForConnectionType({1}); Invalid connection type", 510 getName(), connectionType); // NOI18N 511 log.error("will throw {}", errString); // NOI18N 512 throw new jmri.JmriException(errString); 513 } 514 return result; 515 } 516 517 518 /** 519 * {@inheritDoc} 520 */ 521 @Override 522 public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException { 523 if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) { 524 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid type", 525 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 526 log.error("will throw {}", errString); // NOI18N 527 throw new jmri.JmriException(errString); 528 } 529 if (HitPointType.isTraverserSlotHitType(connectionType)) { 530 if ((o == null) || (o instanceof TrackSegment)) { 531 setSlotConnect((TrackSegment) o, connectionType.traverserTrackIndex()); 532 } else { 533 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid object: {4}", 534 getName(), connectionType, o.getName(), 535 type, o.getClass().getName()); // NOI18N 536 log.error("will throw {}", errString); // NOI18N 537 throw new jmri.JmriException(errString); 538 } 539 } else { 540 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid connection type", 541 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 542 log.error("will throw {}", errString); // NOI18N 543 throw new jmri.JmriException(errString); 544 } 545 } 546 547 548 /** 549 * Test if slot with this index is a mainline track or not. 550 * <p> 551 * Defaults to false (not mainline) if connecting track segment is missing. 552 * 553 * @param index the index 554 * @return true if connecting track segment is mainline 555 */ 556 public boolean isMainlineIndexed(int index) { 557 boolean result = false; // assume failure (pessimist!) 558 559 for (SlotTrack rt : slotList) { 560 if (rt.getConnectionIndex() == index) { 561 TrackSegment ts = rt.getConnect(); 562 if (ts != null) { 563 result = ts.isMainline(); 564 break; 565 } 566 } 567 } 568 return result; 569 } 570 571 /** 572 * Test if slot at this index is a mainline track or not. 573 * <p> 574 * Defaults to false (not mainline) if connecting track segment is missing 575 * 576 * @param i the index 577 * @return true if connecting track segment is mainline 578 */ 579 public boolean isMainlineOrdered(int i) { 580 boolean result = false; // assume failure (pessimist!) 581 if (i < slotList.size()) { 582 SlotTrack rt = slotList.get(i); 583 if (rt != null) { 584 TrackSegment ts = rt.getConnect(); 585 if (ts != null) { 586 result = ts.isMainline(); 587 } 588 } 589 } 590 return result; 591 } 592 593 @Override 594 public boolean isMainline() { 595 return mainline; 596 } 597 598 /** 599 * Set the mainline status of the traverser bridge itself. 600 * @param main true if the bridge is mainline, false otherwise. 601 */ 602 public void setMainline(boolean main) { 603 if (mainline != main) { 604 mainline = main; 605 models.redrawPanel(); 606 models.setDirty(); 607 } 608 } 609 610 public String tLayoutBlockName = ""; 611 public String tExitSignalMastName = ""; 612 public String tBufferSignalMastName = ""; 613 614 /** 615 * Initialization method The name of each track segment connected to a slot 616 * track is initialized by by LayoutTraverserXml, then the following method 617 * is called after the entire LayoutEditor is loaded to set the specific 618 * TrackSegment objects. 619 * 620 * @param p the layout editor 621 */ 622 @Override 623 public void setObjects(@Nonnull LayoutEditor p) { 624 if (tLayoutBlockName != null && !tLayoutBlockName.isEmpty()) { 625 setLayoutBlockByName(tLayoutBlockName); 626 } 627 tLayoutBlockName = null; /// release this memory 628 629 if (tBufferSignalMastName != null && !tBufferSignalMastName.isEmpty()) { 630 setBufferSignalMast(tBufferSignalMastName); 631 } 632 tBufferSignalMastName = null; 633 if (tExitSignalMastName != null && !tExitSignalMastName.isEmpty()) { 634 setExitSignalMast(tExitSignalMastName); 635 } 636 tExitSignalMastName = null; 637 638 slotList.forEach((rt) -> { 639 if (!rt.isDisabled()) { 640 log.debug("Traverser '{}': trying to connect slot index {} to track '{}'", getName(), rt.getConnectionIndex(), rt.connectName); 641 TrackSegment connectedTrack = p.getFinder().findTrackSegmentByName(rt.connectName); 642 log.debug("connectedTrack {}", connectedTrack == null ? "null" : connectedTrack.getName()); 643 rt.setConnect(connectedTrack); 644 if (connectedTrack == null && rt.connectName != null && !rt.connectName.isEmpty()) { 645 log.warn("Traverser '{}': FAILED to find track segment for connection name '{}'", getName(), rt.connectName); 646 } 647 if (rt.approachMastName != null && !rt.approachMastName.isEmpty()) { 648 rt.setApproachMast(rt.approachMastName); 649 } 650 } 651 }); 652 653 // Recalculate dimensions now that all slots are loaded 654 recalculateDimensions(); 655 } 656 657 /** 658 * Is this traverser turnout controlled? 659 * 660 * @return true if so 661 */ 662 public boolean isTurnoutControlled() { 663 return turnoutControlled; 664 } 665 666 /** 667 * Set if this traverser is turnout controlled. 668 * 669 * @param boo set true if so 670 */ 671 public void setTurnoutControlled(boolean boo) { 672 turnoutControlled = boo; 673 } 674 675 /** 676 * Set traverser position to the slot with this index. 677 * 678 * @param index the index 679 */ 680 public void setPosition(int index) { 681 if (isTurnoutControlled()) { 682 boolean found = false; // assume failure (pessimist!) 683 for (SlotTrack rt : slotList) { 684 if (rt.getConnectionIndex() == index) { 685 lastKnownIndex = index; 686 rt.setPosition(); 687 models.redrawPanel(); 688 models.setDirty(); 689 found = true; 690 break; 691 } 692 } 693 if (!found) { 694 log.error("{}.setPosition({}); Attempt to set the position on a non-existant slot track", 695 getName(), index); 696 } 697 } 698 } 699 700 /** 701 * Get the traverser position. 702 * 703 * @return the traverser position 704 */ 705 public int getPosition() { 706 return lastKnownIndex; 707 } 708 709 public void deleteTrackPair(int pairIndex) { 710 if (pairIndex < 0 || (pairIndex * 2 + 1) >= slotList.size()) { 711 return; 712 } 713 SlotTrack slotB = slotList.get(pairIndex * 2 + 1); 714 SlotTrack slotA = slotList.get(pairIndex * 2); 715 716 slotList.remove(slotB); 717 slotList.remove(slotA); 718 slotB.dispose(); 719 slotA.dispose(); 720 renumberSlots(); 721 } 722 723 private void renumberSlots() { 724 int numPairs = getNumberSlots() / 2; 725 double totalWidth = (numPairs > 1) ? (numPairs - 1) * slotOffset : 0; 726 double firstOffset = -totalWidth / 2.0; 727 728 for (int i = 0; i < numPairs; i++) { 729 double offset = firstOffset + (i * slotOffset); 730 slotList.get(i * 2).setOffset(offset); 731 slotList.get(i * 2 + 1).setOffset(offset); 732 } 733 recalculateDimensions(); 734 } 735 736 private void recalculateDimensions() { 737 int numPairs = getNumberSlots() / 2; 738 739 double newLength = (numPairs > 0) ? (((slotOffset - 1) * numPairs)) : slotOffset; 740 setDeckLength(newLength); 741 } 742 743 public void moveSlotPairUp(int pairIndex) { 744 if (pairIndex > 0) { 745 Collections.swap(slotList, pairIndex * 2, (pairIndex - 1) * 2); 746 Collections.swap(slotList, pairIndex * 2 + 1, (pairIndex - 1) * 2 + 1); 747 renumberSlots(); 748 } 749 } 750 751 public void moveSlotPairDown(int pairIndex) { 752 if (pairIndex < (getNumberSlots() / 2) - 1) { 753 Collections.swap(slotList, pairIndex * 2, (pairIndex + 1) * 2); 754 Collections.swap(slotList, pairIndex * 2 + 1, (pairIndex + 1) * 2 + 1); 755 renumberSlots(); 756 } 757 } 758 759 /** 760 * Remove this object from display and persistance. 761 */ 762 public void remove() { 763 // remove from persistance by flagging inactive 764 active = false; 765 } 766 767 private boolean active = true; 768 769 /** 770 * Get if traverser is active. 771 * "active" means that the object is still displayed, and should be stored. 772 * @return true if active, else false. 773 */ 774 public boolean isActive() { 775 return active; 776 } 777 778 /** 779 * Checks if the given mast is an approach mast for any slot on this traverser. 780 * @param mast The SignalMast to check. 781 * @return true if it is an approach mast for one of the slots. 782 */ 783 public boolean isApproachMast(SignalMast mast) { 784 if (mast == null) { 785 return false; 786 } 787 for (SlotTrack slot : slotList) { 788 if (mast.equals(slot.getApproachMast())) { 789 return true; 790 } 791 } 792 return false; 793 } 794 795 /** 796 * Checks if the given block is one of the slot blocks for this traverser. 797 * @param block The Block to check. 798 * @return true if it is a block for one of the slots. 799 */ 800 public boolean isSlotBlock(Block block) { 801 if (block == null) { 802 return false; 803 } 804 for (SlotTrack ray : slotList) { 805 TrackSegment ts = ray.getConnect(); 806 if (ts != null && ts.getLayoutBlock() != null && block.equals(ts.getLayoutBlock().getBlock())) { 807 return true; 808 } 809 } 810 return false; 811 } 812 813 814 public class SlotTrack { 815 816 /** 817 * constructor for SlotTracks 818 * 819 * @param offset its offset 820 * @param index its index 821 */ 822 public SlotTrack(double offset, int index) { 823 this.offset = offset; 824 connect = null; 825 connectionIndex = index; 826 827 disabled = false; 828 disableWhenOccupied = false; 829 } 830 831 // persistant instance variables 832 private double offset = 0.0; 833 private TrackSegment connect = null; 834 private int connectionIndex = -1; 835 836 private boolean disabled = false; 837 private boolean disableWhenOccupied = false; 838 private NamedBeanHandle<SignalMast> approachMast; 839 840 // 841 // Accessor routines 842 // 843 /** 844 * Set slot track disabled. 845 * 846 * @param boo set true to disable 847 */ 848 public void setDisabled(boolean boo) { 849 if (disabled != boo) { 850 disabled = boo; 851 if (models != null) { 852 models.redrawPanel(); 853 } 854 } 855 } 856 857 /** 858 * Is this slot track disabled? 859 * 860 * @return true if so 861 */ 862 public boolean isDisabled() { 863 return disabled; 864 } 865 866 /** 867 * Set slot track disabled if occupied. 868 * 869 * @param boo set true to disable if occupied 870 */ 871 public void setDisabledWhenOccupied(boolean boo) { 872 if (disableWhenOccupied != boo) { 873 disableWhenOccupied = boo; 874 if (models != null) { 875 models.redrawPanel(); 876 } 877 } 878 } 879 880 /** 881 * Is slot track disabled if occupied? 882 * 883 * @return true if so 884 */ 885 public boolean isDisabledWhenOccupied() { 886 return disableWhenOccupied; 887 } 888 889 /** 890 * get the track segment connected to this slot 891 * 892 * @return the track segment connected to this slot 893 */ 894 public TrackSegment getConnect() { 895 // This should be a simple getter, just like in LayoutTurntable.RayTrack. 896 // All connection resolution happens in LayoutTraverser.setObjects(). 897 return connect; 898 } 899 900 /** 901 * Set the track segment connected to this slot. 902 * 903 * @param ts the track segment to connect to this slot 904 */ 905 public void setConnect(TrackSegment ts) { 906 // This should ONLY set the live object reference, just like in LayoutTurntable.RayTrack. 907 // The public 'connectName' field is managed separately by the editor and XML loader. 908 connect = ts; 909 } 910 911 /** 912 * Get the offset for this slot. 913 * 914 * @return the offset for this slot 915 */ 916 public double getOffset() { 917 return offset; 918 } 919 920 /** 921 * Set the offset for this slot. 922 * 923 * @param o the offset for this slot 924 */ 925 public void setOffset(double o) { 926 this.offset = o; 927 } 928 929 /** 930 * Get the connection index for this slot. 931 * 932 * @return the connection index for this slot 933 */ 934 public int getConnectionIndex() { 935 return connectionIndex; 936 } 937 938 /** 939 * Get the approach signal mast for this slot. 940 * @return The signal mast, or null. 941 */ 942 public SignalMast getApproachMast() { 943 if (approachMast == null) { 944 return null; 945 } 946 return approachMast.getBean(); 947 } 948 949 public String getApproachMastName() { 950 if (approachMast == null) { 951 return ""; 952 } 953 return approachMast.getName(); 954 } 955 956 /** 957 * Set the approach signal mast for this slot by name. 958 * @param name The name of the signal mast. 959 */ 960 public void setApproachMast(String name) { 961 if (name == null || name.isEmpty()) { 962 approachMast = null; 963 return; 964 } 965 SignalMast mast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(name); 966 if (mast != null) { 967 approachMast = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(name, mast); 968 } else { 969 approachMast = null; 970 } 971 } 972 973 /** 974 * Is this slot occupied? 975 * 976 * @return true if occupied 977 */ 978 public boolean isOccupied() { 979 boolean result = false; // assume not 980 if (connect != null) { 981 LayoutBlock lb = connect.getLayoutBlock(); 982 if (lb != null) { 983 result = (lb.getOccupancy() == LayoutBlock.OCCUPIED); 984 } 985 } 986 return result; 987 } 988 989 // initialization instance variable (used when loading a LayoutEditor) 990 public String connectName = ""; 991 public String approachMastName = ""; 992 993 private NamedBeanHandle<Turnout> namedTurnout; 994 private int turnoutState; 995 private PropertyChangeListener mTurnoutListener; 996 997 /** 998 * Set the turnout and state for this slot track. 999 * 1000 * @param turnoutName the turnout name 1001 * @param state its state 1002 */ 1003 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", 1004 justification="2nd check of turnoutName is considered redundant by SpotBugs, but required by ecj") // temporary 1005 public void setTurnout(@Nonnull String turnoutName, int state) { 1006 Turnout turnout = null; 1007 if (mTurnoutListener == null) { 1008 mTurnoutListener = (PropertyChangeEvent e) -> { 1009 int turnoutState = getTurnout().getKnownState(); 1010 if (turnoutState == Turnout.THROWN) { 1011 // This slot is now the active one. 1012 // Update the traverser's position indicator. 1013 if (lastKnownIndex != connectionIndex) { 1014 lastKnownIndex = connectionIndex; 1015 models.redrawPanel(); 1016 models.setDirty(); 1017 } 1018 1019 // Command all other slot turnouts to CLOSED. 1020 for (SlotTrack otherSlot : LayoutTraverser.this.slotList) { 1021 if (otherSlot != this && otherSlot.getTurnout() != null) { 1022 // Check state before commanding to prevent potential listener loops 1023 if (otherSlot.getTurnout().getCommandedState() != Turnout.CLOSED) { 1024 otherSlot.getTurnout().setCommandedState(Turnout.CLOSED); 1025 } 1026 } 1027 } 1028 } else if (turnoutState == Turnout.CLOSED) { 1029 // This turnout is now closed. Check if all are closed. 1030 boolean allClosed = true; 1031 for (SlotTrack otherSlot : LayoutTraverser.this.slotList) { 1032 if (otherSlot.getTurnout() != null && otherSlot.getTurnout().getKnownState() != Turnout.CLOSED) { 1033 allClosed = false; 1034 break; 1035 } 1036 } 1037 if (allClosed && lastKnownIndex != -1) { 1038 lastKnownIndex = -1; // All turnouts are closed, blank the bridge 1039 models.redrawPanel(); 1040 models.setDirty(); 1041 } 1042 } 1043 }; 1044 } 1045 if (turnoutName != null) { 1046 turnout = jmri.InstanceManager.turnoutManagerInstance().getTurnout(turnoutName); 1047 } 1048 if (namedTurnout != null && namedTurnout.getBean() != turnout) { 1049 namedTurnout.getBean().removePropertyChangeListener(mTurnoutListener); 1050 } 1051 if (turnout != null && (namedTurnout == null || namedTurnout.getBean() != turnout)) { 1052 if (turnoutName != null && !turnoutName.isEmpty()) { 1053 namedTurnout = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutName, turnout); 1054 turnout.addPropertyChangeListener(mTurnoutListener, turnoutName, "Layout Editor Traverser"); 1055 } 1056 } 1057 if (turnout == null) { 1058 namedTurnout = null; 1059 } 1060 1061 if (this.turnoutState != state) { 1062 this.turnoutState = state; 1063 } 1064 } 1065 1066 /** 1067 * Set the position for this slot track. 1068 */ 1069 public void setPosition() { 1070 if (namedTurnout != null) { 1071 if (disableWhenOccupied && isOccupied()) { // isOccupied is on SlotTrack, so check must be here 1072 log.debug("Can not setPosition of traverser slot when it is occupied"); 1073 } else { 1074 // The listener attached to the turnout will handle de-selecting other slots 1075 // by setting their turnouts to CLOSED. 1076 getTurnout().setCommandedState(Turnout.THROWN); 1077 } 1078 } 1079 } 1080 1081 /** 1082 * Get the turnout for this slot track. 1083 * 1084 * @return the turnout or null 1085 */ 1086 public Turnout getTurnout() { 1087 if (namedTurnout == null) { 1088 return null; 1089 } 1090 return namedTurnout.getBean(); 1091 } 1092 1093 /** 1094 * Get the turnout name for the slot track. 1095 * 1096 * @return the turnout name 1097 */ 1098 @CheckForNull 1099 public String getTurnoutName() { 1100 if (namedTurnout == null) { 1101 return null; 1102 } 1103 return namedTurnout.getName(); 1104 } 1105 1106 /** 1107 * Get the state for the turnout for this slot track. 1108 * 1109 * @return the state 1110 */ 1111 public int getTurnoutState() { 1112 return turnoutState; 1113 } 1114 1115 /** 1116 * Dispose of this slot track. 1117 */ 1118 void dispose() { 1119 if (getTurnout() != null) { 1120 getTurnout().removePropertyChangeListener(mTurnoutListener); 1121 } 1122 if (lastKnownIndex == connectionIndex) { 1123 lastKnownIndex = -1; 1124 } 1125 } 1126 } // class SlotTrack 1127 1128 /** 1129 * {@inheritDoc} 1130 */ 1131 @Override 1132 protected void reCheckBlockBoundary() { 1133 // nothing to see here... move along... 1134 } 1135 1136 /** 1137 * {@inheritDoc} 1138 */ 1139 @Override 1140 @CheckForNull 1141 protected List<LayoutConnectivity> getLayoutConnectivity() { 1142 // nothing to see here... move along... 1143 return null; 1144 } 1145 1146 /** 1147 * {@inheritDoc} 1148 */ 1149 @Override 1150 @Nonnull 1151 public List<HitPointType> checkForFreeConnections() { 1152 List<HitPointType> result = new ArrayList<>(); 1153 1154 for (int k = 0; k < getNumberSlots(); k++) { 1155 var slotTrack = slotList.get(k); 1156 if (slotTrack != null && slotTrack.getConnect() == null && !slotTrack.isDisabled()) { 1157 result.add(HitPointType.traverserTrackIndexedValue(k)); 1158 } 1159 } 1160 1161 return result; 1162 } 1163 1164 /** 1165 * {@inheritDoc} 1166 */ 1167 @Override 1168 public boolean checkForUnAssignedBlocks() { 1169 // Layout turnouts get their block information from the 1170 // track segments attached to their slots so... 1171 // nothing to see here... move along... 1172 return true; 1173 } 1174 1175 /** 1176 * Checks if the path represented by the blocks crosses this traverser. 1177 * @param block1 A block in the path. 1178 * @param block2 Another block in the path. 1179 * @return true if the path crosses this traverser. 1180 */ 1181 public boolean isTraverserBoundary(Block block1, Block block2) { 1182 if (getLayoutBlock() == null) { 1183 return false; 1184 } 1185 Block traverserBlock = getLayoutBlock().getBlock(); 1186 if (traverserBlock == null) { 1187 return false; 1188 } 1189 1190 // Case 1: Moving to/from the traverser block itself. 1191 if ((block1 == traverserBlock && isSlotBlock(block2)) || 1192 (block2 == traverserBlock && isSlotBlock(block1))) { 1193 return true; 1194 } 1195 1196 // Case 2: Moving between two slot blocks (crossing over the traverser). 1197 if (isSlotBlock(block1) && isSlotBlock(block2)) { 1198 return true; 1199 } 1200 return false; 1201 } 1202 1203 /** 1204 * Gets the list of turnouts and their required states to align the traverser 1205 * for a path defined by the given blocks. 1206 * 1207 * @param curBlock The current block in the train's path. 1208 * @param prevBlock The previous block in the train's path. 1209 * @param nextBlock The next block in the train's path. 1210 * @return A list of LayoutTrackExpectedState objects for the turnouts. 1211 */ 1212 public List<LayoutTrackExpectedState<LayoutTurnout>> getTurnoutList(Block curBlock, Block prevBlock, Block nextBlock) { 1213 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList = new ArrayList<>(); 1214 if (!isTurnoutControlled()) { 1215 return turnoutList; 1216 } 1217 1218 Block traverserBlock = (getLayoutBlock() != null) ? getLayoutBlock().getBlock() : null; 1219 if (traverserBlock == null) { 1220 return turnoutList; 1221 } 1222 1223 int targetRay = -1; 1224 1225 // Determine which slot needs to be aligned. 1226 if (prevBlock == traverserBlock) { 1227 // Train is leaving the traverser, so align to the destination slot. 1228 targetRay = getSlotForBlock(curBlock); 1229 } else if (curBlock == traverserBlock) { 1230 // Train is entering the traverser, so align to the approaching slot. 1231 targetRay = getSlotForBlock(prevBlock); 1232 } 1233 1234 if (targetRay != -1) { 1235 Turnout t = getSlotTurnout(targetRay); 1236 if (t != null) { 1237 // Create a temporary LayoutTurnout wrapper for the dispatcher. 1238 // This object is not on a panel and is for logic purposes only. 1239 LayoutLHTurnout tempLayoutTurnout = new LayoutLHTurnout("TRAVERSER_WRAPPER_" + t.getSystemName(), models); 1240 tempLayoutTurnout.setTurnout(t.getSystemName()); 1241 int requiredState = Turnout.THROWN; 1242 1243 log.debug("Adding traverser turnout {} to list with required state {}", t.getDisplayName(), requiredState); 1244 turnoutList.add(new LayoutTrackExpectedState<>(tempLayoutTurnout, requiredState)); 1245 } 1246 } 1247 return turnoutList; 1248 } 1249 1250 private int getSlotForBlock(Block block) { 1251 if (block == null) return -1; 1252 for (int i = 0; i < getNumberSlots(); i++) { 1253 TrackSegment ts = getSlotConnectOrdered(i); 1254 if (ts != null && ts.getLayoutBlock() != null && ts.getLayoutBlock().getBlock() == block) { 1255 return i; 1256 } 1257 } 1258 return -1; 1259 } 1260 1261 /** 1262 * Check for conditions that will caused subsequent errors if the the traverser slot is removed. 1263 * Both slot connections have to be clear. 1264 * @param pairIndex The traverser slot to be deleted. 1265 * @return true if the slot can be deleted. 1266 */ 1267 public boolean isSlotDeleteAllowed(int pairIndex) { 1268 if (pairIndex < 0 || (pairIndex * 2 + 1) >= slotList.size()) { 1269 log.warn("pairIndex out of range, {}, for isSlotDeleteAllowed", pairIndex); 1270 return false; 1271 } 1272 1273 var deleteOk = true; 1274 var msg = new StringBuilder(); 1275 1276 for (int idx = 0; idx < 2; idx++) { 1277 var slot = slotList.get(pairIndex * 2 + idx); 1278 msg.append(isSlotConnectionClear(slot)); 1279 } 1280 1281 if (msg.length() > 0) { 1282 msg.insert(0, Bundle.getMessage("TV_Message_Header")); 1283 msg.append(Bundle.getMessage("TV_Message_Slot_Delete")); 1284 JmriJOptionPane.showMessageDialog(models, 1285 msg.toString(), 1286 Bundle.getMessage("WarningTitle"), // NOI18N 1287 JmriJOptionPane.WARNING_MESSAGE); 1288 deleteOk = false; 1289 } 1290 1291 return deleteOk; 1292 } 1293 1294 /** 1295 * Check for a connected track segment, signal masts or SML. 1296 * @param track The A or B side of a slot. 1297 * @return an empty StringBuilder if clear or a set of messages. 1298 */ 1299 public StringBuilder isSlotConnectionClear(SlotTrack track) { 1300 var smlManager = InstanceManager.getDefault(SignalMastLogicManager.class); 1301 var msg = new StringBuilder(); 1302 1303 var trackSegment = track.getConnect(); 1304 if (trackSegment != null) { 1305 msg.append(Bundle.getMessage("TV_Connection")); 1306 } 1307 1308 // slot approach mast 1309 var approachMast = track.getApproachMast(); 1310 if (approachMast != null && smlManager.isSignalMastUsed(approachMast)) { 1311 msg.append(Bundle.getMessage("TTV_Approach_SML", approachMast.getDisplayName())); 1312 } 1313 1314 if (approachMast != null && models.containsSignalMast(approachMast)) { 1315 msg.append(Bundle.getMessage("TTV_Approach_Mast", approachMast.getDisplayName())); 1316 } 1317 1318 // slot destination mast attached to end bumper or anchor point. 1319 if (trackSegment != null) { 1320 SignalMast destMast = null; 1321 1322 if (trackSegment.getType1() == HitPointType.POS_POINT) { 1323 destMast = getDestinationMast(trackSegment, trackSegment.getConnect1()); 1324 } else if (trackSegment.getType2() == HitPointType.POS_POINT) { 1325 destMast = getDestinationMast(trackSegment, trackSegment.getConnect2()); 1326 } 1327 1328 if (destMast != null && smlManager.isSignalMastUsed(destMast)) { 1329 msg.append(Bundle.getMessage("TTV_Approach_SML", destMast.getDisplayName())); 1330 } 1331 1332 if (destMast != null && models.containsSignalMast(destMast)) { 1333 msg.append(Bundle.getMessage("TTV_Destination_Mast", destMast.getDisplayName())); 1334 } 1335 } 1336 1337 return msg; 1338 } 1339 1340 /** 1341 * Check for conditions that will caused subsequent errors if the the traverser is removed 1342 * or dispatcherManaged is changed from true to false. 1343 * - The exit mast is a SML source. 1344 * - The buffer mast is a SML destination. 1345 * - Exit, buffer or approach masts are on the panel. 1346 */ 1347 public boolean isRemoveAllowed() { 1348 var smlManager = InstanceManager.getDefault(SignalMastLogicManager.class); 1349 var removeOk = true; 1350 var msg = new StringBuilder(); 1351 1352 // Exit SML and icon 1353 var exit = getExitSignalMast(); 1354 if (exit != null && smlManager.isSignalMastUsed(exit)) { 1355 msg.append(Bundle.getMessage("TTV_Exit_SML", exit.getDisplayName())); 1356 } 1357 if (exit != null && models.containsSignalMast(exit)) { 1358 msg.append(Bundle.getMessage("TTV_Exit_Mast", exit.getDisplayName())); 1359 } 1360 1361 // Buffer SML and icon 1362 var buffer = getBufferMast(); 1363 if (buffer != null && smlManager.isSignalMastUsed(buffer)) { 1364 msg.append(Bundle.getMessage("TTV_Buffer_SML", buffer.getDisplayName())); 1365 } 1366 if (buffer != null && models.containsSignalMast(buffer)) { 1367 msg.append(Bundle.getMessage("TTV_Buffer_Mast", buffer.getDisplayName())); 1368 } 1369 1370 for (var slotTrack : slotList) { 1371 msg.append(isSlotConnectionClear(slotTrack)); 1372 } 1373 1374 if (msg.length() > 0) { 1375 msg.insert(0, Bundle.getMessage("TV_Message_Header")); 1376 msg.append(Bundle.getMessage("TV_Message_Remove")); 1377 msg.append(Bundle.getMessage("TTV_Actions")); 1378 msg.append(Bundle.getMessage("TTV_Dispatcher")); 1379 JmriJOptionPane.showMessageDialog(models, 1380 msg.toString(), 1381 Bundle.getMessage("WarningTitle"), // NOI18N 1382 JmriJOptionPane.WARNING_MESSAGE); 1383 removeOk = false; 1384 } 1385 1386 return removeOk; 1387 } 1388 1389 /** 1390 * Check for the presence of destination masts at end bumper and anchor points. 1391 * These are the masts used by the Exit mast SML. 1392 * @param trackSegment The track segment for a turnout ray. 1393 * @param connection The end bumper or anchor point at the end of the track segment. 1394 * @return the destination mast at the end bumper or anchor point. Return null if none assigned. 1395 */ 1396 private SignalMast getDestinationMast(TrackSegment trackSegment, LayoutTrack connection) { 1397 SignalMast destMast = null; 1398 var point = (PositionablePoint)connection; 1399 1400 if (LayoutEditorTools.isAtWestEndOfAnchor(models, trackSegment, point)) { 1401 destMast = point.getEastBoundSignalMast(); 1402 } else { 1403 destMast = point.getWestBoundSignalMast(); 1404 } 1405 1406 return destMast; 1407 } 1408 1409 1410 /** 1411 * {@inheritDoc} 1412 */ 1413 @Override 1414 public void checkForNonContiguousBlocks( 1415 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 1416 log.debug("Traverser '{}': running checkForNonContiguousBlocks...", getName()); 1417 /* 1418 * For each (non-null) blocks of this track do: 1419 * #1) If it's got an entry in the blockNamesToTrackNameSetMap then 1420 * #2) If this track is already in the TrackNameSet for this block 1421 * then return (done!) 1422 * #3) else add a new set (with this block// track) to 1423 * blockNamesToTrackNameSetMap and check all the connections in this 1424 * block (by calling the 2nd method below) 1425 * <p> 1426 * Basically, we're maintaining contiguous track sets for each block found 1427 * (in blockNamesToTrackNameSetMap) 1428 */ 1429 1430 // We're using a map here because it is convient to 1431 // use it to pair up blocks and connections 1432 Map<LayoutTrack, String> blocksAndTracksMap = new HashMap<>(); 1433 for (int k = 0; k < getNumberSlots(); k++) { 1434 TrackSegment ts = isSlotDisabled(k) ? null : getSlotConnectOrdered(k); 1435 if (ts != null) { 1436 log.debug(" - Found connection from slot {} to track '{}' in block '{}'", k, ts.getName(), ts.getBlockName()); 1437 String blockName = ts.getBlockName(); 1438 blocksAndTracksMap.put(ts, blockName); 1439 } 1440 } 1441 1442 List<Set<String>> TrackNameSets; 1443 Set<String> TrackNameSet; 1444 for (Map.Entry<LayoutTrack, String> entry : blocksAndTracksMap.entrySet()) { 1445 LayoutTrack theConnect = entry.getKey(); 1446 String theBlockName = entry.getValue(); 1447 log.debug(" Processing connection to block '{}'", theBlockName); 1448 1449 TrackNameSet = null; // assume not found (pessimist!) 1450 TrackNameSets = blockNamesToTrackNameSetsMap.get(theBlockName); 1451 if (TrackNameSets != null) { // (#1) 1452 for (Set<String> checkTrackNameSet : TrackNameSets) { 1453 if (checkTrackNameSet.add(getName())) { 1454 log.debug("* Add track '{}' to trackNameSet for block '{}'", getName(), theBlockName); 1455 log.debug(" Added traverser '{}' to existing track set for block '{}'", getName(), theBlockName); 1456 } 1457 if (checkTrackNameSet.contains(getName())) { // (#2) 1458 TrackNameSet = checkTrackNameSet; 1459 break; 1460 } 1461 } 1462 } else { // (#3) 1463 log.debug(" Creating NEW track set for block '{}'", theBlockName); 1464 TrackNameSets = new ArrayList<>(); 1465 blockNamesToTrackNameSetsMap.put(theBlockName, TrackNameSets); 1466 } 1467 if (TrackNameSet == null) { 1468 TrackNameSet = new LinkedHashSet<>(); 1469 TrackNameSets.add(TrackNameSet); 1470 } 1471 if (TrackNameSet.add(getName())) { 1472 log.debug(" Added traverser '{}' to new track set for block '{}'", getName(), theBlockName); 1473 } 1474 log.warn(" Flooding from connection '{}'...", theConnect.getName()); 1475 theConnect.collectContiguousTracksNamesInBlockNamed(theBlockName, TrackNameSet); 1476 } 1477 } 1478 1479 /** 1480 * {@inheritDoc} 1481 */ 1482 @Override 1483 public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName, 1484 @Nonnull Set<String> TrackNameSet) { 1485 if (!TrackNameSet.contains(getName())) { 1486 log.debug("Traverser '{}': running collectContiguousTracksNamesInBlockNamed for block '{}'", getName(), blockName); 1487 // for all the slots with matching blocks in this turnout 1488 // #1) if its track segment's block is in this block 1489 // #2) add traverser to TrackNameSet (if not already there) 1490 // #3) if the track segment isn't in the TrackNameSet 1491 // #4) flood it 1492 for (int k = 0; k < getNumberSlots(); k++) { 1493 if (isSlotDisabled(k)) { 1494 continue; 1495 } 1496 TrackSegment ts = getSlotConnectOrdered(k); 1497 if (ts != null) { 1498 String blk = ts.getBlockName(); 1499 if ((!blk.isEmpty()) && (blk.equals(blockName))) { // (#1) 1500 // if we are added to the TrackNameSet 1501 if (TrackNameSet.add(getName())) { 1502 log.debug(" Added traverser '{}' to track set for block '{}'", getName(), blockName); 1503 } 1504 // it's time to play... flood your neighbours! 1505 ts.collectContiguousTracksNamesInBlockNamed(blockName, 1506 TrackNameSet); // (#4) 1507 } 1508 } 1509 } 1510 } 1511 } 1512 1513 /** 1514 * {@inheritDoc} 1515 */ 1516 @Override 1517 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 1518 // traversers don't have blocks... 1519 // nothing to see here, move along... 1520 } 1521 1522 /** 1523 * {@inheritDoc} 1524 */ 1525 @Override 1526 public boolean canRemove() { 1527 return true; 1528 } 1529 1530 /** 1531 * {@inheritDoc} 1532 */ 1533 @Override 1534 public String getTypeName() { 1535 return Bundle.getMessage("TypeName_Traverser"); 1536 } 1537 1538 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTraverser.class); 1539 1540}