001package jmri.jmrit.display.layoutEditor;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.awt.geom.Point2D;
006import java.awt.geom.Rectangle2D;
007import java.text.MessageFormat;
008import java.util.List;
009import java.util.*;
010
011import javax.annotation.*;
012import javax.swing.*;
013
014import jmri.*;
015import jmri.jmrit.display.EditorManager;
016import jmri.jmrit.display.layoutEditor.PositionablePoint.PointType;
017import jmri.jmrit.signalling.SignallingGuiTools;
018import jmri.util.*;
019import jmri.util.swing.JCBHandle;
020import jmri.util.swing.JmriColorChooser;
021
022/**
023 * MVC View component for the PositionablePoint class.
024 *
025 * @author Bob Jacobsen  Copyright (c) 2020
026 *
027 * <p>
028 * Arrows and bumpers are visual, presentation aspects handled in the View.
029 */
030public class PositionablePointView extends LayoutTrackView {
031
032    protected NamedBeanHandle<SignalHead> signalEastHeadNamed = null; // signal head for east (south) bound trains
033    protected NamedBeanHandle<SignalHead> signalWestHeadNamed = null; // signal head for west (north) bound trains
034
035    private NamedBeanHandle<SignalMast> eastBoundSignalMastNamed = null;
036    private NamedBeanHandle<SignalMast> westBoundSignalMastNamed = null;
037    /* We use a namedbeanhandle for the sensors, even though we only store the name here,
038    this is so that we can keep up with moves and changes of userNames */
039    private NamedBeanHandle<Sensor> eastBoundSensorNamed = null;
040    private NamedBeanHandle<Sensor> westBoundSensorNamed = null;
041
042    /**
043     * constructor method.
044     * @param point the positionable point.
045     * @param c location to display the positionable point
046     * @param layoutEditor for access to tools
047     */
048    public PositionablePointView(@Nonnull PositionablePoint point,
049            Point2D c,
050            @Nonnull LayoutEditor layoutEditor) {
051        super(point, c, layoutEditor);
052        this.positionablePoint = point;
053    }
054
055    final private PositionablePoint positionablePoint;
056
057    public PositionablePoint getPoint() { return positionablePoint; }
058
059    // this should only be used for debugging...
060    @Override
061    public String toString() {
062        String result = "PositionalablePoint";
063        switch (getType()) {
064            case ANCHOR: {
065                result = "Anchor";
066                break;
067            }
068            case END_BUMPER: {
069                result = "End Bumper";
070                break;
071            }
072            case EDGE_CONNECTOR: {
073                result = "Edge Connector";
074                break;
075            }
076            default: {
077                result = "Unknown type (" + getType() + ")";
078                break;
079            }
080        }
081        return result + " '" + getName() + "'";
082    }
083
084   /**
085     * Accessor methods
086     * @return Type enum for this Positionable Point
087     */
088    public PointType getType() {
089        return positionablePoint.getType();
090    }
091
092    public void setType(PointType newType) {
093        positionablePoint.setType(newType);
094
095        // (temporary) we keep this echo here until we figure out where arrow info lives
096        if (getType() != newType) {
097            switch (newType) {
098                default:
099                case ANCHOR: {
100                    setTypeAnchor();
101                    break;
102                }
103                case END_BUMPER: {
104                    setTypeEndBumper();
105                    break;
106                }
107                case EDGE_CONNECTOR: {
108                    setTypeEdgeConnector();
109                    break;
110                }
111            }
112            layoutEditor.repaint();
113        }
114    }
115
116    private void setTypeAnchor() {
117        setIdent(layoutEditor.getFinder().uniqueName("A", 1));
118
119        // type = PointType.ANCHOR;
120        positionablePoint.setTypeAnchor();
121
122        if (getConnect1() != null) {
123            TrackSegmentView ctv1 = layoutEditor.getTrackSegmentView(getConnect1());
124            if (getConnect1().getConnect1() == positionablePoint) {
125                ctv1.setArrowEndStart(false);
126                ctv1.setBumperEndStart(false);
127            }
128            if (getConnect1().getConnect2() == positionablePoint) {
129                ctv1.setArrowEndStop(false);
130                ctv1.setBumperEndStop(false);
131            }
132        }
133        if (getConnect2() != null) {
134            TrackSegmentView ctv2 = layoutEditor.getTrackSegmentView(getConnect2());
135            if (getConnect2().getConnect1() == positionablePoint) {
136                ctv2.setArrowEndStart(false);
137                ctv2.setBumperEndStart(false);
138            }
139            if (getConnect2().getConnect2() == positionablePoint) {
140                ctv2.setArrowEndStop(false);
141                ctv2.setBumperEndStop(false);
142            }
143        }
144    }
145
146    private void setTypeEndBumper() {
147        setIdent(layoutEditor.getFinder().uniqueName("EB", 1));
148
149        // type = PointType.END_BUMPER;
150        positionablePoint.setTypeEndBumper();
151
152        if (getConnect1() != null) {
153            TrackSegmentView ctv1 = layoutEditor.getTrackSegmentView(getConnect1());
154            if (getConnect1().getConnect1() == positionablePoint) {
155                ctv1.setArrowEndStart(false);
156                ctv1.setBumperEndStart(true);
157            }
158            if (getConnect1().getConnect2() == positionablePoint) {
159                ctv1.setArrowEndStop(false);
160                ctv1.setBumperEndStop(true);
161            }
162        }
163    }
164
165    private void setTypeEdgeConnector() {
166        setIdent(layoutEditor.getFinder().uniqueName("EC", 1));
167
168        // type = PointType.EDGE_CONNECTOR;
169        positionablePoint.setTypeEdgeConnector();
170
171        if (getConnect1() != null) {
172            TrackSegmentView ctv1 = layoutEditor.getTrackSegmentView(getConnect1());
173            if (getConnect1().getConnect1() == positionablePoint) {
174                ctv1.setBumperEndStart(false);
175            }
176            if (getConnect1().getConnect2() == positionablePoint) {
177                ctv1.setBumperEndStop(false);
178            }
179        }
180    }
181
182    public TrackSegment getConnect1() {
183        return positionablePoint.getConnect1();
184    }
185
186    public TrackSegment getConnect2() {
187        return positionablePoint.getConnect2();
188    }
189
190    public String getLinkedEditorName() {
191        return positionablePoint.getLinkedEditorName();
192    }
193
194    public LayoutEditor getLinkedEditor() {
195        return positionablePoint.getLinkedEditor();
196    }
197
198    public PositionablePoint getLinkedPoint() {
199        return positionablePoint.getLinkedPoint();
200    }
201
202    public void removeLinkedPoint() {
203        positionablePoint.removeLinkedPoint();
204    }
205
206    public String getLinkedPointId() {
207        return positionablePoint.getLinkedPointId();
208    }
209
210    public void setLinkedPoint(PositionablePoint p) {
211        positionablePoint.setLinkedPoint(p);
212    }
213
214    /**
215     * {@inheritDoc}
216     */
217    @Override
218    public void scaleCoords(double xFactor, double yFactor) {
219        Point2D factor = new Point2D.Double(xFactor, yFactor);
220        super.setCoordsCenter(MathUtil.granulize(MathUtil.multiply(getCoordsCenter(), factor), 1.0));
221    }
222
223    /**
224     * {@inheritDoc}
225     */
226    @Override
227    public void translateCoords(double xFactor, double yFactor) {
228        Point2D factor = new Point2D.Double(xFactor, yFactor);
229        super.setCoordsCenter(MathUtil.add(getCoordsCenter(), factor));
230    }
231
232    /**
233     * {@inheritDoc}
234     */
235    @Override
236    public void rotateCoords(double angleDEG) {
237        //can't really rotate a point... so...
238        //nothing to see here... move along...
239    }
240
241    /**
242     * @return the bounds of this positional point
243     */
244    @Override
245    public Rectangle2D getBounds() {
246        Point2D c = getCoordsCenter();
247        //Note: empty bounds don't draw...
248        // so now I'm making them 0.5 bigger in all directions (1 pixel total)
249        return new Rectangle2D.Double(c.getX() - 0.5, c.getY() - 0.5, 1.0, 1.0);
250    }
251
252    @CheckReturnValue
253    protected LayoutEditor getLayoutEditor() {
254        return layoutEditor;
255    }
256
257    @CheckReturnValue
258    @Nonnull
259    public String getEastBoundSignal() {
260        SignalHead h = getEastBoundSignalHead();
261        if (h != null) {
262            return h.getDisplayName();
263        }
264        return "";
265    }
266
267    @CheckForNull
268    @CheckReturnValue
269    public SignalHead getEastBoundSignalHead() {
270        if (getType() == PointType.EDGE_CONNECTOR) {
271            int dir = getConnect1Dir();
272            if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) {
273                if (signalEastHeadNamed != null) {
274                    return signalEastHeadNamed.getBean();
275                }
276                return null;
277            } else if (getLinkedPoint() != null) {
278                // Do some checks to find where the connection is here.
279                int linkDir = getLinkedPoint().getConnect1Dir();
280                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
281                    return getLinkedPoint().getEastBoundSignalHead();
282                }
283            }
284        }
285
286        if (signalEastHeadNamed != null) {
287            return signalEastHeadNamed.getBean();
288        }
289        return null;
290    }
291
292    public void setEastBoundSignal(String signalName) {
293        if (getType() == PointType.EDGE_CONNECTOR) {
294            int dir = getConnect1Dir();
295            if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) {
296                setEastBoundSignalName(signalName);
297            } else if (getLinkedPoint() != null) {
298                int linkDir = getLinkedPoint().getConnect1Dir();
299                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
300                    getLinkedPoint().setEastBoundSignal(signalName);
301                } else {
302                    setEastBoundSignalName(signalName);
303                }
304            } else {
305                setEastBoundSignalName(signalName);
306            }
307        } else {
308            setEastBoundSignalName(signalName);
309        }
310    }
311
312    private void setEastBoundSignalName(@CheckForNull String signalHead) {
313        if (signalHead == null || signalHead.isEmpty()) {
314            signalEastHeadNamed = null;
315            return;
316        }
317
318        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
319        if (head != null) {
320            signalEastHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
321        } else {
322            signalEastHeadNamed = null;
323        }
324    }
325
326    @CheckReturnValue
327    @Nonnull
328    public String getWestBoundSignal() {
329        SignalHead h = getWestBoundSignalHead();
330        if (h != null) {
331            return h.getDisplayName();
332        }
333        return "";
334    }
335
336    @CheckForNull
337    @CheckReturnValue
338    public SignalHead getWestBoundSignalHead() {
339        if (getType() == PointType.EDGE_CONNECTOR) {
340            int dir = getConnect1Dir();
341            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
342                if (signalWestHeadNamed != null) {
343                    return signalWestHeadNamed.getBean();
344                }
345                return null;
346            } else if (getLinkedPoint() != null) {
347                // Do some checks to find where the connection is here.
348                int linkDir = getLinkedPoint().getConnect1Dir();
349                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
350                    return getLinkedPoint().getWestBoundSignalHead();
351                }
352            }
353        }
354
355        if (signalWestHeadNamed != null) {
356            return signalWestHeadNamed.getBean();
357        }
358        return null;
359    }
360
361    public void setWestBoundSignal(String signalName) {
362        if (getType() == PointType.EDGE_CONNECTOR) {
363            int dir = getConnect1Dir();
364            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
365                setWestBoundSignalName(signalName);
366            } else if (getLinkedPoint() != null) {
367                int linkDir = getLinkedPoint().getConnect1Dir();
368                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
369                    getLinkedPoint().setWestBoundSignal(signalName);
370                } else {
371                    setWestBoundSignalName(signalName);
372                }
373            } else {
374                setWestBoundSignalName(signalName);
375            }
376        } else {
377            setWestBoundSignalName(signalName);
378        }
379    }
380
381    private void setWestBoundSignalName(@CheckForNull String signalHead) {
382        if (signalHead == null || signalHead.isEmpty()) {
383            signalWestHeadNamed = null;
384            return;
385        }
386
387        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
388        if (head != null) {
389            signalWestHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
390        } else {
391            signalWestHeadNamed = null;
392        }
393    }
394
395    @CheckReturnValue
396    @Nonnull
397    public String getEastBoundSensorName() {
398        if (eastBoundSensorNamed != null) {
399            return eastBoundSensorNamed.getName();
400        }
401        return "";
402    }
403
404    @CheckReturnValue
405    public Sensor getEastBoundSensor() {
406        if (eastBoundSensorNamed != null) {
407            return eastBoundSensorNamed.getBean();
408        }
409        return null;
410    }
411
412    public void setEastBoundSensor(String sensorName) {
413        if (sensorName == null || sensorName.isEmpty()) {
414            eastBoundSensorNamed = null;
415            return;
416        }
417
418        try {
419            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
420            eastBoundSensorNamed = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
421        } catch (IllegalArgumentException ex) {
422            eastBoundSensorNamed = null;
423        }
424    }
425
426    @CheckReturnValue
427    @Nonnull
428    public String getWestBoundSensorName() {
429        if (westBoundSensorNamed != null) {
430            return westBoundSensorNamed.getName();
431        }
432        return "";
433    }
434
435    @CheckReturnValue
436    public Sensor getWestBoundSensor() {
437        if (westBoundSensorNamed != null) {
438            return westBoundSensorNamed.getBean();
439        }
440        return null;
441    }
442
443    public void setWestBoundSensor(String sensorName) {
444        if (sensorName == null || sensorName.isEmpty()) {
445            westBoundSensorNamed = null;
446            return;
447        }
448        try {
449            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
450            westBoundSensorNamed = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
451        } catch (IllegalArgumentException ex) {
452            westBoundSensorNamed = null;
453        }
454    }
455
456    @CheckReturnValue
457    @Nonnull
458    public String getEastBoundSignalMastName() {
459        if (getEastBoundSignalMastNamed() != null) {
460            return getEastBoundSignalMastNamed().getName();
461        }
462        return "";
463    }
464
465    @CheckReturnValue
466    public SignalMast getEastBoundSignalMast() {
467        if (getEastBoundSignalMastNamed() != null) {
468            return getEastBoundSignalMastNamed().getBean();
469        }
470        return null;
471    }
472
473    @CheckReturnValue
474    private NamedBeanHandle<SignalMast> getEastBoundSignalMastNamed() {
475        if (getType() == PointType.EDGE_CONNECTOR) {
476            int dir = getConnect1Dir();
477            if (dir == Path.SOUTH || dir == Path.EAST || dir == Path.SOUTH_EAST) {
478                return eastBoundSignalMastNamed;
479            } else if (getLinkedPoint() != null) {
480                int linkDir = getLinkedPoint().getConnect1Dir();
481                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
482                    return getLinkedPoint().getEastBoundSignalMastNamed();
483                }
484            }
485        }
486        return eastBoundSignalMastNamed;
487    }
488
489    public void setEastBoundSignalMast(String signalMast) {
490        SignalMast mast = null;
491        if (signalMast != null && !signalMast.isEmpty()) {
492            mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
493            if (mast == null) {
494                log.error("{}.setEastBoundSignalMast({}); Unable to find Signal Mast",
495                        getName(), signalMast);
496                return;
497            }
498        } else {
499            eastBoundSignalMastNamed = null;
500            return;
501        }
502        if (getType() == PointType.EDGE_CONNECTOR) {
503            int dir = getConnect1Dir();
504            if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) {
505                eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
506            } else if (getLinkedPoint() != null) {
507                int linkDir = getLinkedPoint().getConnect1Dir();
508                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
509                    getLinkedPoint().setEastBoundSignalMast(signalMast);
510                } else {
511                    eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
512                }
513            } else {
514                eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
515            }
516        } else {
517            eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
518        }
519    }
520
521    @CheckReturnValue
522    @Nonnull
523    public String getWestBoundSignalMastName() {
524        if (getWestBoundSignalMastNamed() != null) {
525            return getWestBoundSignalMastNamed().getName();
526        }
527        return "";
528    }
529
530    @CheckReturnValue
531    public SignalMast getWestBoundSignalMast() {
532        if (getWestBoundSignalMastNamed() != null) {
533            return getWestBoundSignalMastNamed().getBean();
534        }
535        return null;
536    }
537
538    @CheckReturnValue
539    private NamedBeanHandle<SignalMast> getWestBoundSignalMastNamed() {
540        if (getType() == PointType.EDGE_CONNECTOR) {
541            int dir = getConnect1Dir();
542            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
543                return westBoundSignalMastNamed;
544            } else if (getLinkedPoint() != null) {
545                int linkDir = getLinkedPoint().getConnect1Dir();
546                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
547                    return getLinkedPoint().getWestBoundSignalMastNamed();
548                }
549            }
550        }
551        return westBoundSignalMastNamed;
552    }
553
554    public void setWestBoundSignalMast(String signalMast) {
555        SignalMast mast = null;
556        if (signalMast != null && !signalMast.isEmpty()) {
557            mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
558            if (mast == null) {
559                log.error("{}.setWestBoundSignalMast({}); Unable to find Signal Mast",
560                        getName(), signalMast);
561                return;
562            }
563        } else {
564            westBoundSignalMastNamed = null;
565            return;
566        }
567        if (getType() == PointType.EDGE_CONNECTOR) {
568            int dir = getConnect1Dir();
569            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
570                westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
571            } else if (getLinkedPoint() != null) {
572                int linkDir = getLinkedPoint().getConnect1Dir();
573                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
574                    getLinkedPoint().setWestBoundSignalMast(signalMast);
575                } else {
576                    westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
577                }
578            } else {
579                westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
580            }
581        } else {
582            westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
583        }
584    }
585
586    public void removeBeanReference(jmri.NamedBean nb) {
587        if (nb == null) {
588            return;
589        }
590        if (nb instanceof SignalMast) {
591            if (nb.equals(getWestBoundSignalMast())) {
592                setWestBoundSignalMast(null);
593            } else if (nb.equals(getEastBoundSignalMast())) {
594                setEastBoundSignalMast(null);
595            }
596        } else if (nb instanceof Sensor) {
597            if (nb.equals(getWestBoundSensor())) {
598                setWestBoundSignalMast(null);
599            } else if (nb.equals(getEastBoundSensor())) {
600                setEastBoundSignalMast(null);
601            }
602        } else if (nb instanceof jmri.SignalHead) {
603            if (nb.equals(getWestBoundSignalHead())) {
604                setWestBoundSignal(null);
605            }
606            if (nb.equals(getEastBoundSignalHead())) {
607                setEastBoundSignal(null);
608            }
609
610        }
611    }
612
613    // initialization instance variables (used when loading a LayoutEditor)
614    public String trackSegment1Name = "";
615    public String trackSegment2Name = "";
616
617    /**
618     * setup a connection to a track
619     *
620     * @param track the track we want to connect to
621     * @return true if successful
622     */
623    public boolean setTrackConnection(@Nonnull TrackSegment track) {
624        return replaceTrackConnection(null, track);
625    }
626
627    /**
628     * remove a connection to a track
629     *
630     * @param track the track we want to disconnect from
631     * @return true if successful
632     */
633    public boolean removeTrackConnection(@Nonnull TrackSegment track) {
634        return replaceTrackConnection(track, null);
635    }
636
637    /**
638     * replace old track connection with new track connection
639     *
640     * @param oldTrack the old track connection
641     * @param newTrack the new track connection
642     * @return true if successful
643     */
644    public boolean replaceTrackConnection(@CheckForNull TrackSegment oldTrack, @CheckForNull TrackSegment newTrack) {
645        boolean result = false; // assume failure (pessimist!)
646        // trying to replace old track with null?
647        if (newTrack == null) {
648            // (yes) remove old connection
649            if (oldTrack != null) {
650                result = true;  // assume success (optimist!)
651                if (getConnect1() == oldTrack) {
652                    positionablePoint.setConnect1(null);        // disconnect getConnect1()
653                    reCheckBlockBoundary();
654                    removeLinkedPoint();
655                    positionablePoint.setConnect1(getConnect2());    // Move getConnect2() to getConnect1()
656                    positionablePoint.setConnect2Actual(null);        // disconnect getConnect2()
657                } else if (getConnect2() == oldTrack) {
658                    positionablePoint.setConnect2Actual(null);
659                    reCheckBlockBoundary();
660                } else {
661                    result = false; // didn't find old connection
662                }
663            } else {
664                result = false; // can't replace null with null
665            }
666            if (!result) {
667                log.error("{}.replaceTrackConnection({}, {}); Attempt to remove non-existant track connection",
668                        getName(), (oldTrack == null) ? "null" : oldTrack.getName(), "null");
669            }
670        } else // already connected to newTrack?
671        if ((getConnect1() != newTrack) && (getConnect2() != newTrack)) {
672            // (no) find a connection we can connect to
673            result = true;  // assume success (optimist!)
674            if (getConnect1() == oldTrack) {
675                positionablePoint.setConnect1(newTrack);
676            } else if ((getType() == PointType.ANCHOR) && (getConnect2() == oldTrack)) {
677                positionablePoint.setConnect2Actual(newTrack);
678                if (getConnect1().getLayoutBlock() == getConnect2().getLayoutBlock()) {
679                    westBoundSignalMastNamed = null;
680                    eastBoundSignalMastNamed = null;
681                    setWestBoundSensor("");
682                    setEastBoundSensor("");
683                }
684            } else {
685                log.error("{}.replaceTrackConnection({}, {}); Attempt to assign more than allowed number of connections",
686                        getName(), (oldTrack == null) ? "null" : oldTrack.getName(), newTrack.getName());
687                result = false;
688            }
689        } else {
690            log.warn("{}.replaceTrackConnection({}, {}); Already connected",
691                    getName(), (oldTrack == null) ? "null" : oldTrack.getName(), newTrack.getName());
692            result = false;
693        }
694        return result;
695    }   // replaceTrackConnection
696
697    void removeSML(SignalMast signalMast) {
698        if (signalMast == null) {
699            return;
700        }
701        if (jmri.InstanceManager.getDefault(LayoutBlockManager.class
702        ).isAdvancedRoutingEnabled() && InstanceManager.getDefault(jmri.SignalMastLogicManager.class
703        ).isSignalMastUsed(signalMast)) {
704            SignallingGuiTools.removeSignalMastLogic(
705                    null, signalMast);
706        }
707    }
708
709    protected int maxWidth() {
710        return 5;
711    }
712
713    protected int maxHeight() {
714        return 5;
715    }
716    // cursor location reference for this move (relative to object)
717    int xClick = 0;
718    int yClick = 0;
719
720    public void mousePressed(MouseEvent e) {
721        // remember where we are
722        xClick = e.getX();
723        yClick = e.getY();
724        // if (debug) log.debug("Pressed: "+where(e));
725        if (e.isPopupTrigger()) {
726            showPopup(e);
727        }
728    }
729
730    public void mouseReleased(MouseEvent e) {
731        // if (debug) log.debug("Release: "+where(e));
732        if (e.isPopupTrigger()) {
733            showPopup(e);
734        }
735    }
736
737    public void mouseClicked(MouseEvent e) {
738        if (e.isPopupTrigger()) {
739            showPopup(e);
740        }
741    }
742
743    private JPopupMenu popup = null;
744
745    /**
746     * {@inheritDoc}
747     */
748    @Override
749    @Nonnull
750    protected JPopupMenu showPopup(@Nonnull MouseEvent mouseEvent) {
751        if (popup != null) {
752            popup.removeAll();
753        } else {
754            popup = new JPopupMenu();
755        }
756
757        boolean blockBoundary = false;
758        boolean addSensorsAndSignalMasksMenuItemsFlag = false;
759        JMenuItem jmi = null;
760        switch (getType()) {
761            case ANCHOR:
762                jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Anchor")) + getName());
763                jmi.setEnabled(false);
764
765                LayoutBlock block1 = null;
766                if (getConnect1() != null) {
767                    block1 = getConnect1().getLayoutBlock();
768                }
769                LayoutBlock block2 = block1;
770                if (getConnect2() != null) {
771                    block2 = getConnect2().getLayoutBlock();
772                }
773                if ((block1 != null) && (block1 == block2)) {
774                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + block1.getDisplayName());
775                } else if ((block1 != null) && (block2 != null) && (block1 != block2)) {
776                    jmi = popup.add(Bundle.getMessage("BlockDivider"));
777                    jmi.setEnabled(false);
778                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", 1)) + block1.getDisplayName());
779                    jmi.setEnabled(false);
780                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", 2)) + block2.getDisplayName());
781                    jmi.setEnabled(false);
782                    blockBoundary = true;
783                }
784                jmi.setEnabled(false);
785                break;
786            case END_BUMPER:
787                jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("EndBumper")) + getName());
788                jmi.setEnabled(false);
789
790                LayoutBlock blockEnd = null;
791                if (getConnect1() != null) {
792                    blockEnd = getConnect1().getLayoutBlock();
793                }
794                if (blockEnd != null) {
795                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BlockID")) + blockEnd.getDisplayName());
796                    jmi.setEnabled(false);
797                }
798                addSensorsAndSignalMasksMenuItemsFlag = true;
799                break;
800            case EDGE_CONNECTOR:
801                jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("EdgeConnector")) + getName());
802                jmi.setEnabled(false);
803
804                if (getLinkedEditor() != null) {
805                    String linkName = getLinkedEditorName() + ":" + getLinkedPointId();
806                    jmi = popup.add(Bundle.getMessage("LinkedToX", linkName));
807                } else {
808                    jmi = popup.add(Bundle.getMessage("EdgeNotLinked"));
809                }
810                jmi.setEnabled(false);
811
812                block1 = null;
813                if (getConnect1() != null) {
814                    block1 = getConnect1().getLayoutBlock();
815                }
816                block2 = block1;
817                if (getLinkedPoint() != null) {
818                    if (getLinkedPoint().getConnect1() != null) {
819                        block2 = getLinkedPoint().getConnect1().getLayoutBlock();
820                    }
821                }
822                if ((block1 != null) && (block1 == block2)) {
823                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + block1.getDisplayName());
824                } else if ((block1 != null) && (block2 != null) && (block1 != block2)) {
825                    jmi = popup.add(Bundle.getMessage("BlockDivider"));
826                    jmi.setEnabled(false);
827                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", 1)) + block1.getDisplayName());
828                    jmi.setEnabled(false);
829                    jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", 2)) + block2.getDisplayName());
830                    jmi.setEnabled(false);
831                    blockBoundary = true;
832                }
833                break;
834            default:
835                break;
836        }
837
838        // if there are any track connections
839        if ((getConnect1() != null) || (getConnect2() != null)) {
840            JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections")); // there is no pane opening (which is what ... implies)
841            if (getConnect1() != null) {
842                connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "1") + getConnect1().getName()) {
843                    @Override
844                    public void actionPerformed(ActionEvent e) {
845                        LayoutEditorFindItems lf = layoutEditor.getFinder();
846                        LayoutTrack lt = lf.findObjectByName(getConnect1().getName());
847                        // this shouldn't ever be null... however...
848                        if (lt != null) {
849                            LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
850                            layoutEditor.setSelectionRect(ltv.getBounds());
851                            ltv.showPopup();
852                        }
853                    }
854                });
855            }
856            if (getConnect2() != null) {
857                connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "2") + getConnect2().getName()) {
858                    @Override
859                    public void actionPerformed(ActionEvent e) {
860                        LayoutEditorFindItems lf = layoutEditor.getFinder();
861                        LayoutTrack lt = lf.findObjectByName(getConnect2().getName());
862                        // this shouldn't ever be null... however...
863                        if (lt != null) {
864                            LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
865                            layoutEditor.setSelectionRect(ltv.getBounds());
866                            ltv.showPopup();
867                        }
868                    }
869                });
870            }
871            popup.add(connectionsMenu);
872        }
873
874        if (getConnect1() != null && (getType() == PointType.EDGE_CONNECTOR || getType() == PointType.END_BUMPER)) {
875            //
876            // decorations menu
877            //
878            popup.add(new JSeparator(JSeparator.HORIZONTAL));
879
880            JMenu decorationsMenu = new JMenu(Bundle.getMessage("DecorationMenuTitle"));
881            decorationsMenu.setToolTipText(Bundle.getMessage("DecorationMenuToolTip"));
882            popup.add(decorationsMenu);
883
884            JCheckBoxMenuItem jcbmi;
885            TrackSegmentView ctv1 = layoutEditor.getTrackSegmentView(getConnect1());
886
887            if (getType() == PointType.EDGE_CONNECTOR) {
888                JMenu arrowsMenu = new JMenu(Bundle.getMessage("ArrowsMenuTitle"));
889                decorationsMenu.setToolTipText(Bundle.getMessage("ArrowsMenuToolTip"));
890                decorationsMenu.add(arrowsMenu);
891
892                JMenu arrowsCountMenu = new JMenu(Bundle.getMessage("DecorationStyleMenuTitle"));
893                arrowsCountMenu.setToolTipText(Bundle.getMessage("DecorationStyleMenuToolTip"));
894                arrowsMenu.add(arrowsCountMenu);
895
896                jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle"));
897                arrowsCountMenu.add(jcbmi);
898                jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip"));
899                jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
900                    if (getConnect1().getConnect1() == positionablePoint) {
901                        ctv1.setArrowEndStart(false);
902                    }
903                    if (getConnect1().getConnect2() == positionablePoint) {
904                        ctv1.setArrowEndStop(false);
905                    }
906                    if (!ctv1.isArrowEndStart() && !ctv1.isArrowEndStop()) {
907                        ctv1.setArrowStyle(0);
908                    }
909                });
910                boolean etherEnd = ((getConnect1().getConnect1() == positionablePoint) && ctv1.isArrowEndStart())
911                        || ((getConnect1().getConnect2() == positionablePoint) && ctv1.isArrowEndStop());
912
913                jcbmi.setSelected((ctv1.getArrowStyle() == 0) || !etherEnd);
914
915                // configure the arrows
916                for (int i = 1; i < NUM_ARROW_TYPES; i++) {
917                    jcbmi = loadArrowImageToJCBItem(i, arrowsCountMenu);
918                    final int n = i;
919                    jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
920                        if (getConnect1().getConnect1() == positionablePoint) {
921                            ctv1.setArrowEndStart(true);
922                        }
923                        if (getConnect1().getConnect2() == positionablePoint) {
924                            ctv1.setArrowEndStop(true);
925                        }
926                        ctv1.setArrowStyle(n);
927                    });
928                    jcbmi.setSelected((ctv1.getArrowStyle() == i) && etherEnd);
929                }
930
931                JMenu arrowsDirMenu = new JMenu(Bundle.getMessage("ArrowsDirectionMenuTitle"));
932                arrowsDirMenu.setToolTipText(Bundle.getMessage("ArrowsDirectionMenuToolTip"));
933                arrowsMenu.add(arrowsDirMenu);
934
935                jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle"));
936                arrowsDirMenu.add(jcbmi);
937                jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip"));
938                jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
939                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
940                    ctv.setArrowDirIn(false);
941                    ctv.setArrowDirOut(false);
942                });
943                jcbmi.setSelected(!ctv1.isArrowDirIn() && !ctv1.isArrowDirOut());
944
945                jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("ArrowsDirectionInMenuItemTitle"));
946                arrowsDirMenu.add(jcbmi);
947                jcbmi.setToolTipText(Bundle.getMessage("ArrowsDirectionInMenuItemToolTip"));
948                jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
949                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
950                    ctv.setArrowDirIn(true);
951                    ctv.setArrowDirOut(false);
952                });
953                jcbmi.setSelected(ctv1.isArrowDirIn() && !ctv1.isArrowDirOut());
954
955                jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("ArrowsDirectionOutMenuItemTitle"));
956                arrowsDirMenu.add(jcbmi);
957                jcbmi.setToolTipText(Bundle.getMessage("ArrowsDirectionOutMenuItemToolTip"));
958                jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
959                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
960                    ctv.setArrowDirOut(true);
961                    ctv.setArrowDirIn(false);
962                });
963                jcbmi.setSelected(!ctv1.isArrowDirIn() && ctv1.isArrowDirOut());
964
965                jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("ArrowsDirectionBothMenuItemTitle"));
966                arrowsDirMenu.add(jcbmi);
967                jcbmi.setToolTipText(Bundle.getMessage("ArrowsDirectionBothMenuItemToolTip"));
968                jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
969                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
970                    ctv.setArrowDirIn(true);
971                    ctv.setArrowDirOut(true);
972                });
973                jcbmi.setSelected(ctv1.isArrowDirIn() && ctv1.isArrowDirOut());
974
975                jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("DecorationColorMenuItemTitle")));
976                jmi.setToolTipText(Bundle.getMessage("DecorationColorMenuItemToolTip"));
977                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
978                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
979                    Color newColor = JmriColorChooser.showDialog(null, "Choose a color", ctv.getArrowColor());
980                    if ((newColor != null) && !newColor.equals(ctv.getArrowColor())) {
981                        ctv.setArrowColor(newColor);
982                    }
983                });
984                jmi.setForeground(ctv1.getArrowColor());
985                jmi.setBackground(ColorUtil.contrast(ctv1.getArrowColor()));
986
987                jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel",
988                        Bundle.getMessage("DecorationLineWidthMenuItemTitle")) + ctv1.getArrowLineWidth()));
989                jmi.setToolTipText(Bundle.getMessage("DecorationLineWidthMenuItemToolTip"));
990                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
991                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
992                    //prompt for arrow line width
993                    int newValue = QuickPromptUtil.promptForInt(layoutEditor,
994                            Bundle.getMessage("DecorationLineWidthMenuItemTitle"),
995                            Bundle.getMessage("DecorationLineWidthMenuItemTitle"),
996                            ctv.getArrowLineWidth());
997                    ctv.setArrowLineWidth(newValue);
998                });
999
1000                jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel",
1001                        Bundle.getMessage("DecorationLengthMenuItemTitle")) + ctv1.getArrowLength()));
1002                jmi.setToolTipText(Bundle.getMessage("DecorationLengthMenuItemToolTip"));
1003                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1004                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1005                    //prompt for arrow length
1006                    int newValue = QuickPromptUtil.promptForInt(layoutEditor,
1007                            Bundle.getMessage("DecorationLengthMenuItemTitle"),
1008                            Bundle.getMessage("DecorationLengthMenuItemTitle"),
1009                            ctv.getArrowLength());
1010                    ctv.setArrowLength(newValue);
1011                });
1012
1013                jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel",
1014                        Bundle.getMessage("DecorationGapMenuItemTitle")) + ctv1.getArrowGap()));
1015                jmi.setToolTipText(Bundle.getMessage("DecorationGapMenuItemToolTip"));
1016                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1017                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1018                    //prompt for arrow gap
1019                    int newValue = QuickPromptUtil.promptForInt(layoutEditor,
1020                            Bundle.getMessage("DecorationGapMenuItemTitle"),
1021                            Bundle.getMessage("DecorationGapMenuItemTitle"),
1022                            ctv.getArrowGap());
1023                    ctv.setArrowGap(newValue);
1024                });
1025            } // if (getType() == EDGE_CONNECTOR)
1026
1027            if (getType() == PointType.END_BUMPER) {
1028                JMenu endBumperMenu = new JMenu(Bundle.getMessage("EndBumperMenuTitle"));
1029                decorationsMenu.setToolTipText(Bundle.getMessage("EndBumperMenuToolTip"));
1030                decorationsMenu.add(endBumperMenu);
1031
1032                JCheckBoxMenuItem enableCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("EndBumperEnableMenuItemTitle"));
1033                enableCheckBoxMenuItem.setToolTipText(Bundle.getMessage("EndBumperEnableMenuItemToolTip"));
1034
1035                endBumperMenu.add(enableCheckBoxMenuItem);
1036                enableCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e3) -> {
1037                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1038                    if (getConnect1().getConnect1() == positionablePoint) {
1039                        ctv.setBumperEndStart(enableCheckBoxMenuItem.isSelected());
1040                    }
1041                    if (getConnect1().getConnect2() == positionablePoint) {
1042                        ctv.setBumperEndStop(enableCheckBoxMenuItem.isSelected());
1043                    }
1044                });
1045                if (getConnect1().getConnect1() == positionablePoint) {
1046                    enableCheckBoxMenuItem.setSelected(ctv1.isBumperEndStart());
1047                }
1048                if (getConnect1().getConnect2() == positionablePoint) {
1049                    enableCheckBoxMenuItem.setSelected(ctv1.isBumperEndStop());
1050                }
1051
1052                jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("DecorationColorMenuItemTitle")));
1053                jmi.setToolTipText(Bundle.getMessage("DecorationColorMenuItemToolTip"));
1054                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1055                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1056                    Color newColor = JmriColorChooser.showDialog(null, "Choose a color", ctv.getBumperColor());
1057                    if ((newColor != null) && !newColor.equals(ctv.getBumperColor())) {
1058                        ctv.setBumperColor(newColor);
1059                    }
1060                });
1061                jmi.setForeground(ctv1.getBumperColor());
1062                jmi.setBackground(ColorUtil.contrast(ctv1.getBumperColor()));
1063
1064                jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel",
1065                        Bundle.getMessage("DecorationLineWidthMenuItemTitle")) + ctv1.getBumperLineWidth()));
1066                jmi.setToolTipText(Bundle.getMessage("DecorationLineWidthMenuItemToolTip"));
1067                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1068                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1069                    //prompt for width
1070                    int newValue = QuickPromptUtil.promptForInteger(layoutEditor,
1071                            Bundle.getMessage("DecorationLineWidthMenuItemTitle"),
1072                            Bundle.getMessage("DecorationLineWidthMenuItemTitle"),
1073                            ctv.getBumperLineWidth(), t -> {
1074                                if (t < 0 || t > TrackSegmentView.MAX_BUMPER_WIDTH) {
1075                                    throw new IllegalArgumentException(
1076                                            Bundle.getMessage("DecorationLengthMenuItemRange", TrackSegmentView.MAX_BUMPER_WIDTH));
1077                                }
1078                                return true;
1079                            });
1080                    ctv.setBumperLineWidth(newValue);
1081                });
1082
1083                jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel",
1084                        Bundle.getMessage("DecorationLengthMenuItemTitle")) + ctv1.getBumperLength()));
1085                jmi.setToolTipText(Bundle.getMessage("DecorationLengthMenuItemToolTip"));
1086                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1087                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1088                    //prompt for length
1089                    int newValue = QuickPromptUtil.promptForInteger(layoutEditor,
1090                            Bundle.getMessage("DecorationLengthMenuItemTitle"),
1091                            Bundle.getMessage("DecorationLengthMenuItemTitle"),
1092                            ctv.getBumperLength(), t -> {
1093                                if (t < 0 || t > TrackSegmentView.MAX_BUMPER_LENGTH) {
1094                                    throw new IllegalArgumentException(
1095                                            Bundle.getMessage("DecorationLengthMenuItemRange", TrackSegmentView.MAX_BUMPER_LENGTH));
1096                                }
1097                                return true;
1098                            });
1099                    ctv.setBumperLength(newValue);
1100                });
1101            }
1102        }   // if ((getType() == EDGE_CONNECTOR) || (getType() == END_BUMPER))
1103
1104        popup.add(new JSeparator(JSeparator.HORIZONTAL));
1105
1106        if (getType() == PointType.ANCHOR) {
1107            if (blockBoundary) {
1108                jmi = popup.add(new JMenuItem(Bundle.getMessage("CanNotMergeAtBlockBoundary")));
1109                jmi.setEnabled(false);
1110            } else if ((getConnect1() != null) && (getConnect2() != null)) {
1111                jmi = popup.add(new AbstractAction(Bundle.getMessage("MergeAdjacentTracks")) {
1112                    @Override
1113                    public void actionPerformed(ActionEvent e) {
1114                        PositionablePoint pp_this = positionablePoint;
1115                        // if I'm fully connected...
1116                        if ((getConnect1() != null) && (getConnect2() != null)) {
1117                            // who is my connection 2 connected to (that's not me)?
1118                            LayoutTrack newConnect2 = null;
1119                            HitPointType newType2 = HitPointType.TRACK;
1120                            if (getConnect2().getConnect1() == pp_this) {
1121                                newConnect2 = getConnect2().getConnect2();
1122                                newType2 = getConnect2().type2;
1123                            } else if (getConnect2().getConnect2() == pp_this) {
1124                                newConnect2 = getConnect2().getConnect1();
1125                                newType2 = getConnect2().type1;
1126                            } else {
1127                                //this should never happen however...
1128                                log.error("Join: wrong getConnect2() error.");
1129                            }
1130
1131                            // connect the other connection to my connection 2 to my connection 1
1132                            if (newConnect2 == null) {
1133                                // (this should NEVER happen... however...)
1134                                log.error("Merge: no 'other' connection to getConnect2().");
1135                            } else {
1136                                if (newConnect2 instanceof PositionablePoint) {
1137                                    PositionablePoint pp = (PositionablePoint) newConnect2;
1138                                    pp.replaceTrackConnection(getConnect2(), getConnect1());
1139                                } else {
1140                                    layoutEditor.setLink(newConnect2, newType2, getConnect1(), HitPointType.TRACK);
1141                                }
1142                                // connect the track at my getConnect1() to the newConnect2
1143                                if (getConnect1().getConnect1() == pp_this) {
1144                                    getConnect1().setNewConnect1(newConnect2, newType2);
1145                                } else if (getConnect1().getConnect2() == pp_this) {
1146                                    getConnect1().setNewConnect2(newConnect2, newType2);
1147                                } else {
1148                                    // (this should NEVER happen... however...)
1149                                    log.error("Merge: no connection to connection 1.");
1150                                }
1151                            }
1152
1153                            // remove connection 2 from selection information
1154                            if (layoutEditor.selectedObject == getConnect2()) {
1155                                layoutEditor.selectedObject = null;
1156                            }
1157                            if (layoutEditor.prevSelectedObject == getConnect2()) {
1158                                layoutEditor.prevSelectedObject = null;
1159                            }
1160
1161                            // remove connection 2 from the layoutEditor's list of layout tracks
1162                            layoutEditor.removeLayoutTrackAndRedraw(getConnect2());
1163
1164                            // update affected block
1165                            LayoutBlock block = getConnect2().getLayoutBlock();
1166                            if (block != null) {
1167                                //decrement Block use count
1168                                block.decrementUse();
1169                                layoutEditor.getLEAuxTools().setBlockConnectivityChanged();
1170                                block.updatePaths();
1171                            }
1172                            getConnect2().remove();
1173                            positionablePoint.setConnect2Actual(null);
1174
1175                            //remove this PositionablePoint from selection information
1176                            if (layoutEditor.selectedObject == pp_this) {
1177                                layoutEditor.selectedObject = null;
1178                            }
1179                            if (layoutEditor.prevSelectedObject == pp_this) {
1180                                layoutEditor.prevSelectedObject = null;
1181                            }
1182
1183                            // remove this PositionablePoint and PositionablePointView from the layoutEditor's list of layout tracks
1184                            layoutEditor.removeLayoutTrackAndRedraw(pp_this);
1185                            pp_this.remove();
1186                            dispose();
1187
1188                            layoutEditor.setDirty();
1189                            layoutEditor.redrawPanel();
1190                        } else {
1191                            // (this should NEVER happen... however...)
1192                            log.error("Merge: missing connection(s).");
1193                        }
1194                    }
1195                });
1196            }
1197        }
1198
1199        popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) {
1200            @Override
1201            public void actionPerformed(ActionEvent e
1202            ) {
1203                if (canRemove() && layoutEditor.removePositionablePoint(positionablePoint)) {
1204                    // user is serious about removing this point from the panel
1205                    remove();
1206                    dispose();
1207                }
1208            }
1209        });
1210
1211        JMenu lineType = new JMenu(Bundle.getMessage("ChangeTo"));
1212        jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("Anchor")) {
1213            @Override
1214            public void actionPerformed(ActionEvent e) {
1215                setTypeAnchor();
1216            }
1217        }));
1218
1219        jmi.setSelected(getType() == PointType.ANCHOR);
1220
1221        // you can't change it to an anchor if it has a 2nd connection
1222        // TODO: add error dialog if you try?
1223        if ((getType() == PointType.EDGE_CONNECTOR) && (getConnect2() != null)) {
1224            jmi.setEnabled(false);
1225        }
1226
1227        jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("EndBumper")) {
1228            @Override
1229            public void actionPerformed(ActionEvent e) {
1230                setTypeEndBumper();
1231            }
1232        }));
1233
1234        jmi.setSelected(getType() == PointType.END_BUMPER);
1235
1236        jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("EdgeConnector")) {
1237            @Override
1238            public void actionPerformed(ActionEvent e) {
1239                setTypeEdgeConnector();
1240            }
1241        }));
1242
1243        jmi.setSelected(getType() == PointType.EDGE_CONNECTOR);
1244
1245        popup.add(lineType);
1246
1247        if (!blockBoundary && getType() == PointType.EDGE_CONNECTOR) {
1248            popup.add(new JSeparator(JSeparator.HORIZONTAL));
1249            popup.add(new AbstractAction(Bundle.getMessage("EdgeEditLink")) {
1250                @Override
1251                public void actionPerformed(ActionEvent e) {
1252                    setLink();
1253                }
1254            });
1255        }
1256
1257        if (blockBoundary) {
1258            popup.add(new JSeparator(JSeparator.HORIZONTAL));
1259            if (getType() == PointType.EDGE_CONNECTOR) {
1260                popup.add(new AbstractAction(Bundle.getMessage("EdgeEditLink")) {
1261                    @Override
1262                    public void actionPerformed(ActionEvent e) {
1263                        setLink();
1264                    }
1265                });
1266                popup.add(new AbstractAction(Bundle.getMessage("SetSignals")) {
1267                    @Override
1268                    public void actionPerformed(ActionEvent e) {
1269                        // bring up signals at edge connector tool dialog
1270                        layoutEditor.getLETools().setSignalsAtBlockBoundaryFromMenu(positionablePoint,
1271                                getLayoutEditorToolBarPanel().signalIconEditor,
1272                                getLayoutEditorToolBarPanel().signalFrame);
1273                    }
1274                });
1275            } else {
1276                AbstractAction ssaa = new AbstractAction(Bundle.getMessage("SetSignals")) {
1277                    @Override
1278                    public void actionPerformed(ActionEvent e) {
1279                        // bring up signals at level crossing tool dialog
1280                        layoutEditor.getLETools().setSignalsAtBlockBoundaryFromMenu(positionablePoint,
1281                                getLayoutEditorToolBarPanel().signalIconEditor,
1282                                getLayoutEditorToolBarPanel().signalFrame);
1283                    }
1284                };
1285
1286                JMenu jm = new JMenu(Bundle.getMessage("SignalHeads"));
1287                if (layoutEditor.getLETools().addBlockBoundarySignalHeadInfoToMenu(positionablePoint, jm)) {
1288                    jm.add(ssaa);
1289                    popup.add(jm);
1290                } else {
1291                    popup.add(ssaa);
1292                }
1293            }
1294            addSensorsAndSignalMasksMenuItemsFlag = true;
1295        }
1296        if (addSensorsAndSignalMasksMenuItemsFlag) {
1297            popup.add(new AbstractAction(Bundle.getMessage("SetSignalMasts")) {
1298                @Override
1299                public void actionPerformed(ActionEvent event) {
1300                    // bring up signals at block boundary tool dialog
1301                    layoutEditor.getLETools().setSignalMastsAtBlockBoundaryFromMenu(positionablePoint);
1302                }
1303            });
1304            popup.add(new AbstractAction(Bundle.getMessage("SetSensors")) {
1305                @Override
1306                public void actionPerformed(ActionEvent event) {
1307                    // bring up signals at block boundary tool dialog
1308                    layoutEditor.getLETools().setSensorsAtBlockBoundaryFromMenu(positionablePoint,
1309                            getLayoutEditorToolBarPanel().sensorIconEditor,
1310                            getLayoutEditorToolBarPanel().sensorFrame);
1311                }
1312            });
1313        }
1314
1315        layoutEditor.setShowAlignmentMenu(popup);
1316
1317        popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
1318
1319        return popup;
1320    }   // showPopup
1321
1322    /**
1323     * {@inheritDoc}
1324     */
1325    @Override
1326    public boolean canRemove() {
1327        List<String> itemList = new ArrayList<>();
1328        // A has two track segments, EB has one, EC has one plus optional link
1329
1330        TrackSegment ts1 = getConnect1();
1331        TrackSegment ts2 = getConnect2();
1332
1333        if (ts1 != null) {
1334            itemList.addAll(getSegmentReferences(ts1));
1335        }
1336        if (ts2 != null) {
1337            for (String item : getSegmentReferences(ts2)) {
1338                // Do not add duplicates
1339                if (!itemList.contains(item)) {
1340                    itemList.add(item);
1341                }
1342            }
1343        }
1344
1345        if (!itemList.isEmpty()) {
1346            String typeName = "";
1347            switch (getType()) {
1348                case ANCHOR:
1349                    typeName = "Anchor";  // NOI18N
1350                    break;
1351                case END_BUMPER:
1352                    typeName = "EndBumper";  // NOI18N
1353                    break;
1354                case EDGE_CONNECTOR:
1355                    typeName = "EdgeConnector";  // NOI18N
1356                    break;
1357                default:
1358                    typeName = "Unknown type (" + getType() + ")";  // NOI18N
1359                    break;
1360            }
1361            displayRemoveWarningDialog(itemList, typeName);
1362        }
1363        return itemList.isEmpty();
1364    }
1365
1366    /**
1367     * Build a list of sensors, signal heads, and signal masts attached to a
1368     * connection point.
1369     *
1370     * @param ts The track segment to be checked.
1371     * @return a list of bean reference names.
1372     */
1373    public ArrayList<String> getSegmentReferences(TrackSegment ts) {
1374        ArrayList<String> items = new ArrayList<>();
1375
1376        HitPointType type1 = ts.getType1();
1377        LayoutTrack conn1 = ts.getConnect1();
1378        items.addAll(ts.getPointReferences(type1, conn1));
1379
1380        HitPointType type2 = ts.getType2();
1381        LayoutTrack conn2 = ts.getConnect2();
1382        items.addAll(ts.getPointReferences(type2, conn2));
1383
1384        return items;
1385    }
1386
1387    /**
1388     * Clean up when this object is no longer needed. Should not be called while
1389     * the object is still displayed; see remove()
1390     */
1391    void dispose() {
1392        if (popup != null) {
1393            popup.removeAll();
1394        }
1395        popup = null;
1396        removeLinkedPoint();
1397    }
1398
1399    /**
1400     * Removes this object from display and persistence
1401     */
1402    private void remove() {
1403        // remove from persistence by flagging inactive
1404        active = false;
1405    }
1406
1407    private boolean active = true;
1408
1409    /**
1410     * @return "active" true means that the object is still displayed, and
1411     *         should be stored.
1412     */
1413    protected boolean isActive() {
1414        return active;
1415    }
1416
1417    protected int getConnect1Dir() {
1418        int result = Path.NONE;
1419
1420        TrackSegment ts1 = getConnect1();
1421        if (ts1 != null) {
1422            Point2D p1;
1423            if (ts1.getConnect1() == positionablePoint) {
1424                p1 = layoutEditor.getCoords(ts1.getConnect2(), ts1.getType2());
1425            } else {
1426                p1 = layoutEditor.getCoords(ts1.getConnect1(), ts1.getType1());
1427            }
1428            result = Path.computeDirection(getCoordsCenter(), p1);
1429        }
1430        return result;
1431    }
1432
1433    JDialog editLink = null;
1434    JComboBox<String> linkPointsBox;
1435    JComboBox<JCBHandle<LayoutEditor>> editorCombo; // Stores with LayoutEditor or "None"
1436
1437    void setLink() {
1438        if (getConnect1() == null || getConnect1().getLayoutBlock() == null) {
1439            log.error("{}.setLink(); Can not set link until we have a connecting track with a block assigned", getName());
1440            return;
1441        }
1442        editLink = new JDialog();
1443        editLink.setTitle(Bundle.getMessage("EdgeEditLinkFrom", getConnect1().getLayoutBlock().getDisplayName()));
1444
1445        JPanel container = new JPanel();
1446        container.setLayout(new BorderLayout());
1447
1448        JButton done = new JButton(Bundle.getMessage("ButtonDone"));
1449        done.addActionListener((ActionEvent a) -> updateLink());
1450
1451        container.add(getLinkPanel(), BorderLayout.NORTH);
1452        container.add(done, BorderLayout.SOUTH);
1453        container.revalidate();
1454
1455        editLink.add(container);
1456
1457        // make this button the default button (return or enter activates)
1458        JRootPane rootPane = SwingUtilities.getRootPane(done);
1459        rootPane.setDefaultButton(done);
1460
1461        editLink.pack();
1462        editLink.setModal(false);
1463        editLink.setVisible(true);
1464    }
1465
1466    private ArrayList<PositionablePoint> pointList;
1467
1468    public JPanel getLinkPanel() {
1469        editorCombo = new JComboBox<>();
1470        Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class)
1471                .getAll(LayoutEditor.class);
1472        editorCombo.addItem(new JCBHandle<>("None"));
1473        //if (panels.contains(layoutEditor)) {
1474        //    panels.remove(layoutEditor);
1475        //}
1476        for (LayoutEditor p : panels) {
1477            JCBHandle<LayoutEditor> h = new JCBHandle<>(p);
1478            editorCombo.addItem(h);
1479            if (p == getLinkedEditor()) {
1480                editorCombo.setSelectedItem(h);
1481            }
1482        }
1483
1484        ActionListener selectPanelListener = (ActionEvent a) -> updatePointBox();
1485
1486        editorCombo.addActionListener(selectPanelListener);
1487        JPanel selectorPanel = new JPanel();
1488        selectorPanel.add(new JLabel(Bundle.getMessage("SelectPanel")));
1489        selectorPanel.add(editorCombo);
1490        linkPointsBox = new JComboBox<>();
1491        updatePointBox();
1492        selectorPanel.add(new JLabel(Bundle.getMessage("ConnectingTo")));
1493        selectorPanel.add(linkPointsBox);
1494        return selectorPanel;
1495    }
1496
1497    void updatePointBox() {
1498        linkPointsBox.removeAllItems();
1499        pointList = new ArrayList<>();
1500        if (editorCombo.getSelectedIndex() == 0) {
1501            linkPointsBox.setEnabled(false);
1502            return;
1503        }
1504
1505        linkPointsBox.setEnabled(true);
1506        LayoutEditor le = editorCombo.getItemAt(editorCombo.getSelectedIndex()).item();
1507        for (PositionablePoint p : le.getPositionablePoints()) {
1508            if (p.getType() == PointType.EDGE_CONNECTOR) {
1509                if (p.getLinkedPoint() == positionablePoint) {
1510                    pointList.add(p);
1511                    linkPointsBox.addItem(p.getName());
1512                    linkPointsBox.setSelectedItem(p.getName());
1513                } else if (p.getLinkedPoint() == null) {
1514                    if (p != positionablePoint) {
1515                    if (p.getConnect1() != null && p.getConnect1().getLayoutBlock() != null) {
1516                        if (p.getConnect1().getLayoutBlock() != getConnect1().getLayoutBlock()) {
1517                            pointList.add(p);
1518                                linkPointsBox.addItem(p.getName());
1519                            }
1520                        }
1521                    }
1522                }
1523            }
1524        }
1525        editLink.pack();
1526    } // updatePointBox
1527
1528    public void updateLink() {
1529        if (editorCombo.getSelectedIndex() == 0 || linkPointsBox.getSelectedIndex() == -1) {
1530            if (getLinkedPoint() != null && getConnect2() != null) {
1531                String removeremote = null;
1532                String removelocal = null;
1533                if (getConnect1Dir() == Path.EAST || getConnect1Dir() == Path.SOUTH) {
1534                    removeremote = getLinkedPoint().getEastBoundSignal();
1535                    removelocal = getWestBoundSignal();
1536                    getLinkedPoint().setEastBoundSignal("");
1537                } else {
1538                    removeremote = getLinkedPoint().getWestBoundSignal();
1539                    removelocal = getEastBoundSignal();
1540                    getLinkedPoint().setWestBoundSignal("");
1541
1542                }
1543                // removelocal and removeremote have been set here.
1544                if (!removeremote.isEmpty()) {
1545                    jmri.SignalHead sh = InstanceManager.getDefault(jmri.SignalHeadManager.class
1546                    ).getSignalHead(removeremote);
1547                    getLinkedEditor().removeSignalHead(sh);
1548                    jmri.jmrit.blockboss.BlockBossLogic.getStoppedObject(removeremote);
1549
1550                }
1551                if (!removelocal.isEmpty()) {
1552                    jmri.SignalHead sh = InstanceManager.getDefault(jmri.SignalHeadManager.class
1553                    ).getSignalHead(removelocal);
1554                    layoutEditor.removeSignalHead(sh);
1555                    jmri.jmrit.blockboss.BlockBossLogic.getStoppedObject(removelocal);
1556                }
1557            }
1558            setLinkedPoint(null);
1559        } else {
1560            setLinkedPoint(pointList.get(linkPointsBox.getSelectedIndex()));
1561        }
1562        editLink.setVisible(false);
1563
1564    }
1565
1566    /**
1567     * {@inheritDoc}
1568     */
1569    @Override
1570    protected HitPointType findHitPointType(Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) {
1571        HitPointType result = HitPointType.NONE;  // assume point not on connection
1572        //note: optimization here: instead of creating rectangles for all the
1573        // points to check below, we create a rectangle for the test point
1574        // and test if the points below are in that rectangle instead.
1575        Rectangle2D r = layoutEditor.layoutEditorControlCircleRectAt(hitPoint);
1576        Point2D p, minPoint = MathUtil.zeroPoint2D;
1577
1578        double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
1579        double distance, minDistance = Float.POSITIVE_INFINITY;
1580
1581        if (!requireUnconnected || (getConnect1() == null)
1582                || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) {
1583            // test point control rectangle
1584            p = getCoordsCenter();
1585            distance = MathUtil.distance(p, hitPoint);
1586            if (distance < minDistance) {
1587                minDistance = distance;
1588                minPoint = p;
1589                result = HitPointType.POS_POINT;
1590            }
1591        }
1592        if ((useRectangles && !r.contains(minPoint))
1593                || (!useRectangles && (minDistance > circleRadius))) {
1594            result = HitPointType.NONE;
1595        }
1596        return result;
1597    }   // findHitPointType
1598
1599    /**
1600     * return the coordinates for a specified connection type
1601     *
1602     * @param connectionType the connection type
1603     * @return the coordinates for the specified connection type
1604     */
1605    @Override
1606    public Point2D getCoordsForConnectionType(HitPointType connectionType) {
1607        Point2D result = getCoordsCenter();
1608        if (connectionType != HitPointType.POS_POINT) {
1609            log.error("{}.getCoordsForConnectionType({}); Invalid Connection Type",
1610                    getName(), connectionType); //I18IN
1611        }
1612        return result;
1613    }
1614
1615    /**
1616     * {@inheritDoc}
1617     */
1618    @Override
1619    public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
1620        LayoutTrack result = null;
1621        if (connectionType == HitPointType.POS_POINT) {
1622            result = getConnect1();
1623            if (null == result) {
1624                result = getConnect2();
1625            }
1626        } else {
1627            String errString = MessageFormat.format("{0}.getConnection({1}); Invalid Connection Type",
1628                    getName(), connectionType); //I18IN
1629            log.error("will throw {}", errString);
1630            throw new jmri.JmriException(errString);
1631        }
1632        return result;
1633    }
1634
1635    /**
1636     * {@inheritDoc}
1637     */
1638    @Override
1639    public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException {
1640        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
1641            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); unexpected type",
1642                    getName(), connectionType, (o == null) ? "null" : o.getName(), type); //I18IN
1643            log.error("will throw {}", errString); //I18IN
1644            throw new jmri.JmriException(errString);
1645        }
1646        if (connectionType != HitPointType.POS_POINT) {
1647            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid Connection Type",
1648                    getName(), connectionType, (o == null) ? "null" : o.getName(), type); //I18IN
1649            log.error("will throw {}", errString); //I18IN
1650            throw new jmri.JmriException(errString);
1651        }
1652    }
1653
1654    /**
1655     * return true if this connection type is disconnected
1656     *
1657     * @param connectionType the connection type to test
1658     * @return true if the connection for this connection type is free
1659     */
1660    @Override
1661    public boolean isDisconnected(HitPointType connectionType) {
1662        boolean result = false;
1663        if (connectionType == HitPointType.POS_POINT) {
1664            result = ((getConnect1() == null) || (getConnect2() == null));
1665        } else {
1666            log.error("{}.isDisconnected({}); Invalid Connection Type",
1667                    getName(), connectionType); //I18IN
1668        }
1669        return result;
1670    }
1671
1672    /**
1673     * Draw track decorations.
1674     *
1675     * This type of track has none, so this method is empty.
1676     */
1677    @Override
1678    protected void drawDecorations(Graphics2D g2) {
1679        log.trace("PositionablePointView::drawDecorations");
1680    }
1681
1682    /**
1683     * {@inheritDoc}
1684     */
1685    @Override
1686    protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) {
1687        //nothing to do here... move along...
1688        log.trace("PositionablePointView::draw1");
1689    }   // draw1
1690
1691    /**
1692     * {@inheritDoc}
1693     */
1694    @Override
1695    protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) {
1696        //nothing to do here... move along...
1697        log.trace("PositionablePointView::draw2");
1698    }
1699
1700    /**
1701     * {@inheritDoc}
1702     */
1703    @Override
1704    protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) {
1705        log.trace("PositionablePointView::highlightUnconnected");
1706        if ((specificType == HitPointType.NONE) || (specificType == HitPointType.POS_POINT)) {
1707            if ((getConnect1() == null)
1708                    || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) {
1709                g2.fill(trackControlCircleAt(getCoordsCenter()));
1710            }
1711        }
1712    }
1713
1714    /**
1715     * {@inheritDoc}
1716     */
1717    @Override
1718    protected void drawEditControls(Graphics2D g2) {
1719        log.trace("PositionablePointView::drawEditControls c1:{} c2:{} {}", getConnect1(), getConnect2(), getType());
1720        TrackSegment ts1 = getConnect1();
1721        if (ts1 == null) {
1722            g2.setColor(Color.red);
1723        } else {
1724            TrackSegment ts2 = null;
1725            if (getType() == PointType.ANCHOR) {
1726                ts2 = getConnect2();
1727            } else if (getType() == PointType.EDGE_CONNECTOR) {
1728                if (getLinkedPoint() != null) {
1729                    ts2 = getLinkedPoint().getConnect1();
1730                }
1731            }
1732            if ((getType() != PointType.END_BUMPER) && (ts2 == null)) {
1733                g2.setColor(Color.yellow);
1734            } else {
1735                g2.setColor(Color.green);
1736            }
1737        }
1738        log.trace("      at {} in {} draw {}",
1739                getCoordsCenter(), g2.getColor(),
1740                layoutEditor.layoutEditorControlRectAt(getCoordsCenter()));
1741
1742        g2.draw(layoutEditor.layoutEditorControlRectAt(getCoordsCenter()));
1743    }   // drawEditControls
1744
1745    /**
1746     * {@inheritDoc}
1747     */
1748    @Override
1749    protected void drawTurnoutControls(Graphics2D g2) {
1750        log.trace("PositionablePointView::drawTurnoutControls");
1751        // PositionablePoints don't have turnout controls...
1752        // nothing to see here... move along...
1753    }
1754
1755    /**
1756     * {@inheritDoc}
1757     */
1758    @Override
1759    public void reCheckBlockBoundary() {
1760        if (getType() == PointType.END_BUMPER) {
1761            return;
1762        }
1763        if (getConnect1() == null && getConnect2() == null) {
1764            //This is no longer a block boundary, therefore will remove signal masts and sensors if present
1765            if (westBoundSignalMastNamed != null) {
1766                removeSML(getWestBoundSignalMast());
1767            }
1768            if (eastBoundSignalMastNamed != null) {
1769                removeSML(getEastBoundSignalMast());
1770            }
1771            westBoundSignalMastNamed = null;
1772            eastBoundSignalMastNamed = null;
1773            setWestBoundSensor("");
1774            setEastBoundSensor("");
1775            //TODO: May want to look at a method to remove the assigned mast
1776            //from the panel and potentially any SignalMast logics generated
1777        } else if (getConnect1() == null || getConnect2() == null) {
1778            //could still be in the process of rebuilding the point details
1779        } else if (getConnect1().getLayoutBlock() == getConnect2().getLayoutBlock()) {
1780            //We are no longer a block bounardy
1781            if (westBoundSignalMastNamed != null) {
1782                removeSML(getWestBoundSignalMast());
1783            }
1784            if (eastBoundSignalMastNamed != null) {
1785                removeSML(getEastBoundSignalMast());
1786            }
1787            westBoundSignalMastNamed = null;
1788            eastBoundSignalMastNamed = null;
1789            setWestBoundSensor("");
1790            setEastBoundSensor("");
1791            //TODO: May want to look at a method to remove the assigned mast
1792            //from the panel and potentially any SignalMast logics generated
1793        }
1794    }   // reCheckBlockBoundary
1795
1796    /**
1797     * {@inheritDoc}
1798     */
1799    @Override
1800    protected List<LayoutConnectivity> getLayoutConnectivity() {
1801        return positionablePoint.getLayoutConnectivity();
1802    }
1803
1804    /**
1805     * {@inheritDoc}
1806     */
1807    @Override
1808    public List<HitPointType> checkForFreeConnections() {
1809        List<HitPointType> result = new ArrayList<>();
1810
1811        if ((getConnect1() == null)
1812                || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) {
1813            result.add(HitPointType.POS_POINT);
1814        }
1815        return result;
1816    }
1817
1818    /**
1819     * {@inheritDoc}
1820     */
1821    @Override
1822    public boolean checkForUnAssignedBlocks() {
1823        // Positionable Points don't have blocks so...
1824        // nothing to see here... move along...
1825        return true;
1826    }
1827
1828    /**
1829     * {@inheritDoc}
1830     */
1831    @Override
1832    public void checkForNonContiguousBlocks(
1833            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
1834        /*
1835        * For each (non-null) blocks of this track do:
1836        * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
1837        * #2) If this track is not in one of the TrackNameSets for this block
1838        * #3) add a new set (with this block/track) to
1839        *     blockNamesToTrackNameSetMap and
1840        * #4) check all the connections in this
1841        *     block (by calling the 2nd method below)
1842        * <p>
1843        *     Basically, we're maintaining contiguous track sets for each block found
1844        *     (in blockNamesToTrackNameSetMap)
1845         */
1846        //check the 1st connection points block
1847        TrackSegment ts1 = getConnect1();
1848        String blk1 = null;
1849        List<Set<String>> TrackNameSets = null;
1850        Set<String> TrackNameSet = null;    // assume not found (pessimist!)
1851
1852        // this should never be null... but just in case...
1853        if (ts1 != null) {
1854            blk1 = ts1.getBlockName();
1855            if (!blk1.isEmpty()) {
1856                TrackNameSets = blockNamesToTrackNameSetsMap.get(blk1);
1857                if (TrackNameSets != null) { // (#1)
1858                    for (Set<String> checkTrackNameSet : TrackNameSets) {
1859                        if (checkTrackNameSet.contains(getName())) { // (#2)
1860                            TrackNameSet = checkTrackNameSet;
1861                            break;
1862                        }
1863                    }
1864                } else {    // (#3)
1865                    log.debug("*New block (''{}'') trackNameSets", blk1);
1866                    TrackNameSets = new ArrayList<>();
1867                    blockNamesToTrackNameSetsMap.put(blk1, TrackNameSets);
1868                }
1869                if (TrackNameSet == null) {
1870                    TrackNameSet = new LinkedHashSet<>();
1871                    log.debug("*    Add track ''{}'' to trackNameSet for block ''{}''", getName(), blk1);
1872                    TrackNameSet.add(getName());
1873                    TrackNameSets.add(TrackNameSet);
1874                }
1875                if (getConnect1() != null) { // (#4)
1876                    getConnect1().collectContiguousTracksNamesInBlockNamed(blk1, TrackNameSet);
1877                }
1878            }
1879        }
1880
1881        if (getType() == PointType.ANCHOR) {
1882            //check the 2nd connection points block
1883            TrackSegment ts2 = getConnect2();
1884            // this should never be null... but just in case...
1885            if (ts2 != null) {
1886                String blk2 = ts2.getBlockName();
1887                if (!blk2.isEmpty()) {
1888                    TrackNameSet = null;    // assume not found (pessimist!)
1889                    TrackNameSets = blockNamesToTrackNameSetsMap.get(blk2);
1890                    if (TrackNameSets != null) { // (#1)
1891                        for (Set<String> checkTrackNameSet : TrackNameSets) {
1892                            if (checkTrackNameSet.contains(getName())) { // (#2)
1893                                TrackNameSet = checkTrackNameSet;
1894                                break;
1895                            }
1896                        }
1897                    } else {    // (#3)
1898                        log.debug("*New block (''{}'') trackNameSets", blk2);
1899                        TrackNameSets = new ArrayList<>();
1900                        blockNamesToTrackNameSetsMap.put(blk2, TrackNameSets);
1901                    }
1902                    if (TrackNameSet == null) {
1903                        TrackNameSet = new LinkedHashSet<>();
1904                        log.debug("*    Add track ''{}'' to TrackNameSet for block ''{}''", getName(), blk2);
1905                        TrackNameSets.add(TrackNameSet);
1906                        TrackNameSet.add(getName());
1907                    }
1908                    if (getConnect2() != null) { // (#4)
1909                        getConnect2().collectContiguousTracksNamesInBlockNamed(blk2, TrackNameSet);
1910                    }
1911                }
1912            }
1913        }
1914    } // collectContiguousTracksNamesInBlockNamed
1915
1916    /**
1917     * {@inheritDoc}
1918     */
1919    @Override
1920    public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName,
1921            @Nonnull Set<String> TrackNameSet) {
1922        if (!TrackNameSet.contains(getName())) {
1923            TrackSegment ts1 = getConnect1();
1924            // this should never be null... but just in case...
1925            if (ts1 != null) {
1926                String blk1 = ts1.getBlockName();
1927                // is this the blockName we're looking for?
1928                if (blk1.equals(blockName)) {
1929                    // if we are added to the TrackNameSet
1930                    if (TrackNameSet.add(getName())) {
1931                        log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
1932                    }
1933                    // this should never be null... but just in case...
1934                    if (getConnect1() != null) {
1935                        getConnect1().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1936                    }
1937                }
1938            }
1939            if (getType() == PointType.ANCHOR) {
1940                TrackSegment ts2 = getConnect2();
1941                // this should never be null... but just in case...
1942                if (ts2 != null) {
1943                    String blk2 = ts2.getBlockName();
1944                    // is this the blockName we're looking for?
1945                    if (blk2.equals(blockName)) {
1946                        // if we are added to the TrackNameSet
1947                        if (TrackNameSet.add(getName())) {
1948                            log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
1949                        }
1950                        // this should never be null... but just in case...
1951                        if (getConnect2() != null) {
1952                            // it's time to play... flood your neighbour!
1953                            getConnect2().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1954                        }
1955                    }
1956                }
1957            }
1958        }
1959    }
1960
1961    /**
1962     * {@inheritDoc}
1963     */
1964    @Override
1965    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
1966        // positionable points don't have blocks...
1967        // nothing to see here, move along...
1968    }
1969
1970    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PositionablePointView.class);
1971}