001package jmri.jmrit.display.layoutEditor; 002 003import java.text.MessageFormat; 004import java.util.*; 005import java.util.Map.Entry; 006 007import javax.annotation.CheckForNull; 008import javax.annotation.Nonnull; 009import javax.swing.JComboBox; 010 011import jmri.*; 012 013/** 014 * A LayoutSlip is a crossing of two straight tracks designed in such a way as 015 * to allow trains to change from one straight track to the other, as well as 016 * going straight across. 017 * <p> 018 * A LayoutSlip has four connection points, designated A, B, C, and D. A train 019 * may proceed between A and D, A and C, B and D and in the case of 020 * double-slips, B and C. 021 * <br> 022 * <pre> 023 * \\ // 024 * A==-==D 025 * \\ // 026 * X 027 * // \\ 028 * B==-==C 029 * // \\ 030 * </pre> 031 * <br> 032 * For drawing purposes, each LayoutSlip carries a center point and 033 * displacements for A and B. The displacements for C = - the displacement for 034 * A, and the displacement for D = - the displacement for B. The center point 035 * and these displacements may be adjusted by the user when in edit mode. 036 * <p> 037 * When LayoutSlips are first created, there are no connections. Block 038 * information and connections are added when available. 039 * <p> 040 * SignalHead names are saved here to keep track of where signals are. 041 * LayoutSlip only serves as a storage place for SignalHead names. The names are 042 * placed here by Set Signals at Level Crossing in Tools menu. 043 * 044 * @author Dave Duchamp Copyright (c) 2004-2007 045 * @author George Warner Copyright (c) 2017-2019 046 */ 047abstract public class LayoutSlip extends LayoutTurnout { 048 049 /** 050 * Constructor method. 051 * 052 * @param id slip ID. 053 * @param models the layout editor. 054 * @param type slip type, SINGLE_SLIP or DOUBLE_SLIP. 055 */ 056 public LayoutSlip(String id, 057 LayoutEditor models, TurnoutType type) { 058 super(id, models, type); 059 060 turnoutStates.put(STATE_AC, new TurnoutState(Turnout.CLOSED, Turnout.CLOSED)); 061 turnoutStates.put(STATE_AD, new TurnoutState(Turnout.CLOSED, Turnout.THROWN)); 062 turnoutStates.put(STATE_BD, new TurnoutState(Turnout.THROWN, Turnout.THROWN)); 063 if (type == TurnoutType.SINGLE_SLIP) { 064 turnoutStates.remove(STATE_BC); 065 } else if (type == TurnoutType.DOUBLE_SLIP) { 066 turnoutStates.put(STATE_BC, new TurnoutState(Turnout.THROWN, Turnout.CLOSED)); 067 } else { 068 log.error("{}.setSlipType({}); invalid slip type", getName(), type); // I18IN 069 } 070 } 071 072 public int currentState = UNKNOWN; 073 074 private String turnoutBName = ""; 075 private NamedBeanHandle<Turnout> namedTurnoutB = null; 076 077 private java.beans.PropertyChangeListener mTurnoutListener = null; 078 079 // this should only be used for debugging... 080 @Override 081 public String toString() { 082 return String.format("LayoutSlip %s (%s)", getId(), getSlipStateString(getSlipState())); 083 } 084 085 public TurnoutType getSlipType() { 086 return type; 087 } 088 089 public int getSlipState() { 090 return currentState; 091 } 092 093 public String getTurnoutBName() { 094 if (namedTurnoutB != null) { 095 return namedTurnoutB.getName(); 096 } 097 return turnoutBName; 098 } 099 100 public Turnout getTurnoutB() { 101 Turnout result = null; 102 if (namedTurnoutB == null) { 103 if (!turnoutBName.isEmpty()) { 104 setTurnoutB(turnoutBName); 105 } 106 } 107 if (namedTurnoutB != null) { 108 result = namedTurnoutB.getBean(); 109 } 110 return result; 111 } 112 113 public void setTurnoutB(@CheckForNull String tName) { 114 boolean reactivate = false; 115 if (namedTurnoutB != null) { 116 deactivateTurnout(); 117 reactivate = (namedTurnout != null); 118 } 119 turnoutBName = tName; 120 Turnout turnout = null; 121 if (turnoutBName != null && !turnoutBName.isEmpty()) { 122 turnout = InstanceManager.turnoutManagerInstance().getTurnout(turnoutBName); 123 } 124 if (turnout != null) { 125 namedTurnoutB = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutBName, turnout); 126 activateTurnout(); 127 } else { 128 turnoutBName = ""; 129 namedTurnoutB = null; 130 } 131 if (reactivate) { 132 // this has to be called even on a delete in order 133 // to re-activate namedTurnout (A) (if necessary) 134 activateTurnout(); 135 } 136 } 137 138 /** 139 * {@inheritDoc} 140 */ 141 @Override 142 public LayoutTrack getConnection(HitPointType connectionType) { 143 switch (connectionType) { 144 case SLIP_A: 145 return connectA; 146 case SLIP_B: 147 return connectB; 148 case SLIP_C: 149 return connectC; 150 case SLIP_D: 151 return connectD; 152 default: 153 String errString = MessageFormat.format("{0}.getConnection({1}); Invalid Connection Type", 154 getName(), connectionType); // I18IN 155 log.error("will throw {}", errString); 156 throw new IllegalArgumentException(errString); 157 } 158 } 159 160 /** 161 * {@inheritDoc} 162 */ 163 @Override 164 public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException { 165 if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) { 166 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid type", 167 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // I18IN 168 log.error("will throw {}", errString); 169 throw new jmri.JmriException(errString); 170 } 171 switch (connectionType) { 172 case SLIP_A: 173 connectA = o; 174 break; 175 case SLIP_B: 176 connectB = o; 177 break; 178 case SLIP_C: 179 connectC = o; 180 break; 181 case SLIP_D: 182 connectD = o; 183 break; 184 default: 185 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid Connection Type", 186 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // I18IN 187 log.error("will throw {}", errString); 188 throw new jmri.JmriException(errString); 189 } 190 } 191 192 public String getDisplayName() { 193 String name = "Slip " + getId(); 194 String tnA = getTurnoutName(); 195 String tnB = getTurnoutBName(); 196 if ((tnA != null) && !tnA.isEmpty()) { 197 name += " (" + tnA; 198 } 199 if ((tnB != null) && !tnB.isEmpty()) { 200 if (name.contains(" (")) { 201 name += ", "; 202 } else { 203 name += "("; 204 } 205 name += tnB; 206 } 207 if (name.contains("(")) { 208 name += ")"; 209 } 210 return name; 211 } 212 213 String getSlipStateString(int slipState) { // package access for View forward 214 String result = Bundle.getMessage("BeanStateUnknown"); 215 switch (slipState) { 216 case STATE_AC: { 217 result = "AC"; 218 break; 219 } 220 case STATE_BD: { 221 result = "BD"; 222 break; 223 } 224 case STATE_AD: { 225 result = "AD"; 226 break; 227 } 228 case STATE_BC: { 229 result = "BC"; 230 break; 231 } 232 default: { 233 break; 234 } 235 } 236 return result; 237 } 238 239 /** 240 * Toggle slip states if clicked on, physical turnout exists, and not 241 * disabled. 242 * @param selectedPointType the selected hit point type. 243 */ 244 public void toggleState(HitPointType selectedPointType) { 245 if (!disabled && !(disableWhenOccupied && isOccupied())) { 246 int newSlipState = getSlipState(); 247 switch (selectedPointType) { 248 case SLIP_LEFT: { 249 switch (newSlipState) { 250 case STATE_AC: { 251 if (type == TurnoutType.SINGLE_SLIP) { 252 newSlipState = STATE_BD; 253 } else { 254 newSlipState = STATE_BC; 255 } 256 break; 257 } 258 case STATE_AD: { 259 newSlipState = STATE_BD; 260 break; 261 } 262 case STATE_BC: 263 default: { 264 newSlipState = STATE_AC; 265 break; 266 } 267 case STATE_BD: { 268 newSlipState = STATE_AD; 269 break; 270 } 271 } 272 break; 273 } 274 case SLIP_RIGHT: { 275 switch (newSlipState) { 276 case STATE_AC: { 277 newSlipState = STATE_AD; 278 break; 279 } 280 case STATE_AD: { 281 newSlipState = STATE_AC; 282 break; 283 } 284 case STATE_BC: 285 default: { 286 newSlipState = STATE_BD; 287 break; 288 } 289 case STATE_BD: { 290 if (type == TurnoutType.SINGLE_SLIP) { 291 newSlipState = STATE_AC; 292 } else { 293 newSlipState = STATE_BC; 294 } 295 break; 296 } 297 } 298 break; 299 } 300 default: 301 jmri.util.LoggingUtil.warnOnce(log, "Unexpected selectedPointType = {}", selectedPointType); 302 break; 303 } // switch 304 setSlipState(newSlipState); 305 } 306 } 307 308 void setSlipState(int newSlipState) { 309 if (disableWhenOccupied && isOccupied()) { 310 log.debug("Turnout not changed as Block is Occupied"); 311 } else if (!disabled) { 312 currentState = newSlipState; 313 TurnoutState ts = turnoutStates.get(newSlipState); 314 if (getTurnout() != null) { 315 getTurnout().setCommandedState(ts.getTurnoutAState()); 316 } 317 if (getTurnoutB() != null) { 318 getTurnoutB().setCommandedState(ts.getTurnoutBState()); 319 } 320 } 321 } 322 323 /** 324 * is this turnout occupied? 325 * 326 * @return true if occupied 327 */ 328 @Override 329 boolean isOccupied() { 330 Boolean result = false; // assume failure (pessimist!) 331 switch (getSlipState()) { 332 case STATE_AC: { 333 result = ((getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED) 334 || (getLayoutBlockC().getOccupancy() == LayoutBlock.OCCUPIED)); 335 break; 336 } 337 case STATE_AD: { 338 result = ((getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED) 339 || (getLayoutBlockD().getOccupancy() == LayoutBlock.OCCUPIED)); 340 break; 341 } 342 case STATE_BC: { 343 result = ((getLayoutBlockB().getOccupancy() == LayoutBlock.OCCUPIED) 344 || (getLayoutBlockC().getOccupancy() == LayoutBlock.OCCUPIED)); 345 break; 346 } 347 case STATE_BD: { 348 result = ((getLayoutBlockB().getOccupancy() == LayoutBlock.OCCUPIED) 349 || (getLayoutBlockD().getOccupancy() == LayoutBlock.OCCUPIED)); 350 break; 351 } 352 case UNKNOWN: { 353 result = ((getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED) 354 || (getLayoutBlockB().getOccupancy() == LayoutBlock.OCCUPIED) 355 || (getLayoutBlockC().getOccupancy() == LayoutBlock.OCCUPIED) 356 || (getLayoutBlockD().getOccupancy() == LayoutBlock.OCCUPIED)); 357 break; 358 } 359 default: { 360 log.error("{}.isOccupied(); invalid slip state: {}", getName(), getSlipState()); 361 break; 362 } 363 } 364 return result; 365 } // isOccupied() 366 367 /** 368 * Activate/Deactivate turnout to redraw when turnout state changes 369 */ 370 void activateTurnout() { 371 if (namedTurnout != null) { 372 namedTurnout.getBean().addPropertyChangeListener(mTurnoutListener 373 = (java.beans.PropertyChangeEvent e) -> { 374 updateState(); 375 }, namedTurnout.getName(), "Layout Editor Slip"); 376 } 377 if (namedTurnoutB != null) { 378 namedTurnoutB.getBean().addPropertyChangeListener(mTurnoutListener 379 = (java.beans.PropertyChangeEvent e) -> { 380 updateState(); 381 }, namedTurnoutB.getName(), "Layout Editor Slip"); 382 } 383 updateState(); 384 } 385 386 void deactivateTurnout() { 387 if (mTurnoutListener != null) { 388 if (namedTurnout != null) { 389 namedTurnout.getBean().removePropertyChangeListener(mTurnoutListener); 390 } 391 if (namedTurnoutB != null) { 392 namedTurnoutB.getBean().removePropertyChangeListener(mTurnoutListener); 393 } 394 mTurnoutListener = null; 395 } 396 } 397 398 399 @Override 400 public void updateBlockInfo() { 401 LayoutBlock b1 = null; 402 LayoutBlock b2 = null; 403 if (getLayoutBlock() != null) { 404 getLayoutBlock().updatePaths(); 405 } 406 if (connectA != null) { 407 b1 = ((TrackSegment) connectA).getLayoutBlock(); 408 if ((b1 != null) && (b1 != getLayoutBlock())) { 409 b1.updatePaths(); 410 } 411 } 412 if (connectC != null) { 413 b2 = ((TrackSegment) connectC).getLayoutBlock(); 414 if ((b2 != null) && (b2 != getLayoutBlock()) && (b2 != b1)) { 415 b2.updatePaths(); 416 } 417 } 418 419 if (connectB != null) { 420 b1 = ((TrackSegment) connectB).getLayoutBlock(); 421 if ((b1 != null) && (b1 != getLayoutBlock())) { 422 b1.updatePaths(); 423 } 424 } 425 if (connectD != null) { 426 b2 = ((TrackSegment) connectD).getLayoutBlock(); 427 if ((b2 != null) && (b2 != getLayoutBlock()) && (b2 != b1)) { 428 b2.updatePaths(); 429 } 430 } 431 reCheckBlockBoundary(); 432 } 433 434 /** 435 * Methods to test if mainline track or not Returns true if either 436 * connecting track segment is mainline Defaults to not mainline if 437 * connecting track segments are missing 438 */ 439 @Override 440 public boolean isMainline() { 441 if (((connectA != null) && (((TrackSegment) connectA).isMainline())) 442 || ((connectB != null) && (((TrackSegment) connectB).isMainline())) 443 || ((connectC != null) && (((TrackSegment) connectC).isMainline())) 444 || ((connectD != null) && (((TrackSegment) connectD).isMainline()))) { 445 return true; 446 } else { 447 return false; 448 } 449 } 450 451 @Override 452 public String[] getBlockBoundaries() { 453 final String[] boundaryBetween = new String[4]; 454 455 if ((!getBlockName().isEmpty()) && (getLayoutBlock() != null)) { 456 if ((connectA instanceof TrackSegment) && (((TrackSegment) connectA).getLayoutBlock() != getLayoutBlock())) { 457 try { 458 boundaryBetween[0] = (((TrackSegment) connectA).getLayoutBlock().getDisplayName() + " - " + getLayoutBlock().getDisplayName()); 459 } catch (java.lang.NullPointerException e) { 460 // Can be considered normal if tracksegement hasn't yet been allocated a block 461 log.debug("TrackSegement at connection A doesn't contain a layout block"); 462 } 463 } 464 if ((connectC instanceof TrackSegment) && (((TrackSegment) connectC).getLayoutBlock() != getLayoutBlock())) { 465 try { 466 boundaryBetween[2] = (((TrackSegment) connectC).getLayoutBlock().getDisplayName() + " - " + getLayoutBlock().getDisplayName()); 467 } catch (java.lang.NullPointerException e) { 468 // Can be considered normal if tracksegement hasn't yet been allocated a block 469 log.debug("TrackSegement at connection C doesn't contain a layout block"); 470 } 471 } 472 if ((connectB instanceof TrackSegment) && (((TrackSegment) connectB).getLayoutBlock() != getLayoutBlock())) { 473 try { 474 boundaryBetween[1] = (((TrackSegment) connectB).getLayoutBlock().getDisplayName() + " - " + getLayoutBlock().getDisplayName()); 475 } catch (java.lang.NullPointerException e) { 476 // Can be considered normal if tracksegement hasn't yet been allocated a block 477 log.debug("TrackSegement at connection B doesn't contain a layout block"); 478 } 479 } 480 if ((connectD instanceof TrackSegment) && (((TrackSegment) connectD).getLayoutBlock() != getLayoutBlock())) { 481 try { 482 boundaryBetween[3] = (((TrackSegment) connectD).getLayoutBlock().getDisplayName() + " - " + getLayoutBlock().getDisplayName()); 483 } catch (java.lang.NullPointerException e) { 484 // Can be considered normal if tracksegement hasn't yet been allocated a block 485 log.debug("TrackSegement at connection D doesn't contain a layout block"); 486 } 487 } 488 } 489 return boundaryBetween; 490 } 491 492 /** 493 * Removes this object from display and persistance 494 */ 495 @Override 496 public void remove() { 497 disableSML(getSignalAMast()); 498 disableSML(getSignalBMast()); 499 disableSML(getSignalCMast()); 500 disableSML(getSignalDMast()); 501 removeSML(getSignalAMast()); 502 removeSML(getSignalBMast()); 503 removeSML(getSignalCMast()); 504 removeSML(getSignalDMast()); 505 } 506 507 void disableSML(SignalMast signalMast) { 508 if (signalMast == null) { 509 return; 510 } 511 InstanceManager.getDefault(jmri.SignalMastLogicManager.class).disableLayoutEditorUse(signalMast); 512 } 513 514 HashMap<Integer, TurnoutState> turnoutStates = new LinkedHashMap<>(4); 515 516 public HashMap<Integer, TurnoutState> getTurnoutStates() { 517 return turnoutStates; 518 } 519 520 public int getTurnoutState(@Nonnull Turnout turn, int state) { 521 if (turn == getTurnout()) { 522 return getTurnoutState(state); 523 } 524 return getTurnoutBState(state); 525 } 526 527 public int getTurnoutState(int state) { 528 return turnoutStates.get(state).getTurnoutAState(); 529 } 530 531 public int getTurnoutBState(int state) { 532 return turnoutStates.get(state).getTurnoutBState(); 533 } 534 535 public void setTurnoutStates(int state, @Nonnull String turnStateA, @Nonnull String turnStateB) { 536 if (!turnoutStates.containsKey(state)) { 537 log.error("{}.setTurnoutStates({}, {}, {}); invalid state for slip", 538 getName(), state, turnStateA, turnStateB); 539 return; 540 } 541 turnoutStates.get(state).setTurnoutAState(Integer.parseInt(turnStateA)); 542 turnoutStates.get(state).setTurnoutBState(Integer.parseInt(turnStateB)); 543 } 544 545 // Internal call to update the state of the slip depending upon the turnout states. 546 void updateState() { 547 if ((getTurnout() != null) && (getTurnoutB() != null)) { 548 int state_a = getTurnout().getKnownState(); 549 int state_b = getTurnoutB().getKnownState(); 550 for (Entry<Integer, TurnoutState> en : turnoutStates.entrySet()) { 551 if (en.getValue().getTurnoutAState() == state_a) { 552 if (en.getValue().getTurnoutBState() == state_b) { 553 currentState = en.getKey(); 554 models.redrawPanel(); 555 return; 556 } 557 } 558 } 559 } 560 } 561 562 /** 563 * Check if either turnout is inconsistent. This is used to create an 564 * alternate slip image. 565 * 566 * @return true if either turnout is inconsistent. 567 */ 568 boolean isTurnoutInconsistent() { 569 Turnout tA = getTurnout(); 570 if (tA != null && tA.getKnownState() == INCONSISTENT) { 571 return true; 572 } 573 Turnout tB = getTurnoutB(); 574 if (tB != null && tB.getKnownState() == INCONSISTENT) { 575 return true; 576 } 577 return false; 578 } 579 580 public static class TurnoutState { 581 582 private int turnoutAstate = Turnout.CLOSED; 583 private int turnoutBstate = Turnout.CLOSED; 584 private JComboBox<String> turnoutABox; 585 private JComboBox<String> turnoutBBox; 586 587 TurnoutState(int turnoutAstate, int turnoutBstate) { 588 this.turnoutAstate = turnoutAstate; 589 this.turnoutBstate = turnoutBstate; 590 } 591 592 public int getTurnoutAState() { 593 return turnoutAstate; 594 } 595 596 public int getTurnoutBState() { 597 return turnoutBstate; 598 } 599 600 public void setTurnoutAState(int state) { 601 turnoutAstate = state; 602 } 603 604 public void setTurnoutBState(int state) { 605 turnoutBstate = state; 606 } 607 608 public JComboBox<String> getComboA() { 609 if (turnoutABox == null) { 610 String[] state = new String[]{InstanceManager.turnoutManagerInstance().getClosedText(), 611 InstanceManager.turnoutManagerInstance().getThrownText()}; 612 turnoutABox = new JComboBox<>(state); 613 if (turnoutAstate == Turnout.THROWN) { 614 turnoutABox.setSelectedIndex(1); 615 } 616 } 617 return turnoutABox; 618 } 619 620 public JComboBox<String> getComboB() { 621 if (turnoutBBox == null) { 622 String[] state = new String[]{InstanceManager.turnoutManagerInstance().getClosedText(), 623 InstanceManager.turnoutManagerInstance().getThrownText()}; 624 turnoutBBox = new JComboBox<>(state); 625 if (turnoutBstate == Turnout.THROWN) { 626 turnoutBBox.setSelectedIndex(1); 627 } 628 } 629 return turnoutBBox; 630 } 631 632 public int getTestTurnoutAState() { 633 int result = Turnout.THROWN; 634 if (turnoutABox != null) { 635 if (turnoutABox.getSelectedIndex() == 0) { 636 result = Turnout.CLOSED; 637 } 638 } 639 return result; 640 } 641 642 public int getTestTurnoutBState() { 643 int result = Turnout.THROWN; 644 if (turnoutBBox != null) { 645 if (turnoutBBox.getSelectedIndex() == 0) { 646 result = Turnout.CLOSED; 647 } 648 } 649 return result; 650 } 651 652 public void updateStatesFromCombo() { 653 if ((turnoutABox != null) && (turnoutBBox != null)) { 654 turnoutAstate = getTestTurnoutAState(); 655 turnoutBstate = getTestTurnoutBState(); 656 } 657 } 658 659 @Override 660 public boolean equals(Object object) { 661 if (this == object) { 662 return true; 663 } 664 if (object == null) { 665 return false; 666 } 667 if (!(object instanceof TurnoutState)) { 668 return false; 669 } 670 TurnoutState tso = (TurnoutState) object; 671 672 return ((getTurnoutAState() == tso.getTurnoutAState()) 673 && (getTurnoutBState() == tso.getTurnoutBState())); 674 } 675 676 /** 677 * Hash on the header 678 */ 679 @Override 680 public int hashCode() { 681 int result = 7; 682 result = (37 * result) + getTurnoutAState(); 683 result = (37 * result) + getTurnoutBState(); 684 685 return result; 686 } 687 688 } // class TurnoutState 689 690 /* 691 this is used by ConnectivityUtil to determine the turnout state necessary to get from prevLayoutBlock ==> currLayoutBlock ==> nextLayoutBlock 692 */ 693 @Override 694 protected int getConnectivityStateForLayoutBlocks( 695 @CheckForNull LayoutBlock thisLayoutBlock, 696 @CheckForNull LayoutBlock prevLayoutBlock, 697 @CheckForNull LayoutBlock nextLayoutBlock, 698 boolean suppress) { 699 int result = Turnout.UNKNOWN; 700 LayoutBlock layoutBlockA = ((TrackSegment) getConnectA()).getLayoutBlock(); 701 LayoutBlock layoutBlockB = ((TrackSegment) getConnectB()).getLayoutBlock(); 702 LayoutBlock layoutBlockC = ((TrackSegment) getConnectC()).getLayoutBlock(); 703 LayoutBlock layoutBlockD = ((TrackSegment) getConnectD()).getLayoutBlock(); 704 705 if (layoutBlockA == thisLayoutBlock) { 706 if (layoutBlockC == nextLayoutBlock || layoutBlockC == prevLayoutBlock) { 707 result = LayoutSlip.STATE_AC; 708 } else if (layoutBlockD == nextLayoutBlock || layoutBlockD == prevLayoutBlock) { 709 result = LayoutSlip.STATE_AD; 710 } else if (layoutBlockC == thisLayoutBlock) { 711 result = LayoutSlip.STATE_AC; 712 } else if (layoutBlockD == thisLayoutBlock) { 713 result = LayoutSlip.STATE_AD; 714 } 715 } else if (layoutBlockB == thisLayoutBlock) { 716 if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) { 717 if (layoutBlockD == nextLayoutBlock || layoutBlockD == prevLayoutBlock) { 718 result = LayoutSlip.STATE_BD; 719 } else if (layoutBlockC == nextLayoutBlock || layoutBlockC == prevLayoutBlock) { 720 result = LayoutSlip.STATE_BC; 721 } else if (layoutBlockD == thisLayoutBlock) { 722 result = LayoutSlip.STATE_BD; 723 } else if (layoutBlockC == thisLayoutBlock) { 724 result = LayoutSlip.STATE_BC; 725 } 726 } else { 727 if (layoutBlockD == nextLayoutBlock || layoutBlockD == prevLayoutBlock) { 728 result = LayoutSlip.STATE_BD; 729 } else if (layoutBlockD == thisLayoutBlock) { 730 result = LayoutSlip.STATE_BD; 731 } 732 } 733 } else if (layoutBlockC == thisLayoutBlock) { 734 if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) { 735 if (layoutBlockA == nextLayoutBlock || layoutBlockA == prevLayoutBlock) { 736 result = LayoutSlip.STATE_AC; 737 } else if (layoutBlockB == nextLayoutBlock || layoutBlockB == prevLayoutBlock) { 738 result = LayoutSlip.STATE_BC; 739 } else if (layoutBlockA == thisLayoutBlock) { 740 result = LayoutSlip.STATE_AC; 741 } else if (layoutBlockB == thisLayoutBlock) { 742 result = LayoutSlip.STATE_BC; 743 } 744 } else { 745 if (layoutBlockA == nextLayoutBlock || layoutBlockA == prevLayoutBlock) { 746 result = LayoutSlip.STATE_AC; 747 } else if (layoutBlockA == thisLayoutBlock) { 748 result = LayoutSlip.STATE_AC; 749 } 750 } 751 } else if (layoutBlockD == thisLayoutBlock) { 752 if (layoutBlockA == nextLayoutBlock || layoutBlockA == prevLayoutBlock) { 753 result = LayoutSlip.STATE_AD; 754 } else if (layoutBlockB == nextLayoutBlock || layoutBlockB == prevLayoutBlock) { 755 result = LayoutSlip.STATE_BD; 756 } else if (layoutBlockA == thisLayoutBlock) { 757 result = LayoutSlip.STATE_AD; 758 } else if (layoutBlockB == thisLayoutBlock) { 759 result = LayoutSlip.STATE_AD; 760 } 761 } else { 762 result = LayoutSlip.UNKNOWN; 763 } 764 if (!suppress && (result == LayoutSlip.UNKNOWN)) { 765 log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine slip setting", getName()); 766 } 767 return result; 768 } // getConnectivityStateForLayoutBlocks 769 770 /* 771 * {@inheritDoc} 772 */ 773 @Override 774 public void reCheckBlockBoundary() { 775 if (connectA == null && connectB == null && connectC == null && connectD == null) { 776 // This is no longer a block boundary, therefore will remove signal masts and sensors if present 777 if (signalAMastNamed != null) { 778 removeSML(getSignalAMast()); 779 } 780 if (signalBMastNamed != null) { 781 removeSML(getSignalBMast()); 782 } 783 if (signalCMastNamed != null) { 784 removeSML(getSignalCMast()); 785 } 786 if (signalDMastNamed != null) { 787 removeSML(getSignalDMast()); 788 } 789 signalAMastNamed = null; 790 signalBMastNamed = null; 791 signalCMastNamed = null; 792 signalDMastNamed = null; 793 sensorANamed = null; 794 sensorBNamed = null; 795 sensorCNamed = null; 796 sensorDNamed = null; 797 return; 798 // May want to look at a method to remove the assigned mast from the panel and potentially any logics generated 799 } else if (connectA == null || connectB == null || connectC == null || connectD == null) { 800 // could still be in the process of rebuilding the point details 801 return; 802 } 803 804 TrackSegment trkA; 805 TrackSegment trkB; 806 TrackSegment trkC; 807 TrackSegment trkD; 808 809 if (connectA instanceof TrackSegment) { 810 trkA = (TrackSegment) connectA; 811 if (trkA.getLayoutBlock() == getLayoutBlock()) { 812 if (signalAMastNamed != null) { 813 removeSML(getSignalAMast()); 814 } 815 signalAMastNamed = null; 816 sensorANamed = null; 817 } 818 } 819 if (connectC instanceof TrackSegment) { 820 trkC = (TrackSegment) connectC; 821 if (trkC.getLayoutBlock() == getLayoutBlock()) { 822 if (signalCMastNamed != null) { 823 removeSML(getSignalCMast()); 824 } 825 signalCMastNamed = null; 826 sensorCNamed = null; 827 } 828 } 829 if (connectB instanceof TrackSegment) { 830 trkB = (TrackSegment) connectB; 831 if (trkB.getLayoutBlock() == getLayoutBlock()) { 832 if (signalBMastNamed != null) { 833 removeSML(getSignalBMast()); 834 } 835 signalBMastNamed = null; 836 sensorBNamed = null; 837 } 838 } 839 840 if (connectD instanceof TrackSegment) { 841 trkD = (TrackSegment) connectC; 842 if (trkD.getLayoutBlock() == getLayoutBlock()) { 843 if (signalDMastNamed != null) { 844 removeSML(getSignalDMast()); 845 } 846 signalDMastNamed = null; 847 sensorDNamed = null; 848 } 849 } 850 } // reCheckBlockBoundary() 851 852 /* 853 * {@inheritDoc} 854 */ 855 @Override 856 @Nonnull 857 protected List<LayoutConnectivity> getLayoutConnectivity() { 858 List<LayoutConnectivity> results = new ArrayList<>(); 859 860 log.trace("Start in LayoutSlip.getLayoutConnectivity for {}", getName()); 861 862 LayoutConnectivity lc = null; 863 LayoutBlock lbA = getLayoutBlock(), lbB = getLayoutBlockB(), lbC = getLayoutBlockC(), lbD = getLayoutBlockD(); 864 865 log.trace(" type: {}", type); 866 log.trace(" lbA: {}", lbA); 867 log.trace(" lbB: {}", lbB); 868 log.trace(" lbC: {}", lbC); 869 log.trace(" lbD: {}", lbD); 870 871 if (lbA != null) { 872 if (lbA != lbC) { 873 // have a AC block boundary, create a LayoutConnectivity 874 log.debug("Block boundary ('{}'<->'{}') found at {}", lbA, lbC, this); 875 lc = new LayoutConnectivity(lbA, lbC); 876 lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_AC); 877 878 // The following line needs to change, because it uses location of 879 // the points on the SlipView itself. Switch to 880 // direction from connections 881 //lc.setDirection(Path.computeDirection(getCoordsA(), getCoordsC())); 882 lc.setDirection( models.computeDirectionAC(this) ); 883 884 log.trace("getLayoutConnectivity lbA != lbC"); 885 log.trace(" Block boundary ('{}'<->'{}') found at {}", lbA, lbC, this); 886 887 results.add(lc); 888 } 889 if (lbB != lbD) { 890 // have a BD block boundary, create a LayoutConnectivity 891 log.debug("Block boundary ('{}'<->'{}') found at {}", lbB, lbD, this); 892 lc = new LayoutConnectivity(lbB, lbD); 893 lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_BD); 894 895 // The following line needs to change, because it uses location of 896 // the points on the SlipView itself. Switch to 897 // direction from connections 898 //lc.setDirection(Path.computeDirection(getCoordsB(), getCoordsD())); 899 lc.setDirection( models.computeDirectionBD(this) ); 900 901 log.trace("getLayoutConnectivity lbA != lbC"); 902 log.trace(" Block boundary ('{}'<->'{}') found at {}", lbB, lbD, this); 903 904 results.add(lc); 905 } 906 if (lbA != lbD) { 907 // have a AD block boundary, create a LayoutConnectivity 908 log.debug("Block boundary ('{}'<->'{}') found at {}", lbA, lbD, this); 909 lc = new LayoutConnectivity(lbA, lbD); 910 lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_AD); 911 912 // The following line needs to change, because it uses location of 913 // the points on the SlipView itself. Switch to 914 // direction from connections 915 //lc.setDirection(Path.computeDirection(getCoordsA(), getCoordsD())); 916 lc.setDirection( models.computeDirectionAD(this) ); 917 918 log.trace("getLayoutConnectivity lbA != lbC"); 919 log.trace(" Block boundary ('{}'<->'{}') found at {}", lbA, lbD, this); 920 921 results.add(lc); 922 } 923 if ((type == TurnoutType.DOUBLE_SLIP) && (lbB != lbC)) { 924 // have a BC block boundary, create a LayoutConnectivity 925 log.debug("Block boundary ('{}'<->'{}') found at {}", lbB, lbC, this); 926 lc = new LayoutConnectivity(lbB, lbC); 927 lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_BC); 928 929 // The following line needs to change, because it uses location of 930 // the points on the SlipView itself. Switch to 931 // direction from connections 932 //lc.setDirection(Path.computeDirection(getCoordsB(), getCoordsC())); 933 lc.setDirection( models.computeDirectionBC(this) ); 934 935 log.trace("getLayoutConnectivity lbA != lbC"); 936 log.trace(" Block boundary ('{}'<->'{}') found at {}", lbB, lbC, this); 937 938 results.add(lc); 939 } 940 } 941 return results; 942 } 943 944 /** 945 * {@inheritDoc} 946 */ 947 @Override 948 public List<HitPointType> checkForFreeConnections() { 949 List<HitPointType> result = new ArrayList<>(); 950 951 // check the A connection point 952 if (getConnectA() == null) { 953 result.add(HitPointType.SLIP_A); 954 } 955 956 // check the B connection point 957 if (getConnectB() == null) { 958 result.add(HitPointType.SLIP_B); 959 } 960 961 // check the C connection point 962 if (getConnectC() == null) { 963 result.add(HitPointType.SLIP_C); 964 } 965 966 // check the D connection point 967 if (getConnectD() == null) { 968 result.add(HitPointType.SLIP_D); 969 } 970 return result; 971 } 972 973 // NOTE: LayoutSlip uses the checkForNonContiguousBlocks 974 // and collectContiguousTracksNamesInBlockNamed methods 975 // inherited from LayoutTurnout 976 // 977 978 /** 979 * Create the tooltip name string for a slip. 980 * @return the turnout display names or the Id. 981 */ 982 @Nonnull 983 @Override 984 public String getNameString() { 985 var turnout = getTurnout(); 986 var turnoutB = getTurnoutB(); 987 if (turnout != null && turnoutB != null) { 988 return turnout.getDisplayName(jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME) + " : " + 989 turnoutB.getDisplayName(jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME); 990 } 991 return getId(); 992 } 993 994 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutSlip.class); 995}