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