001package jmri.jmrit.display.layoutEditor;
002
003import java.awt.Color;
004import java.awt.Graphics2D;
005import java.awt.event.ActionEvent;
006import java.awt.event.MouseEvent;
007import java.awt.geom.*;
008import java.util.List;
009
010import javax.annotation.CheckForNull;
011import javax.annotation.Nonnull;
012import javax.swing.*;
013
014import jmri.InstanceManager;
015import jmri.Turnout;
016import jmri.jmrit.display.layoutEditor.LayoutTurnout.TurnoutType;
017import jmri.jmrit.display.layoutEditor.blockRoutingTable.LayoutBlockRouteTableAction;
018import jmri.util.MathUtil;
019
020/**
021 * MVC View component for the LayoutSlip class.
022 *
023 * @author Bob Jacobsen  Copyright (c) 2020
024 *
025 */
026public class LayoutSlipView extends LayoutTurnoutView {
027
028    /**
029     * Constructor method.
030     * @param slip the layout sip to create view for.
031     * @param c 2D point.
032     * @param rot rotation.
033     * @param layoutEditor the layout editor.
034     */
035    public LayoutSlipView(@Nonnull LayoutSlip slip,
036            Point2D c, double rot,
037            @Nonnull LayoutEditor layoutEditor) {
038        super(slip, c, rot, layoutEditor);
039        this.slip = slip;
040
041        dispA = new Point2D.Double(-20.0, 0.0);
042        pointA = MathUtil.add(getCoordsCenter(), dispA);
043        pointC = MathUtil.subtract(getCoordsCenter(), dispA);
044        dispB = new Point2D.Double(-14.0, 14.0);
045        pointB = MathUtil.add(getCoordsCenter(), dispB);
046        pointD = MathUtil.subtract(getCoordsCenter(), dispB);
047
048        rotateCoords(rot);
049
050        editor = new jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutSlipEditor(layoutEditor);
051    }
052
053    final private LayoutSlip slip;
054
055    public int currentState = UNKNOWN;
056
057    public LayoutSlip getSlip() {return slip; }
058    // this should only be used for debugging...
059    @Override
060    public String toString() {
061        return String.format("LayoutSlip %s (%s)", getId(), getSlipStateString(getSlipState()));
062    }
063
064    public TurnoutType getSlipType() {
065        return slip.getSlipType();
066    }
067
068    public int getSlipState() {
069        return slip.getSlipState();
070    }
071
072    public String getTurnoutBName() {
073       return slip.getTurnoutBName();
074    }
075
076    public Turnout getTurnoutB() {
077       return slip.getTurnoutB();
078    }
079
080    public void setTurnoutB(@CheckForNull String tName) {
081        slip.setTurnoutB(tName);
082    }
083
084    /**
085     * {@inheritDoc}
086     */
087    @Override
088    public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
089        return slip.getConnection(connectionType);
090    }
091
092    /**
093     * {@inheritDoc}
094     */
095    @Override
096    public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException {
097        slip.setConnection(connectionType, o, type);
098    }
099
100    public String getDisplayName() {
101        String name = "Slip " + getId();
102        String tnA = getTurnoutName();
103        String tnB = getTurnoutBName();
104        if ((tnA != null) && !tnA.isEmpty()) {
105            name += " (" + tnA;
106        }
107        if ((tnB != null) && !tnB.isEmpty()) {
108            if (name.contains(" (")) {
109                name += ", ";
110            } else {
111                name += "(";
112            }
113            name += tnB;
114        }
115        if (name.contains("(")) {
116            name += ")";
117        }
118        return name;
119    }
120
121    private String getSlipStateString(int slipState) {
122       return slip.getSlipStateString(slipState);
123    }
124
125    /**
126     * Toggle slip states if clicked on, physical turnout exists, and not
127     * disabled
128     * @param selectedPointType See {@link LayoutSlip#toggleState} for definition
129     */
130    public void toggleState(HitPointType selectedPointType) {
131       slip.toggleState(selectedPointType);
132    }
133
134    /**
135     * is this turnout occupied?
136     *
137     * @return true if occupied
138     */
139    private boolean isOccupied() {
140       return slip.isOccupied();
141    }
142
143    @Override
144    public Point2D getCoordsA() {
145        return pointA;
146    }
147
148    @Override
149    public Point2D getCoordsB() {
150        return pointB;
151    }
152
153    @Override
154    public Point2D getCoordsC() {
155        return pointC;
156    }
157
158    @Override
159    public Point2D getCoordsD() {
160        return pointD;
161    }
162
163    Point2D getCoordsLeft() {
164        Point2D leftCenter = MathUtil.midPoint(getCoordsA(), getCoordsB());
165        double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
166        double leftFract = circleRadius / getCoordsCenter().distance(leftCenter);
167        return MathUtil.lerp(getCoordsCenter(), leftCenter, leftFract);
168    }
169
170    Point2D getCoordsRight() {
171        Point2D rightCenter = MathUtil.midPoint(getCoordsC(), getCoordsD());
172        double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
173        double rightFract = circleRadius / getCoordsCenter().distance(rightCenter);
174        return MathUtil.lerp(getCoordsCenter(), rightCenter, rightFract);
175    }
176
177    /**
178     * return the coordinates for the specified connection type
179     *
180     * @param connectionType the connection type
181     * @return the Point2D coordinates
182     */
183    @Override
184    public Point2D getCoordsForConnectionType(HitPointType connectionType) {
185        Point2D result = getCoordsCenter();
186        switch (connectionType) {
187            case SLIP_A:
188                result = getCoordsA();
189                break;
190            case SLIP_B:
191                result = getCoordsB();
192                break;
193            case SLIP_C:
194                result = getCoordsC();
195                break;
196            case SLIP_D:
197                result = getCoordsD();
198                break;
199            case SLIP_LEFT:
200                result = getCoordsLeft();
201                break;
202            case SLIP_RIGHT:
203                result = getCoordsRight();
204                break;
205            default:
206                log.error("{}.getCoordsForConnectionType({}); Invalid Connection Type", getName(), connectionType); // I18IN
207        }
208        return result;
209    }
210
211    /**
212     * {@inheritDoc}
213     */
214    // just here for testing; should be removed when I'm done...
215    @Override
216    public Rectangle2D getBounds() {
217        return super.getBounds();
218    }
219
220    @Override
221    public void updateBlockInfo() {
222        slip.updateBlockInfo();
223    }
224
225    /**
226     * {@inheritDoc}
227     */
228    @Override
229    protected HitPointType findHitPointType(@Nonnull Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) {
230        HitPointType result = HitPointType.NONE;  // assume point not on connection
231
232        if (!requireUnconnected) {
233            // calculate radius of turnout control circle
234            double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
235
236            // get left and right centers
237            Point2D leftCenter = getCoordsLeft();
238            Point2D rightCenter = getCoordsRight();
239
240            if (useRectangles) {
241                // calculate turnout's left control rectangle
242                Rectangle2D leftRectangle = layoutEditor.layoutEditorControlCircleRectAt(leftCenter);
243                if (leftRectangle.contains(hitPoint)) {
244                    // point is in this turnout's left control rectangle
245                    result = HitPointType.SLIP_LEFT;
246                }
247                Rectangle2D rightRectangle = layoutEditor.layoutEditorControlCircleRectAt(rightCenter);
248                if (rightRectangle.contains(hitPoint)) {
249                    // point is in this turnout's right control rectangle
250                    result = HitPointType.SLIP_RIGHT;
251                }
252            } else {
253                // check east/west turnout control circles
254                double leftDistance = hitPoint.distance(leftCenter);
255                double rightDistance = hitPoint.distance(rightCenter);
256
257                if ((leftDistance <= circleRadius) || (rightDistance <= circleRadius)) {
258                    // mouse was pressed on this slip
259                    result = (leftDistance < rightDistance) ? HitPointType.SLIP_LEFT : HitPointType.SLIP_RIGHT;
260                }
261            }
262        }
263
264        // have we found anything yet?
265        if (result == HitPointType.NONE) {
266            // rather than create rectangles for all the points below and
267            // see if the passed in point is in one of those rectangles
268            // we can create a rectangle for the passed in point and then
269            // test if any of the points below are in that rectangle instead.
270            Rectangle2D r = layoutEditor.layoutEditorControlRectAt(hitPoint);
271
272            if (!requireUnconnected || (getConnectA() == null)) {
273                // check the A connection point
274                if (r.contains(getCoordsA())) {
275                    result = HitPointType.SLIP_A;
276                }
277            }
278
279            if (!requireUnconnected || (getConnectB() == null)) {
280                // check the B connection point
281                if (r.contains(getCoordsB())) {
282                    result = HitPointType.SLIP_B;
283                }
284            }
285
286            if (!requireUnconnected || (getConnectC() == null)) {
287                // check the C connection point
288                if (r.contains(getCoordsC())) {
289                    result = HitPointType.SLIP_C;
290                }
291            }
292
293            if (!requireUnconnected || (getConnectD() == null)) {
294                // check the D connection point
295                if (r.contains(getCoordsD())) {
296                    result = HitPointType.SLIP_D;
297                }
298            }
299        }
300        return result;
301    }   // findHitPointType
302
303    /*
304    * Modify coordinates methods
305     */
306    /**
307     * set center coordinates
308     *
309     * @param p the coordinates to set
310     */
311    @Override
312    public void setCoordsCenter(@Nonnull Point2D p) {
313        super.setCoordsCenter(p);
314        pointA = MathUtil.add(getCoordsCenter(), dispA);
315        pointB = MathUtil.add(getCoordsCenter(), dispB);
316        pointC = MathUtil.subtract(getCoordsCenter(), dispA);
317        pointD = MathUtil.subtract(getCoordsCenter(), dispB);
318    }
319
320    @Override
321    public void setCoordsA(@Nonnull Point2D p) {
322        pointA = p;
323        dispA = MathUtil.subtract(pointA, getCoordsCenter());
324        pointC = MathUtil.subtract(getCoordsCenter(), dispA);
325    }
326
327    @Override
328    public void setCoordsB(@Nonnull Point2D p) {
329        pointB = p;
330        dispB = MathUtil.subtract(pointB, getCoordsCenter());
331        pointD = MathUtil.subtract(getCoordsCenter(), dispB);
332    }
333
334    @Override
335    public void setCoordsC(@Nonnull Point2D p) {
336        pointC = p;
337        dispA = MathUtil.subtract(getCoordsCenter(), pointC);
338        pointA = MathUtil.add(getCoordsCenter(), dispA);
339    }
340
341    @Override
342    public void setCoordsD(@Nonnull Point2D p) {
343        pointD = p;
344        dispB = MathUtil.subtract(getCoordsCenter(), pointD);
345        pointB = MathUtil.add(getCoordsCenter(), dispB);
346    }
347
348    JPopupMenu popup = null;
349
350    /**
351     * {@inheritDoc}
352     */
353    @Override
354    @Nonnull
355    protected JPopupMenu showPopup(@CheckForNull MouseEvent mouseEvent) {
356        if (popup != null) {
357            popup.removeAll();
358        } else {
359            popup = new JPopupMenu();
360        }
361        if (layoutEditor.isEditable()) {
362            String slipStateString = getSlipStateString(getSlipState());
363            slipStateString = String.format(" (%s)", slipStateString);
364
365            JMenuItem jmi = null;
366            switch (getSlipType()) {
367                case SINGLE_SLIP: {
368                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("LayoutSingleSlip")) + getId() + slipStateString);
369                    break;
370                }
371                case DOUBLE_SLIP: {
372                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("LayoutDoubleSlip")) + getId() + slipStateString);
373                    break;
374                }
375                default: {
376                    log.error("{}.showPopup(<mouseEvent>); Invalid slip type: {}", getName(), getSlipType()); // I18IN
377                }
378            }
379            if (jmi != null) {
380                jmi.setEnabled(false);
381            }
382
383            if (getTurnout() == null) {
384                jmi = popup.add(Bundle.getMessage("NoTurnout"));
385            } else {
386                String stateString = getTurnoutStateString(getTurnout().getKnownState());
387                stateString = String.format(" (%s)", stateString);
388                jmi = popup.add(Bundle.getMessage("BeanNameTurnout") + ": " + getTurnoutName() + stateString);
389            }
390            jmi.setEnabled(false);
391
392            if (getTurnoutB() == null) {
393                jmi = popup.add(Bundle.getMessage("NoTurnout"));
394            } else {
395                String stateString = getTurnoutStateString(getTurnoutB().getKnownState());
396                stateString = String.format(" (%s)", stateString);
397                jmi = popup.add(Bundle.getMessage("BeanNameTurnout") + ": " + getTurnoutBName() + stateString);
398            }
399            jmi.setEnabled(false);
400
401            boolean blockAssigned = false;
402            if (getBlockName().isEmpty()) {
403                jmi = popup.add(Bundle.getMessage("NoBlock"));
404                jmi.setEnabled(false);
405            } else {
406                blockAssigned = true;
407
408                jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "A")) + getLayoutBlock().getDisplayName());
409                jmi.setEnabled(false);
410
411                // check if extra blocks have been entered
412                if ((getLayoutBlockB() != null) && (getLayoutBlockB() != getLayoutBlock())) {
413                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "B")) + getLayoutBlockB().getDisplayName());
414                    jmi.setEnabled(false);
415                }
416                if ((getLayoutBlockC() != null) && (getLayoutBlockC() != getLayoutBlock())) {
417                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "C")) + getLayoutBlockC().getDisplayName());
418                    jmi.setEnabled(false);
419                }
420                if ((getLayoutBlockD() != null) && (getLayoutBlockD() != getLayoutBlock())) {
421                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "D")) + getLayoutBlockD().getDisplayName());
422                    jmi.setEnabled(false);
423                }
424            }
425
426            // if there are any track connections
427            if ((getConnectA() != null) || (getConnectB() != null)
428                    || (getConnectC() != null) || (getConnectD() != null)) {
429                JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections")); // there is no pane opening (which is what ... implies)
430                if (getConnectA() != null) {
431                    connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "A") + getConnectA().getName()) {
432                        @Override
433                        public void actionPerformed(ActionEvent e) {
434                            LayoutEditorFindItems lf = layoutEditor.getFinder();
435                            LayoutTrack lt = lf.findObjectByName(getConnectA().getName());
436                            // this shouldn't ever be null... however...
437                            if (lt != null) {
438                                LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
439                                layoutEditor.setSelectionRect(ltv.getBounds());
440                                ltv.showPopup();
441                            }
442                        }
443                    });
444                }
445                if (getConnectB() != null) {
446                    connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "B") + getConnectB().getName()) {
447                        @Override
448                        public void actionPerformed(ActionEvent e) {
449                            LayoutEditorFindItems lf = layoutEditor.getFinder();
450                            LayoutTrack lt = lf.findObjectByName(getConnectB().getName());
451                            // this shouldn't ever be null... however...
452                            if (lt != null) {
453                                LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
454                                layoutEditor.setSelectionRect(ltv.getBounds());
455                                ltv.showPopup();
456                            }
457                        }
458                    });
459                }
460                if (getConnectC() != null) {
461                    connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "C") + getConnectC().getName()) {
462                        @Override
463                        public void actionPerformed(ActionEvent e) {
464                            LayoutEditorFindItems lf = layoutEditor.getFinder();
465                            LayoutTrack lt = lf.findObjectByName(getConnectC().getName());
466                            // this shouldn't ever be null... however...
467                            if (lt != null) {
468                                LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
469                                layoutEditor.setSelectionRect(ltv.getBounds());
470                                ltv.showPopup();
471                            }
472                        }
473                    });
474                }
475                if (getConnectD() != null) {
476                    connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "D") + getConnectD().getName()) {
477                        @Override
478                        public void actionPerformed(ActionEvent e) {
479                            LayoutEditorFindItems lf = layoutEditor.getFinder();
480                            LayoutTrack lt = lf.findObjectByName(getConnectD().getName());
481                            // this shouldn't ever be null... however...
482                            if (lt != null) {
483                                LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
484                                layoutEditor.setSelectionRect(ltv.getBounds());
485                                ltv.showPopup();
486                            }
487                        }
488                    });
489                }
490                popup.add(connectionsMenu);
491            }
492
493            popup.add(new JSeparator(JSeparator.HORIZONTAL));
494
495            JCheckBoxMenuItem hiddenCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("Hidden"));
496            hiddenCheckBoxMenuItem.setSelected(isHidden());
497            popup.add(hiddenCheckBoxMenuItem);
498            hiddenCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e1) -> {
499                JCheckBoxMenuItem o = (JCheckBoxMenuItem) e1.getSource();
500                setHidden(o.isSelected());
501            });
502
503            JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(Bundle.getMessage("Disabled"));
504            cbmi.setSelected(isDisabled());
505            popup.add(cbmi);
506            cbmi.addActionListener((java.awt.event.ActionEvent e2) -> {
507                JCheckBoxMenuItem o = (JCheckBoxMenuItem) e2.getSource();
508                setDisabled(o.isSelected());
509            });
510
511            cbmi = new JCheckBoxMenuItem(Bundle.getMessage("DisabledWhenOccupied"));
512            cbmi.setSelected(isDisabledWhenOccupied());
513            popup.add(cbmi);
514            cbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
515                JCheckBoxMenuItem o = (JCheckBoxMenuItem) e3.getSource();
516                setDisableWhenOccupied(o.isSelected());
517            });
518
519            popup.add(new AbstractAction(Bundle.getMessage("ButtonEdit")) {
520                @Override
521                public void actionPerformed(ActionEvent e) {
522                    editor.editLayoutTrack(LayoutSlipView.this);
523                }
524            });
525            popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) {
526                @Override
527                public void actionPerformed(ActionEvent e) {
528                    if (canRemove() && layoutEditor.removeLayoutSlip(slip)) {
529                        // Returned true if user did not cancel
530                        remove();
531                        dispose();
532                    }
533                }
534            });
535            if ((getConnectA() == null) && (getConnectB() == null)
536                    && (getConnectC() == null) && (getConnectD() == null)) {
537                JMenuItem rotateItem = new JMenuItem(Bundle.getMessage("Rotate") + "...");
538                popup.add(rotateItem);
539                rotateItem.addActionListener(
540                        (ActionEvent event) -> {
541                            boolean entering = true;
542                            boolean error = false;
543                            String newAngle = "";
544                            while (entering) {
545                                // prompt for rotation angle
546                                error = false;
547                                newAngle = JOptionPane.showInputDialog(layoutEditor,
548                                        Bundle.getMessage("MakeLabel", Bundle.getMessage("EnterRotation")));
549                                if (newAngle.isEmpty()) {
550                                    return;  // cancelled
551                                }
552                                double rot = 0.0;
553                                try {
554                                    rot = Double.parseDouble(newAngle);
555                                } catch (Exception e1) {
556                                    JOptionPane.showMessageDialog(layoutEditor, Bundle.getMessage("Error3")
557                                            + " " + e1, Bundle.getMessage("ErrorTitle"), JOptionPane.ERROR_MESSAGE);
558                                    error = true;
559                                    newAngle = "";
560                                }
561                                if (!error) {
562                                    entering = false;
563                                    if (rot != 0.0) {
564                                        rotateCoords(rot);
565                                        layoutEditor.redrawPanel();
566                                    }
567                                }
568                            }
569                        }
570                );
571            }
572            if ((getTurnout() != null) && (getTurnoutB() != null)) {
573                if (blockAssigned) {
574                    AbstractAction ssaa = new AbstractAction(Bundle.getMessage("SetSignals")) {
575                        @Override
576                        public void actionPerformed(ActionEvent e) {
577                            layoutEditor.getLETools().setSignalsAtSlipFromMenu(
578                                    slip,
579                                    getLayoutEditorToolBarPanel().signalIconEditor,
580                                    getLayoutEditorToolBarPanel().signalFrame);
581                        }
582                    };
583                    JMenu jm = new JMenu(Bundle.getMessage("SignalHeads"));
584                    if (layoutEditor.getLETools().addLayoutSlipSignalHeadInfoToMenu(
585                            slip, jm)) {
586                        jm.add(ssaa);
587                        popup.add(jm);
588                    } else {
589                        popup.add(ssaa);
590                    }
591
592                }
593
594                final String[] boundaryBetween = getBlockBoundaries();
595                boolean blockBoundaries = false;
596
597                for (int i = 0; i < 4; i++) {
598                    if (boundaryBetween[i] != null) {
599                        blockBoundaries = true;
600                    }
601                }
602                if (blockBoundaries) {
603                    popup.add(new AbstractAction(Bundle.getMessage("SetSignalMasts")) {
604                        @Override
605                        public void actionPerformed(ActionEvent e) {
606                            layoutEditor.getLETools().setSignalMastsAtSlipFromMenu(
607                                    slip,
608                                    boundaryBetween,
609                                    getLayoutEditorToolBarPanel().signalFrame);
610                        }
611                    });
612                    popup.add(new AbstractAction(Bundle.getMessage("SetSensors")) {
613                        @Override
614                        public void actionPerformed(ActionEvent e) {
615                            layoutEditor.getLETools().setSensorsAtSlipFromMenu(
616                                    slip, boundaryBetween,
617                                    getLayoutEditorToolBarPanel().sensorIconEditor,
618                                    getLayoutEditorToolBarPanel().sensorFrame);
619                        }
620                    });
621                }
622
623                if (jmri.InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()
624                        && blockAssigned) {
625                    popup.add(new AbstractAction(Bundle.getMessage("ViewBlockRouting")) {
626                        @Override
627                        public void actionPerformed(ActionEvent event) {
628                            AbstractAction routeTableAction = new LayoutBlockRouteTableAction("ViewRouting", getLayoutBlock());
629                            routeTableAction.actionPerformed(event);
630                        }
631                    });
632                }
633            }
634            setAdditionalEditPopUpMenu(popup);
635            layoutEditor.setShowAlignmentMenu(popup);
636            popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
637        } else if (!viewAdditionalMenu.isEmpty()) {
638            setAdditionalViewPopUpMenu(popup);
639            popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
640        }
641        return popup;
642    }   // showPopup
643
644    @Override
645    public String[] getBlockBoundaries() {
646        return slip.getBlockBoundaries();
647    }
648
649    /**
650     * Clean up when this object is no longer needed. Should not be called while
651     * the object is still displayed; see remove()
652     */
653    @Override
654    public void dispose() {
655        if (popup != null) {
656            popup.removeAll();
657        }
658        popup = null;
659    }
660
661    /**
662     * Removes this object from display and persistance
663     */
664    @Override
665    public void remove() {
666        slip.remove();
667    }
668
669
670    public int getTurnoutState(@Nonnull Turnout turn, int state) {
671       return slip.getTurnoutState(turn, state);
672    }
673
674    public int getTurnoutState(int state) {
675       return slip.getTurnoutState(state);
676    }
677
678    public int getTurnoutBState(int state) {
679       return slip.getTurnoutBState(state);
680    }
681
682    public void setTurnoutStates(int state, @Nonnull String turnStateA, @Nonnull String turnStateB) {
683        slip.setTurnoutStates(state, turnStateA, turnStateB);
684    }
685
686    /**
687     * Check if either turnout is inconsistent. This is used to create an
688     * alternate slip image.
689     *
690     * @return true if either turnout is inconsistent.
691     */
692    private boolean isTurnoutInconsistent() {
693       return slip.isTurnoutInconsistent();
694    }
695
696    @Override
697    protected void draw1(Graphics2D g2, boolean drawMain, boolean isBlock) {
698        Point2D pA = getCoordsA();
699        Point2D pB = getCoordsB();
700        Point2D pC = getCoordsC();
701        Point2D pD = getCoordsD();
702
703        boolean mainlineA = isMainlineA();
704        boolean mainlineB = isMainlineB();
705        boolean mainlineC = isMainlineC();
706        boolean mainlineD = isMainlineD();
707
708        boolean drawUnselectedLeg = layoutEditor.isTurnoutDrawUnselectedLeg()
709                || isTurnoutInconsistent();
710
711        int slipState = getSlipState();
712
713        Color color = g2.getColor();
714
715        // if this isn't a block line all these will be the same color
716        Color colorA = color, colorB = color, colorC = color, colorD = color;
717
718        if (isBlock) {
719            LayoutBlock layoutBlockA = getLayoutBlock();
720            colorA = (layoutBlockA != null) ? layoutBlockA.getBlockTrackColor() : color;
721            LayoutBlock layoutBlockB = getLayoutBlockB();
722            colorB = (layoutBlockB != null) ? layoutBlockB.getBlockTrackColor() : color;
723            LayoutBlock layoutBlockC = getLayoutBlockC();
724            colorC = (layoutBlockC != null) ? layoutBlockC.getBlockTrackColor() : color;
725            LayoutBlock layoutBlockD = getLayoutBlockD();
726            colorD = (layoutBlockD != null) ? layoutBlockD.getBlockTrackColor() : color;
727
728            if (slipState == STATE_AC) {
729                colorA = (layoutBlockA != null) ? layoutBlockA.getBlockColor() : color;
730                colorC = (layoutBlockC != null) ? layoutBlockC.getBlockColor() : color;
731            } else if (slipState == STATE_BD) {
732                colorB = (layoutBlockB != null) ? layoutBlockB.getBlockColor() : color;
733                colorD = (layoutBlockD != null) ? layoutBlockD.getBlockColor() : color;
734            } else if (slipState == STATE_AD) {
735                colorA = (layoutBlockA != null) ? layoutBlockA.getBlockColor() : color;
736                colorD = (layoutBlockD != null) ? layoutBlockD.getBlockColor() : color;
737            } else if (slipState == STATE_BC) {
738                colorB = (layoutBlockB != null) ? layoutBlockB.getBlockColor() : color;
739                colorC = (layoutBlockC != null) ? layoutBlockC.getBlockColor() : color;
740            }
741        }
742        Point2D oneForthPointAC = MathUtil.oneFourthPoint(pA, pC);
743        Point2D oneThirdPointAC = MathUtil.oneThirdPoint(pA, pC);
744        Point2D midPointAC = MathUtil.midPoint(pA, pC);
745        Point2D twoThirdsPointAC = MathUtil.twoThirdsPoint(pA, pC);
746        Point2D threeFourthsPointAC = MathUtil.threeFourthsPoint(pA, pC);
747
748        Point2D oneForthPointBD = MathUtil.oneFourthPoint(pB, pD);
749        Point2D oneThirdPointBD = MathUtil.oneThirdPoint(pB, pD);
750        Point2D midPointBD = MathUtil.midPoint(pB, pD);
751        Point2D twoThirdsPointBD = MathUtil.twoThirdsPoint(pB, pD);
752        Point2D threeFourthsPointBD = MathUtil.threeFourthsPoint(pB, pD);
753
754        Point2D midPointAD = MathUtil.midPoint(oneThirdPointAC, twoThirdsPointBD);
755        Point2D midPointBC = MathUtil.midPoint(oneThirdPointBD, twoThirdsPointAC);
756
757        if (slipState == STATE_AD) {
758            // draw A<===>D
759            if (drawMain == mainlineA) {
760                g2.setColor(colorA);
761                g2.draw(new Line2D.Double(pA, oneThirdPointAC));
762                g2.draw(new Line2D.Double(oneThirdPointAC, midPointAD));
763            }
764            if (drawMain == mainlineD) {
765                g2.setColor(colorD);
766                g2.draw(new Line2D.Double(midPointAD, twoThirdsPointBD));
767                g2.draw(new Line2D.Double(twoThirdsPointBD, pD));
768            }
769        } else if (slipState == STATE_AC) {
770            // draw A<===>C
771            if (drawMain == mainlineA) {
772                g2.setColor(colorA);
773                g2.draw(new Line2D.Double(pA, oneThirdPointAC));
774                g2.draw(new Line2D.Double(oneThirdPointAC, midPointAC));
775            }
776            if (drawMain == mainlineC) {
777                g2.setColor(colorC);
778                g2.draw(new Line2D.Double(midPointAC, twoThirdsPointAC));
779                g2.draw(new Line2D.Double(twoThirdsPointAC, pC));
780            }
781        } else if (slipState == STATE_BD) {
782            // draw B<===>D
783            if (drawMain == mainlineB) {
784                g2.setColor(colorB);
785                g2.draw(new Line2D.Double(pB, oneThirdPointBD));
786                g2.draw(new Line2D.Double(oneThirdPointBD, midPointBD));
787            }
788            if (drawMain == mainlineD) {
789                g2.setColor(colorD);
790                g2.draw(new Line2D.Double(midPointBD, twoThirdsPointBD));
791                g2.draw(new Line2D.Double(twoThirdsPointBD, pD));
792            }
793        } else if (slipState == STATE_BC) {
794            if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) {
795                // draw B<===>C
796                if (drawMain == mainlineB) {
797                    g2.setColor(colorB);
798                    g2.draw(new Line2D.Double(pB, oneThirdPointBD));
799                    g2.draw(new Line2D.Double(oneThirdPointBD, midPointBC));
800                }
801                if (drawMain == mainlineC) {
802                    g2.setColor(colorC);
803                    g2.draw(new Line2D.Double(midPointBC, twoThirdsPointAC));
804                    g2.draw(new Line2D.Double(twoThirdsPointAC, pC));
805                }
806            }   // DOUBLE_SLIP
807        }
808
809        if (!isBlock || drawUnselectedLeg) {
810            if (slipState == STATE_AC) {
811                if (drawMain == mainlineB) {
812                    g2.setColor(colorB);
813                    g2.draw(new Line2D.Double(pB, oneForthPointBD));
814                }
815                if (drawMain == mainlineD) {
816                    g2.setColor(colorD);
817                    g2.draw(new Line2D.Double(threeFourthsPointBD, pD));
818                }
819            } else if (slipState == STATE_BD) {
820                if (drawMain == mainlineA) {
821                    g2.setColor(colorA);
822                    g2.draw(new Line2D.Double(pA, oneForthPointAC));
823                }
824                if (drawMain == mainlineC) {
825                    g2.setColor(colorC);
826                    g2.draw(new Line2D.Double(threeFourthsPointAC, pC));
827                }
828            } else if (slipState == STATE_AD) {
829                if (drawMain == mainlineB) {
830                    g2.setColor(colorB);
831                    g2.draw(new Line2D.Double(pB, oneForthPointBD));
832                }
833                if (drawMain == mainlineC) {
834                    g2.setColor(colorC);
835                    g2.draw(new Line2D.Double(threeFourthsPointAC, pC));
836                }
837            } else if (slipState == STATE_BC) {
838                if (drawMain == mainlineA) {
839                    g2.setColor(colorA);
840                    g2.draw(new Line2D.Double(pA, oneForthPointAC));
841                }
842                if (drawMain == mainlineD) {
843                    g2.setColor(colorD);
844                    g2.draw(new Line2D.Double(threeFourthsPointBD, pD));
845                }
846            } else {
847                if (drawMain == mainlineA) {
848                    g2.setColor(colorA);
849                    g2.draw(new Line2D.Double(pA, oneForthPointAC));
850                }
851                if (drawMain == mainlineB) {
852                    g2.setColor(colorB);
853                    g2.draw(new Line2D.Double(pB, oneForthPointBD));
854                }
855                if (drawMain == mainlineC) {
856                    g2.setColor(colorC);
857                    g2.draw(new Line2D.Double(threeFourthsPointAC, pC));
858                }
859                if (drawMain == mainlineD) {
860                    g2.setColor(colorD);
861                    g2.draw(new Line2D.Double(threeFourthsPointBD, pD));
862                }
863            }
864        }
865    }   // draw1
866
867    /**
868     * {@inheritDoc}
869     */
870    @Override
871    protected void draw2(Graphics2D g2, boolean drawMain, float railDisplacement) {
872        Point2D pA = getCoordsA();
873        Point2D pB = getCoordsB();
874        Point2D pC = getCoordsC();
875        Point2D pD = getCoordsD();
876        Point2D pM = getCoordsCenter();
877
878        Point2D vAC = MathUtil.normalize(MathUtil.subtract(pC, pA), railDisplacement);
879        double dirAC_DEG = MathUtil.computeAngleDEG(pA, pC);
880        Point2D vACo = MathUtil.orthogonal(vAC);
881        Point2D pAL = MathUtil.subtract(pA, vACo);
882        Point2D pAR = MathUtil.add(pA, vACo);
883        Point2D pCL = MathUtil.subtract(pC, vACo);
884        Point2D pCR = MathUtil.add(pC, vACo);
885
886        Point2D vBD = MathUtil.normalize(MathUtil.subtract(pD, pB), railDisplacement);
887        double dirBD_DEG = MathUtil.computeAngleDEG(pB, pD);
888        Point2D vBDo = MathUtil.orthogonal(vBD);
889        Point2D pBL = MathUtil.subtract(pB, vBDo);
890        Point2D pBR = MathUtil.add(pB, vBDo);
891        Point2D pDL = MathUtil.subtract(pD, vBDo);
892        Point2D pDR = MathUtil.add(pD, vBDo);
893
894        double deltaDEG = MathUtil.absDiffAngleDEG(dirAC_DEG, dirBD_DEG);
895        double deltaRAD = Math.toRadians(deltaDEG);
896
897        double hypotV = railDisplacement / Math.cos((Math.PI - deltaRAD) / 2.0);
898        double hypotK = railDisplacement / Math.cos(deltaRAD / 2.0);
899
900        log.debug("dir AC: {}, BD: {}, diff: {}", dirAC_DEG, dirBD_DEG, deltaDEG);
901
902        Point2D vDisK = MathUtil.normalize(MathUtil.subtract(vAC, vBD), hypotK);
903        Point2D vDisV = MathUtil.normalize(MathUtil.orthogonal(vDisK), hypotV);
904        Point2D pKL = MathUtil.subtract(pM, vDisK);
905        Point2D pKR = MathUtil.add(pM, vDisK);
906        Point2D pVL = MathUtil.add(pM, vDisV);
907        Point2D pVR = MathUtil.subtract(pM, vDisV);
908
909        // this is the vector (rail gaps) for the diamond parts
910        double railGap = 2.0 / Math.sin(deltaRAD);
911        Point2D vAC2 = MathUtil.normalize(vAC, railGap);
912        Point2D vBD2 = MathUtil.normalize(vBD, railGap);
913        // KR and VR toward A, KL and VL toward C
914        Point2D pKRtA = MathUtil.subtract(pKR, vAC2);
915        Point2D pVRtA = MathUtil.subtract(pVR, vAC2);
916        Point2D pKLtC = MathUtil.add(pKL, vAC2);
917        Point2D pVLtC = MathUtil.add(pVL, vAC2);
918
919        // VR and KL toward B, KR and VL toward D
920        Point2D pVRtB = MathUtil.subtract(pVR, vBD2);
921        Point2D pKLtB = MathUtil.subtract(pKL, vBD2);
922        Point2D pKRtD = MathUtil.add(pKR, vBD2);
923        Point2D pVLtD = MathUtil.add(pVL, vBD2);
924
925        // outer (closed) switch points
926        Point2D pAPL = MathUtil.add(pAL, MathUtil.subtract(pVL, pAR));
927        Point2D pBPR = MathUtil.add(pBR, MathUtil.subtract(pVL, pBL));
928        Point2D pCPR = MathUtil.add(pCR, MathUtil.subtract(pVR, pCL));
929        Point2D pDPL = MathUtil.add(pDL, MathUtil.subtract(pVR, pDR));
930
931        // this is the vector (rail gaps) for the inner (open) switch points
932        Point2D vACo2 = MathUtil.normalize(vACo, 2.0);
933        Point2D vBDo2 = MathUtil.normalize(vBDo, 2.0);
934        Point2D pASL = MathUtil.add(pAPL, vACo2);
935        Point2D pBSR = MathUtil.subtract(pBPR, vBDo2);
936        Point2D pCSR = MathUtil.subtract(pCPR, vACo2);
937        Point2D pDSL = MathUtil.add(pDPL, vBDo2);
938
939        Point2D pVLP = MathUtil.add(pVLtD, vAC2);
940        Point2D pVRP = MathUtil.subtract(pVRtA, vBD2);
941
942        Point2D pKLH = MathUtil.midPoint(pM, pKL);
943        Point2D pKRH = MathUtil.midPoint(pM, pKR);
944
945        boolean mainlineA = isMainlineA();
946        boolean mainlineB = isMainlineB();
947        boolean mainlineC = isMainlineC();
948        boolean mainlineD = isMainlineD();
949
950        if (drawMain == mainlineA) {
951            g2.draw(new Line2D.Double(pAR, pVL));
952            g2.draw(new Line2D.Double(pVLtD, pKLtB));
953            GeneralPath path = new GeneralPath();
954            path.moveTo(pAL.getX(), pAL.getY());
955            path.lineTo(pAPL.getX(), pAPL.getY());
956            path.quadTo(pKL.getX(), pKL.getY(), pDPL.getX(), pDPL.getY());
957            g2.draw(path);
958        }
959        if (drawMain == mainlineB) {
960            g2.draw(new Line2D.Double(pBL, pVL));
961            g2.draw(new Line2D.Double(pVLtC, pKRtA));
962            if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) {
963                GeneralPath path = new GeneralPath();
964                path.moveTo(pBR.getX(), pBR.getY());
965                path.lineTo(pBPR.getX(), pBPR.getY());
966                path.quadTo(pKR.getX(), pKR.getY(), pCPR.getX(), pCPR.getY());
967                g2.draw(path);
968            } else {
969                g2.draw(new Line2D.Double(pBR, pKR));
970            }
971        }
972        if (drawMain == mainlineC) {
973            g2.draw(new Line2D.Double(pCL, pVR));
974            g2.draw(new Line2D.Double(pVRtB, pKRtD));
975            if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) {
976                GeneralPath path = new GeneralPath();
977                path.moveTo(pCR.getX(), pCR.getY());
978                path.lineTo(pCPR.getX(), pCPR.getY());
979                path.quadTo(pKR.getX(), pKR.getY(), pBPR.getX(), pBPR.getY());
980                g2.draw(path);
981            } else {
982                g2.draw(new Line2D.Double(pCR, pKR));
983            }
984        }
985        if (drawMain == mainlineD) {
986            g2.draw(new Line2D.Double(pDR, pVR));
987            g2.draw(new Line2D.Double(pVRtA, pKLtC));
988            GeneralPath path = new GeneralPath();
989            path.moveTo(pDL.getX(), pDL.getY());
990            path.lineTo(pDPL.getX(), pDPL.getY());
991            path.quadTo(pKL.getX(), pKL.getY(), pAPL.getX(), pAPL.getY());
992            g2.draw(path);
993        }
994
995        int slipState = getSlipState();
996        if (slipState == STATE_AD) {
997            if (drawMain == mainlineA) {
998                g2.draw(new Line2D.Double(pASL, pKL));
999                g2.draw(new Line2D.Double(pVLP, pKLH));
1000            }
1001            if (drawMain == mainlineB) {
1002                g2.draw(new Line2D.Double(pBPR, pKR));
1003                g2.draw(new Line2D.Double(pVLtC, pKRH));
1004            }
1005            if (drawMain == mainlineC) {
1006                g2.draw(new Line2D.Double(pCPR, pKR));
1007                g2.draw(new Line2D.Double(pVRtB, pKRH));
1008            }
1009            if (drawMain == mainlineD) {
1010                g2.draw(new Line2D.Double(pDSL, pKL));
1011                g2.draw(new Line2D.Double(pVRP, pKLH));
1012            }
1013        } else if (slipState == STATE_AC) {
1014            if (drawMain == mainlineA) {
1015                g2.draw(new Line2D.Double(pAPL, pKL));
1016                g2.draw(new Line2D.Double(pVLtD, pKLH));
1017            }
1018            if (drawMain == mainlineB) {
1019                g2.draw(new Line2D.Double(pBSR, pKR));
1020                g2.draw(new Line2D.Double(pVLP, pKRH));
1021            }
1022            if (drawMain == mainlineC) {
1023                g2.draw(new Line2D.Double(pCPR, pKR));
1024                g2.draw(new Line2D.Double(pVRtB, pKRH));
1025            }
1026            if (drawMain == mainlineD) {
1027                g2.draw(new Line2D.Double(pDSL, pKL));
1028                g2.draw(new Line2D.Double(pVRP, pKLH));
1029            }
1030        } else if (slipState == STATE_BD) {
1031            if (drawMain == mainlineA) {
1032                g2.draw(new Line2D.Double(pASL, pKL));
1033                g2.draw(new Line2D.Double(pVLP, pKLH));
1034            }
1035            if (drawMain == mainlineB) {
1036                g2.draw(new Line2D.Double(pBPR, pKR));
1037                g2.draw(new Line2D.Double(pVLtC, pKRH));
1038            }
1039            if (drawMain == mainlineC) {
1040                g2.draw(new Line2D.Double(pCSR, pKR));
1041                g2.draw(new Line2D.Double(pVRP, pKRH));
1042            }
1043            if (drawMain == mainlineD) {
1044                g2.draw(new Line2D.Double(pDPL, pKL));
1045                g2.draw(new Line2D.Double(pVRtA, pKLH));
1046            }
1047        } else if ((getTurnoutType() == TurnoutType.DOUBLE_SLIP)
1048                && (slipState == STATE_BC)) {
1049            if (drawMain == mainlineA) {
1050                g2.draw(new Line2D.Double(pAPL, pKL));
1051                g2.draw(new Line2D.Double(pVLtD, pKLH));
1052            }
1053            if (drawMain == mainlineB) {
1054                g2.draw(new Line2D.Double(pBSR, pKR));
1055                g2.draw(new Line2D.Double(pVLP, pKRH));
1056            }
1057            if (drawMain == mainlineC) {
1058                g2.draw(new Line2D.Double(pCSR, pKR));
1059                g2.draw(new Line2D.Double(pVRP, pKRH));
1060            }
1061            if (drawMain == mainlineD) {
1062                g2.draw(new Line2D.Double(pDPL, pKL));
1063                g2.draw(new Line2D.Double(pVRtA, pKLH));
1064            }
1065        }   // DOUBLE_SLIP
1066    }   // draw2
1067
1068    /**
1069     * {@inheritDoc}
1070     */
1071    @Override
1072    protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) {
1073        if (((specificType == HitPointType.NONE) || (specificType == HitPointType.SLIP_A))
1074                && (getConnectA() == null)) {
1075            g2.fill(trackControlCircleAt(getCoordsA()));
1076        }
1077
1078        if (((specificType == HitPointType.NONE) || (specificType == HitPointType.SLIP_B))
1079                && (getConnectB() == null)) {
1080            g2.fill(trackControlCircleAt(getCoordsB()));
1081        }
1082
1083        if (((specificType == HitPointType.NONE) || (specificType == HitPointType.SLIP_C))
1084                && (getConnectC() == null)) {
1085            g2.fill(trackControlCircleAt(getCoordsC()));
1086        }
1087
1088        if (((specificType == HitPointType.NONE) || (specificType == HitPointType.SLIP_D))
1089                && (getConnectD() == null)) {
1090            g2.fill(trackControlCircleAt(getCoordsD()));
1091        }
1092    }
1093
1094    @Override
1095    protected void drawTurnoutControls(Graphics2D g2) {
1096        if (!isDisabled() && !(isDisabledWhenOccupied() && isOccupied())) {
1097            // TODO: query user base if this is "acceptable" (can obstruct state)
1098            if (false) {
1099                int stateA = UNKNOWN;
1100                Turnout toA = getTurnout();
1101                if (toA != null) {
1102                    stateA = toA.getKnownState();
1103                }
1104
1105                Color foregroundColor = g2.getColor();
1106                Color backgroundColor = g2.getBackground();
1107
1108                if (stateA == Turnout.THROWN) {
1109                    g2.setColor(backgroundColor);
1110                } else if (stateA != Turnout.CLOSED) {
1111                    g2.setColor(Color.GRAY);
1112                }
1113                Point2D rightCircleCenter = getCoordsRight();
1114                if (layoutEditor.isTurnoutFillControlCircles()) {
1115                    g2.fill(trackControlCircleAt(rightCircleCenter));
1116                } else {
1117                    g2.draw(trackControlCircleAt(rightCircleCenter));
1118                }
1119                if (stateA != Turnout.CLOSED) {
1120                    g2.setColor(foregroundColor);
1121                }
1122
1123                int stateB = UNKNOWN;
1124                Turnout toB = getTurnoutB();
1125                if (toB != null) {
1126                    stateB = toB.getKnownState();
1127                }
1128
1129                if (stateB == Turnout.THROWN) {
1130                    g2.setColor(backgroundColor);
1131                } else if (stateB != Turnout.CLOSED) {
1132                    g2.setColor(Color.GRAY);
1133                }
1134                // drawHidden left/right turnout control circles
1135                Point2D leftCircleCenter = getCoordsLeft();
1136                if (layoutEditor.isTurnoutFillControlCircles()) {
1137                    g2.fill(trackControlCircleAt(leftCircleCenter));
1138                } else {
1139                    g2.draw(trackControlCircleAt(leftCircleCenter));
1140                }
1141                if (stateB != Turnout.CLOSED) {
1142                    g2.setColor(foregroundColor);
1143                }
1144            } else {
1145                Point2D rightCircleCenter = getCoordsRight();
1146                g2.draw(trackControlCircleAt(rightCircleCenter));
1147                Point2D leftCircleCenter = getCoordsLeft();
1148                g2.draw(trackControlCircleAt(leftCircleCenter));
1149            }
1150        }
1151    } // drawTurnoutControls
1152
1153    public static class TurnoutState {
1154
1155        private int turnoutA = Turnout.CLOSED;
1156        private int turnoutB = Turnout.CLOSED;
1157        private JComboBox<String> turnoutABox;
1158        private JComboBox<String> turnoutBBox;
1159
1160        TurnoutState(int turnoutA, int turnoutB) {
1161            this.turnoutA = turnoutA;
1162            this.turnoutB = turnoutB;
1163        }
1164
1165        public int getTurnoutAState() {
1166            return turnoutA;
1167        }
1168
1169        public int getTurnoutBState() {
1170            return turnoutB;
1171        }
1172
1173        public void setTurnoutAState(int state) {
1174            turnoutA = state;
1175        }
1176
1177        public void setTurnoutBState(int state) {
1178            turnoutB = state;
1179        }
1180
1181        public JComboBox<String> getComboA() {
1182            if (turnoutABox == null) {
1183                String[] state = new String[]{InstanceManager.turnoutManagerInstance().getClosedText(),
1184                    InstanceManager.turnoutManagerInstance().getThrownText()};
1185                turnoutABox = new JComboBox<>(state);
1186                if (turnoutA == Turnout.THROWN) {
1187                    turnoutABox.setSelectedIndex(1);
1188                }
1189            }
1190            return turnoutABox;
1191        }
1192
1193        public JComboBox<String> getComboB() {
1194            if (turnoutBBox == null) {
1195                String[] state = new String[]{InstanceManager.turnoutManagerInstance().getClosedText(),
1196                    InstanceManager.turnoutManagerInstance().getThrownText()};
1197                turnoutBBox = new JComboBox<>(state);
1198                if (turnoutB == Turnout.THROWN) {
1199                    turnoutBBox.setSelectedIndex(1);
1200                }
1201            }
1202            return turnoutBBox;
1203        }
1204
1205        public int getTestTurnoutAState() {
1206            int result = Turnout.THROWN;
1207            if (turnoutABox != null) {
1208                if (turnoutABox.getSelectedIndex() == 0) {
1209                    result = Turnout.CLOSED;
1210                }
1211            }
1212            return result;
1213        }
1214
1215        public int getTestTurnoutBState() {
1216            int result = Turnout.THROWN;
1217            if (turnoutBBox != null) {
1218                if (turnoutBBox.getSelectedIndex() == 0) {
1219                    result = Turnout.CLOSED;
1220                }
1221            }
1222            return result;
1223        }
1224
1225        public void updateStatesFromCombo() {
1226            if ((turnoutABox != null) && (turnoutBBox != null)) {
1227                turnoutA = getTestTurnoutAState();
1228                turnoutB = getTestTurnoutBState();
1229            }
1230        }
1231
1232        @Override
1233        public boolean equals(Object object) {
1234            if (this == object) {
1235                return true;
1236            }
1237            if (object == null) {
1238                return false;
1239            }
1240            if (!(object instanceof TurnoutState)) {
1241                return false;
1242            }
1243            TurnoutState tso = (TurnoutState) object;
1244
1245            return ((getTurnoutAState() == tso.getTurnoutAState())
1246                    && (getTurnoutBState() == tso.getTurnoutBState()));
1247        }
1248
1249        /**
1250         * Hash on the header
1251         */
1252        @Override
1253        public int hashCode() {
1254            int result = 7;
1255            result = (37 * result) + getTurnoutAState();
1256            result = (37 * result) + getTurnoutBState();
1257
1258            return result;
1259        }
1260
1261    }   // class TurnoutState
1262
1263    /*
1264    this is used by ConnectivityUtil to determine the turnout state necessary to get from prevLayoutBlock ==> currLayoutBlock ==> nextLayoutBlock
1265     */
1266    @Override
1267    protected int getConnectivityStateForLayoutBlocks(
1268            @CheckForNull LayoutBlock thisLayoutBlock,
1269            @CheckForNull LayoutBlock prevLayoutBlock,
1270            @CheckForNull LayoutBlock nextLayoutBlock,
1271            boolean suppress) {
1272
1273        return slip.getConnectivityStateForLayoutBlocks(thisLayoutBlock,
1274                                                        prevLayoutBlock, nextLayoutBlock,
1275                                                        suppress);
1276    }
1277
1278    /*
1279    * {@inheritDoc}
1280     */
1281    @Override
1282    public void reCheckBlockBoundary() {
1283        slip.reCheckBlockBoundary();
1284    }
1285
1286    /*
1287    * {@inheritDoc}
1288     */
1289    @Override
1290    @Nonnull
1291    protected List<LayoutConnectivity> getLayoutConnectivity() {
1292        return slip.getLayoutConnectivity();
1293    }
1294
1295    /**
1296     * {@inheritDoc}
1297     */
1298    @Override
1299    public List<HitPointType> checkForFreeConnections() {
1300        return slip.checkForFreeConnections();
1301    }
1302
1303    // NOTE: LayoutSlip uses the checkForNonContiguousBlocks
1304    //      and collectContiguousTracksNamesInBlockNamed methods
1305    //      inherited from LayoutTurnout
1306    //
1307    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutSlipView.class);
1308}