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}