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