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.JmriMouseEvent;
022
023/**
024 * MVC View component for the PositionablePoint class.
025 *
026 * @author Bob Jacobsen Copyright (c) 2020
027 *
028 * <p>
029 * Arrows and bumpers are visual, presentation aspects handled in the View.
030 */
031public class PositionablePointView extends LayoutTrackView {
032
033    protected NamedBeanHandle<SignalHead> signalEastHeadNamed = null; // signal head for east (south) bound trains
034    protected NamedBeanHandle<SignalHead> signalWestHeadNamed = null; // signal head for west (north) bound trains
035
036    private NamedBeanHandle<SignalMast> eastBoundSignalMastNamed = null;
037    private NamedBeanHandle<SignalMast> westBoundSignalMastNamed = null;
038    /* We use a namedbeanhandle for the sensors, even though we only store the name here,
039    this is so that we can keep up with moves and changes of userNames */
040    private NamedBeanHandle<Sensor> eastBoundSensorNamed = null;
041    private NamedBeanHandle<Sensor> westBoundSensorNamed = null;
042
043    /**
044     * constructor method.
045     *
046     * @param point        the positionable point.
047     * @param c            location to display the positionable point
048     * @param layoutEditor for access to tools
049     */
050    public PositionablePointView(@Nonnull PositionablePoint point,
051            Point2D c,
052            @Nonnull LayoutEditor layoutEditor) {
053        super(point, c, layoutEditor);
054        this.positionablePoint = point;
055    }
056
057    final private PositionablePoint positionablePoint;
058
059    public PositionablePoint getPoint() {
060        return positionablePoint;
061    }
062
063    // this should only be used for debugging...
064    @Override
065    public String toString() {
066        String result = "PositionalablePoint";
067        switch (getType()) {
068            case ANCHOR: {
069                result = "Anchor";
070                break;
071            }
072            case END_BUMPER: {
073                result = "End Bumper";
074                break;
075            }
076            case EDGE_CONNECTOR: {
077                result = "Edge Connector";
078                break;
079            }
080            default: {
081                result = "Unknown type (" + getType() + ")";
082                break;
083            }
084        }
085        return result + " '" + getName() + "'";
086    }
087
088    /**
089     * Accessor methods
090     *
091     * @return Type enum for this Positionable Point
092     */
093    public PointType getType() {
094        return positionablePoint.getType();
095    }
096
097    public void setType(PointType newType) {
098        positionablePoint.setType(newType);
099
100        // (temporary) we keep this echo here until we figure out where arrow info lives
101        if (getType() != newType) {
102            switch (newType) {
103                default:
104                case ANCHOR: {
105                    setTypeAnchor();
106                    break;
107                }
108                case END_BUMPER: {
109                    setTypeEndBumper();
110                    break;
111                }
112                case EDGE_CONNECTOR: {
113                    setTypeEdgeConnector();
114                    break;
115                }
116            }
117            layoutEditor.repaint();
118        }
119    }
120
121    private void setTypeAnchor() {
122        setIdent(layoutEditor.getFinder().uniqueName("A", 1));
123
124        // type = PointType.ANCHOR;
125        positionablePoint.setTypeAnchor();
126
127        if (getConnect1() != null) {
128            TrackSegmentView ctv1 = layoutEditor.getTrackSegmentView(getConnect1());
129            if (getConnect1().getConnect1() == positionablePoint) {
130                ctv1.setArrowEndStart(false);
131                ctv1.setBumperEndStart(false);
132            }
133            if (getConnect1().getConnect2() == positionablePoint) {
134                ctv1.setArrowEndStop(false);
135                ctv1.setBumperEndStop(false);
136            }
137        }
138        if (getConnect2() != null) {
139            TrackSegmentView ctv2 = layoutEditor.getTrackSegmentView(getConnect2());
140            if (getConnect2().getConnect1() == positionablePoint) {
141                ctv2.setArrowEndStart(false);
142                ctv2.setBumperEndStart(false);
143            }
144            if (getConnect2().getConnect2() == positionablePoint) {
145                ctv2.setArrowEndStop(false);
146                ctv2.setBumperEndStop(false);
147            }
148        }
149    }
150
151    private void setTypeEndBumper() {
152        setIdent(layoutEditor.getFinder().uniqueName("EB", 1));
153
154        // type = PointType.END_BUMPER;
155        positionablePoint.setTypeEndBumper();
156
157        if (getConnect1() != null) {
158            TrackSegmentView ctv1 = layoutEditor.getTrackSegmentView(getConnect1());
159            if (getConnect1().getConnect1() == positionablePoint) {
160                ctv1.setArrowEndStart(false);
161                ctv1.setBumperEndStart(true);
162            }
163            if (getConnect1().getConnect2() == positionablePoint) {
164                ctv1.setArrowEndStop(false);
165                ctv1.setBumperEndStop(true);
166            }
167        }
168    }
169
170    private void setTypeEdgeConnector() {
171        setIdent(layoutEditor.getFinder().uniqueName("EC", 1));
172
173        // type = PointType.EDGE_CONNECTOR;
174        positionablePoint.setTypeEdgeConnector();
175
176        if (getConnect1() != null) {
177            TrackSegmentView ctv1 = layoutEditor.getTrackSegmentView(getConnect1());
178            if (getConnect1().getConnect1() == positionablePoint) {
179                ctv1.setBumperEndStart(false);
180            }
181            if (getConnect1().getConnect2() == positionablePoint) {
182                ctv1.setBumperEndStop(false);
183            }
184        }
185    }
186
187    public TrackSegment getConnect1() {
188        return positionablePoint.getConnect1();
189    }
190
191    public TrackSegment getConnect2() {
192        return positionablePoint.getConnect2();
193    }
194
195    public String getLinkedEditorName() {
196        return positionablePoint.getLinkedEditorName();
197    }
198
199    public LayoutEditor getLinkedEditor() {
200        return positionablePoint.getLinkedEditor();
201    }
202
203    public PositionablePoint getLinkedPoint() {
204        return positionablePoint.getLinkedPoint();
205    }
206
207    public void removeLinkedPoint() {
208        positionablePoint.removeLinkedPoint();
209    }
210
211    public String getLinkedPointId() {
212        return positionablePoint.getLinkedPointId();
213    }
214
215    public void setLinkedPoint(PositionablePoint p) {
216        positionablePoint.setLinkedPoint(p);
217    }
218
219    /**
220     * {@inheritDoc}
221     */
222    @Override
223    public void scaleCoords(double xFactor, double yFactor) {
224        Point2D factor = new Point2D.Double(xFactor, yFactor);
225        super.setCoordsCenter(MathUtil.granulize(MathUtil.multiply(getCoordsCenter(), factor), 1.0));
226    }
227
228    /**
229     * {@inheritDoc}
230     */
231    @Override
232    public void translateCoords(double xFactor, double yFactor) {
233        Point2D factor = new Point2D.Double(xFactor, yFactor);
234        super.setCoordsCenter(MathUtil.add(getCoordsCenter(), factor));
235    }
236
237    /**
238     * {@inheritDoc}
239     */
240    @Override
241    public void rotateCoords(double angleDEG) {
242        //can't really rotate a point... so...
243        //nothing to see here... move along...
244    }
245
246    /**
247     * @return the bounds of this positional point
248     */
249    @Override
250    public Rectangle2D getBounds() {
251        Point2D c = getCoordsCenter();
252        //Note: empty bounds don't draw...
253        // so now I'm making them 0.5 bigger in all directions (1 pixel total)
254        return new Rectangle2D.Double(c.getX() - 0.5, c.getY() - 0.5, 1.0, 1.0);
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(JmriMouseEvent 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(JmriMouseEvent e) {
731        // if (debug) log.debug("Release: "+where(e));
732        if (e.isPopupTrigger()) {
733            showPopup(e);
734        }
735    }
736
737    public void mouseClicked(JmriMouseEvent 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 JmriMouseEvent 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                    addSensorsAndSignalMasksMenuItemsFlag = true;
798                }
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) {
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            } else {
1026
1027                JMenu endBumperMenu = new JMenu(Bundle.getMessage("EndBumperMenuTitle"));
1028                decorationsMenu.setToolTipText(Bundle.getMessage("EndBumperMenuToolTip"));
1029                decorationsMenu.add(endBumperMenu);
1030
1031                JCheckBoxMenuItem enableCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("EndBumperEnableMenuItemTitle"));
1032                enableCheckBoxMenuItem.setToolTipText(Bundle.getMessage("EndBumperEnableMenuItemToolTip"));
1033
1034                endBumperMenu.add(enableCheckBoxMenuItem);
1035                enableCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e3) -> {
1036                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1037                    if (getConnect1().getConnect1() == positionablePoint) {
1038                        ctv.setBumperEndStart(enableCheckBoxMenuItem.isSelected());
1039                    }
1040                    if (getConnect1().getConnect2() == positionablePoint) {
1041                        ctv.setBumperEndStop(enableCheckBoxMenuItem.isSelected());
1042                    }
1043                });
1044                if (getConnect1().getConnect1() == positionablePoint) {
1045                    enableCheckBoxMenuItem.setSelected(ctv1.isBumperEndStart());
1046                }
1047                if (getConnect1().getConnect2() == positionablePoint) {
1048                    enableCheckBoxMenuItem.setSelected(ctv1.isBumperEndStop());
1049                }
1050
1051                jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("DecorationColorMenuItemTitle")));
1052                jmi.setToolTipText(Bundle.getMessage("DecorationColorMenuItemToolTip"));
1053                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1054                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1055                    Color newColor = JmriColorChooser.showDialog(null, "Choose a color", ctv.getBumperColor());
1056                    if ((newColor != null) && !newColor.equals(ctv.getBumperColor())) {
1057                        ctv.setBumperColor(newColor);
1058                    }
1059                });
1060                jmi.setForeground(ctv1.getBumperColor());
1061                jmi.setBackground(ColorUtil.contrast(ctv1.getBumperColor()));
1062
1063                jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel",
1064                        Bundle.getMessage("DecorationLineWidthMenuItemTitle")) + ctv1.getBumperLineWidth()));
1065                jmi.setToolTipText(Bundle.getMessage("DecorationLineWidthMenuItemToolTip"));
1066                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1067                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1068                    //prompt for width
1069                    int newValue = QuickPromptUtil.promptForInteger(layoutEditor,
1070                            Bundle.getMessage("DecorationLineWidthMenuItemTitle"),
1071                            Bundle.getMessage("DecorationLineWidthMenuItemTitle"),
1072                            ctv.getBumperLineWidth(), t -> {
1073                        if (t < 0 || t > TrackSegmentView.MAX_BUMPER_WIDTH) {
1074                            throw new IllegalArgumentException(
1075                                    Bundle.getMessage("DecorationLengthMenuItemRange", TrackSegmentView.MAX_BUMPER_WIDTH));
1076                        }
1077                        return true;
1078                    });
1079                    ctv.setBumperLineWidth(newValue);
1080                });
1081
1082                jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel",
1083                        Bundle.getMessage("DecorationLengthMenuItemTitle")) + ctv1.getBumperLength()));
1084                jmi.setToolTipText(Bundle.getMessage("DecorationLengthMenuItemToolTip"));
1085                jmi.addActionListener((java.awt.event.ActionEvent e3) -> {
1086                    TrackSegmentView ctv = layoutEditor.getTrackSegmentView(getConnect1());
1087                    //prompt for length
1088                    int newValue = QuickPromptUtil.promptForInteger(layoutEditor,
1089                            Bundle.getMessage("DecorationLengthMenuItemTitle"),
1090                            Bundle.getMessage("DecorationLengthMenuItemTitle"),
1091                            ctv.getBumperLength(), t -> {
1092                        if (t < 0 || t > TrackSegmentView.MAX_BUMPER_LENGTH) {
1093                            throw new IllegalArgumentException(
1094                                    Bundle.getMessage("DecorationLengthMenuItemRange", TrackSegmentView.MAX_BUMPER_LENGTH));
1095                        }
1096                        return true;
1097                    });
1098                    ctv.setBumperLength(newValue);
1099                });
1100            } // if (getType() == EDGE_CONNECTOR)
1101        }   // if (getConnect1() != null)
1102
1103        popup.add(new JSeparator(JSeparator.HORIZONTAL));
1104
1105        if (getType() == PointType.ANCHOR) {
1106            if (blockBoundary) {
1107                jmi = popup.add(new JMenuItem(Bundle.getMessage("CanNotMergeAtBlockBoundary")));
1108                jmi.setEnabled(false);
1109            } else if ((getConnect1() != null) && (getConnect2() != null)) {
1110                jmi = popup.add(new AbstractAction(Bundle.getMessage("MergeAdjacentTracks")) {
1111                    @Override
1112                    public void actionPerformed(ActionEvent e) {
1113                        PositionablePoint pp_this = positionablePoint;
1114                        // if I'm fully connected...
1115                        if ((getConnect1() != null) && (getConnect2() != null)) {
1116                            // who is my connection 2 connected to (that's not me)?
1117                            LayoutTrack newConnect2 = null;
1118                            HitPointType newType2 = HitPointType.TRACK;
1119                            if (getConnect2().getConnect1() == pp_this) {
1120                                newConnect2 = getConnect2().getConnect2();
1121                                newType2 = getConnect2().type2;
1122                            } else if (getConnect2().getConnect2() == pp_this) {
1123                                newConnect2 = getConnect2().getConnect1();
1124                                newType2 = getConnect2().type1;
1125                            } else {
1126                                //this should never happen however...
1127                                log.error("Join: wrong getConnect2() error.");
1128                            }
1129
1130                            // connect the other connection to my connection 2 to my connection 1
1131                            if (newConnect2 == null) {
1132                                // (this should NEVER happen... however...)
1133                                log.error("Merge: no 'other' connection to getConnect2().");
1134                            } else {
1135                                if (newConnect2 instanceof PositionablePoint) {
1136                                    PositionablePoint pp = (PositionablePoint) newConnect2;
1137                                    pp.replaceTrackConnection(getConnect2(), getConnect1());
1138                                } else {
1139                                    layoutEditor.setLink(newConnect2, newType2, getConnect1(), HitPointType.TRACK);
1140                                }
1141                                // connect the track at my getConnect1() to the newConnect2
1142                                if (getConnect1().getConnect1() == pp_this) {
1143                                    getConnect1().setNewConnect1(newConnect2, newType2);
1144                                } else if (getConnect1().getConnect2() == pp_this) {
1145                                    getConnect1().setNewConnect2(newConnect2, newType2);
1146                                } else {
1147                                    // (this should NEVER happen... however...)
1148                                    log.error("Merge: no connection to connection 1.");
1149                                }
1150                            }
1151
1152                            // remove connection 2 from selection information
1153                            if (layoutEditor.selectedObject == getConnect2()) {
1154                                layoutEditor.selectedObject = null;
1155                            }
1156                            if (layoutEditor.prevSelectedObject == getConnect2()) {
1157                                layoutEditor.prevSelectedObject = null;
1158                            }
1159
1160                            // remove connection 2 from the layoutEditor's list of layout tracks
1161                            layoutEditor.removeLayoutTrackAndRedraw(getConnect2());
1162
1163                            // update affected block
1164                            LayoutBlock block = getConnect2().getLayoutBlock();
1165                            if (block != null) {
1166                                //decrement Block use count
1167                                block.decrementUse();
1168                                layoutEditor.getLEAuxTools().setBlockConnectivityChanged();
1169                                block.updatePaths();
1170                            }
1171                            getConnect2().remove();
1172                            positionablePoint.setConnect2Actual(null);
1173
1174                            //remove this PositionablePoint from selection information
1175                            if (layoutEditor.selectedObject == pp_this) {
1176                                layoutEditor.selectedObject = null;
1177                            }
1178                            if (layoutEditor.prevSelectedObject == pp_this) {
1179                                layoutEditor.prevSelectedObject = null;
1180                            }
1181                            clearPossibleSelection();
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() && removeInlineLogixNG()
1204                        && layoutEditor.removePositionablePoint(positionablePoint)) {
1205                    // user is serious about removing this point from the panel
1206                    clearPossibleSelection();
1207                    remove();
1208                    dispose();
1209                }
1210            }
1211        });
1212
1213        JMenu lineType = new JMenu(Bundle.getMessage("ChangeTo"));
1214        jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("Anchor")) {
1215            @Override
1216            public void actionPerformed(ActionEvent e) {
1217                setTypeAnchor();
1218            }
1219        }));
1220
1221        jmi.setSelected(getType() == PointType.ANCHOR);
1222
1223        // you can't change it to an anchor if it has a 2nd connection
1224        // TODO: add error dialog if you try?
1225        if ((getType() == PointType.EDGE_CONNECTOR) && (getConnect2() != null)) {
1226            jmi.setEnabled(false);
1227        }
1228
1229        jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("EndBumper")) {
1230            @Override
1231            public void actionPerformed(ActionEvent e) {
1232                setTypeEndBumper();
1233            }
1234        }));
1235
1236        jmi.setSelected(getType() == PointType.END_BUMPER);
1237
1238        jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("EdgeConnector")) {
1239            @Override
1240            public void actionPerformed(ActionEvent e) {
1241                setTypeEdgeConnector();
1242            }
1243        }));
1244
1245        jmi.setSelected(getType() == PointType.EDGE_CONNECTOR);
1246
1247        popup.add(lineType);
1248
1249        if (!blockBoundary && getType() == PointType.EDGE_CONNECTOR) {
1250            popup.add(new JSeparator(JSeparator.HORIZONTAL));
1251            popup.add(new AbstractAction(Bundle.getMessage("EdgeEditLink")) {
1252                @Override
1253                public void actionPerformed(ActionEvent e) {
1254                    setLink();
1255                }
1256            });
1257        }
1258
1259        if (blockBoundary) {
1260            popup.add(new JSeparator(JSeparator.HORIZONTAL));
1261            if (getType() == PointType.EDGE_CONNECTOR) {
1262                popup.add(new AbstractAction(Bundle.getMessage("EdgeEditLink")) {
1263                    @Override
1264                    public void actionPerformed(ActionEvent e) {
1265                        setLink();
1266                    }
1267                });
1268                popup.add(new AbstractAction(Bundle.getMessage("SetSignals")) {
1269                    @Override
1270                    public void actionPerformed(ActionEvent e) {
1271                        // bring up signals at edge connector tool dialog
1272                        layoutEditor.getLETools().setSignalsAtBlockBoundaryFromMenu(positionablePoint,
1273                                getLayoutEditorToolBarPanel().signalIconEditor,
1274                                getLayoutEditorToolBarPanel().signalFrame);
1275                    }
1276                });
1277            } else {
1278                AbstractAction ssaa = new AbstractAction(Bundle.getMessage("SetSignals")) {
1279                    @Override
1280                    public void actionPerformed(ActionEvent e) {
1281                        // bring up signals at level crossing tool dialog
1282                        layoutEditor.getLETools().setSignalsAtBlockBoundaryFromMenu(positionablePoint,
1283                                getLayoutEditorToolBarPanel().signalIconEditor,
1284                                getLayoutEditorToolBarPanel().signalFrame);
1285                    }
1286                };
1287
1288                JMenu jm = new JMenu(Bundle.getMessage("SignalHeads"));
1289                if (layoutEditor.getLETools().addBlockBoundarySignalHeadInfoToMenu(positionablePoint, jm)) {
1290                    jm.add(ssaa);
1291                    popup.add(jm);
1292                } else {
1293                    popup.add(ssaa);
1294                }
1295            }
1296            addSensorsAndSignalMasksMenuItemsFlag = true;
1297        }
1298        if (addSensorsAndSignalMasksMenuItemsFlag) {
1299            popup.add(new AbstractAction(Bundle.getMessage("SetSignalMasts")) {
1300                @Override
1301                public void actionPerformed(ActionEvent event) {
1302                    // bring up signals at block boundary tool dialog
1303                    layoutEditor.getLETools().setSignalMastsAtBlockBoundaryFromMenu(positionablePoint);
1304                }
1305            });
1306            popup.add(new AbstractAction(Bundle.getMessage("SetSensors")) {
1307                @Override
1308                public void actionPerformed(ActionEvent event) {
1309                    // bring up signals at block boundary tool dialog
1310                    layoutEditor.getLETools().setSensorsAtBlockBoundaryFromMenu(positionablePoint,
1311                            getLayoutEditorToolBarPanel().sensorIconEditor,
1312                            getLayoutEditorToolBarPanel().sensorFrame);
1313                }
1314            });
1315        }
1316
1317        layoutEditor.setShowAlignmentMenu(popup);
1318
1319        addCommonPopupItems(mouseEvent, popup);
1320
1321        popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
1322
1323        return popup;
1324    }   // showPopup
1325
1326    /**
1327     * If an anchor point is selected via a track segment connection, it will be
1328     * in the track selection list. When the merge or delete finishes, draw can
1329     * no longer find the object resulting in a Java exception.
1330     * <p>
1331     * If the anchor point is in the track selection list, the selection groups
1332     * are cleared.
1333     */
1334    private void clearPossibleSelection() {
1335        if (layoutEditor.getLayoutTrackSelection().contains(positionablePoint)) {
1336            layoutEditor.clearSelectionGroups();
1337        }
1338    }
1339
1340    /**
1341     * {@inheritDoc}
1342     */
1343    @Override
1344    public boolean canRemove() {
1345        List<String> itemList = new ArrayList<>();
1346        // A has two track segments, EB has one, EC has one plus optional link
1347
1348        TrackSegment ts1 = getConnect1();
1349        TrackSegment ts2 = getConnect2();
1350
1351        if (ts1 != null) {
1352            itemList.addAll(getSegmentReferences(ts1));
1353        }
1354        if (ts2 != null) {
1355            for (String item : getSegmentReferences(ts2)) {
1356                // Do not add duplicates
1357                if (!itemList.contains(item)) {
1358                    itemList.add(item);
1359                }
1360            }
1361        }
1362
1363        if (!itemList.isEmpty()) {
1364            String typeName = "";
1365            switch (getType()) {
1366                case ANCHOR:
1367                    typeName = "Anchor";  // NOI18N
1368                    break;
1369                case END_BUMPER:
1370                    typeName = "EndBumper";  // NOI18N
1371                    break;
1372                case EDGE_CONNECTOR:
1373                    typeName = "EdgeConnector";  // NOI18N
1374                    break;
1375                default:
1376                    typeName = "Unknown type (" + getType() + ")";  // NOI18N
1377                    break;
1378            }
1379            displayRemoveWarningDialog(itemList, typeName);
1380        }
1381        return itemList.isEmpty();
1382    }
1383
1384    /**
1385     * Build a list of sensors, signal heads, and signal masts attached to a
1386     * connection point.
1387     *
1388     * @param ts The track segment to be checked.
1389     * @return a list of bean reference names.
1390     */
1391    public ArrayList<String> getSegmentReferences(TrackSegment ts) {
1392        ArrayList<String> items = new ArrayList<>();
1393
1394        HitPointType type1 = ts.getType1();
1395        LayoutTrack conn1 = ts.getConnect1();
1396        items.addAll(ts.getPointReferences(type1, conn1));
1397
1398        HitPointType type2 = ts.getType2();
1399        LayoutTrack conn2 = ts.getConnect2();
1400        items.addAll(ts.getPointReferences(type2, conn2));
1401
1402        return items;
1403    }
1404
1405    /**
1406     * Clean up when this object is no longer needed. Should not be called while
1407     * the object is still displayed; see remove()
1408     */
1409    void dispose() {
1410        if (popup != null) {
1411            popup.removeAll();
1412        }
1413        popup = null;
1414        removeLinkedPoint();
1415    }
1416
1417    /**
1418     * Removes this object from display and persistence
1419     */
1420    private void remove() {
1421        // remove from persistence by flagging inactive
1422        active = false;
1423    }
1424
1425    private boolean active = true;
1426
1427    /**
1428     * @return "active" true means that the object is still displayed, and
1429     *         should be stored.
1430     */
1431    protected boolean isActive() {
1432        return active;
1433    }
1434
1435    protected int getConnect1Dir() {
1436        int result = Path.NONE;
1437
1438        TrackSegment ts1 = getConnect1();
1439        if (ts1 != null) {
1440            Point2D p1;
1441            if (ts1.getConnect1() == positionablePoint) {
1442                p1 = layoutEditor.getCoords(ts1.getConnect2(), ts1.getType2());
1443            } else {
1444                p1 = layoutEditor.getCoords(ts1.getConnect1(), ts1.getType1());
1445            }
1446            result = Path.computeDirection(getCoordsCenter(), p1);
1447        }
1448        return result;
1449    }
1450
1451    JDialog editLink = null;
1452    JComboBox<String> linkPointsBox;
1453    JComboBox<JCBHandle<LayoutEditor>> editorCombo; // Stores with LayoutEditor or "None"
1454
1455    void setLink() {
1456        if (getConnect1() == null || getConnect1().getLayoutBlock() == null) {
1457            log.error("{}.setLink(); Can not set link until we have a connecting track with a block assigned", getName());
1458            return;
1459        }
1460        editLink = new JDialog();
1461        editLink.setTitle(Bundle.getMessage("EdgeEditLinkFrom", getConnect1().getLayoutBlock().getDisplayName()));
1462
1463        JPanel container = new JPanel();
1464        container.setLayout(new BorderLayout());
1465
1466        JButton done = new JButton(Bundle.getMessage("ButtonDone"));
1467        done.addActionListener((ActionEvent a) -> updateLink());
1468
1469        container.add(getLinkPanel(), BorderLayout.NORTH);
1470        container.add(done, BorderLayout.SOUTH);
1471        container.revalidate();
1472
1473        editLink.add(container);
1474
1475        // make this button the default button (return or enter activates)
1476        JRootPane rootPane = SwingUtilities.getRootPane(done);
1477        rootPane.setDefaultButton(done);
1478
1479        editLink.pack();
1480        editLink.setModal(false);
1481        editLink.setVisible(true);
1482    }
1483
1484    private ArrayList<PositionablePoint> pointList;
1485
1486    public JPanel getLinkPanel() {
1487        editorCombo = new JComboBox<>();
1488        Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class)
1489                .getAll(LayoutEditor.class);
1490        editorCombo.addItem(new JCBHandle<>("None"));
1491        //if (panels.contains(layoutEditor)) {
1492        //    panels.remove(layoutEditor);
1493        //}
1494        for (LayoutEditor p : panels) {
1495            JCBHandle<LayoutEditor> h = new JCBHandle<>(p);
1496            editorCombo.addItem(h);
1497            if (p == getLinkedEditor()) {
1498                editorCombo.setSelectedItem(h);
1499            }
1500        }
1501
1502        ActionListener selectPanelListener = (ActionEvent a) -> updatePointBox();
1503
1504        editorCombo.addActionListener(selectPanelListener);
1505        JPanel selectorPanel = new JPanel();
1506        selectorPanel.add(new JLabel(Bundle.getMessage("SelectPanel")));
1507        selectorPanel.add(editorCombo);
1508        linkPointsBox = new JComboBox<>();
1509        updatePointBox();
1510        selectorPanel.add(new JLabel(Bundle.getMessage("ConnectingTo")));
1511        selectorPanel.add(linkPointsBox);
1512        return selectorPanel;
1513    }
1514
1515    void updatePointBox() {
1516        linkPointsBox.removeAllItems();
1517        pointList = new ArrayList<>();
1518        if (editorCombo.getSelectedIndex() == 0) {
1519            linkPointsBox.setEnabled(false);
1520            return;
1521        }
1522
1523        linkPointsBox.setEnabled(true);
1524        LayoutEditor le = editorCombo.getItemAt(editorCombo.getSelectedIndex()).item();
1525        for (PositionablePoint p : le.getPositionablePoints()) {
1526            if (p.getType() == PointType.EDGE_CONNECTOR) {
1527                if (p.getLinkedPoint() == positionablePoint) {
1528                    pointList.add(p);
1529                    linkPointsBox.addItem(p.getName());
1530                    linkPointsBox.setSelectedItem(p.getName());
1531                } else if (p.getLinkedPoint() == null) {
1532                    if (p != positionablePoint) {
1533                        if (p.getConnect1() != null && p.getConnect1().getLayoutBlock() != null) {
1534                            if (p.getConnect1().getLayoutBlock() != getConnect1().getLayoutBlock()) {
1535                                pointList.add(p);
1536                                linkPointsBox.addItem(p.getName());
1537                            }
1538                        }
1539                    }
1540                }
1541            }
1542        }
1543        editLink.pack();
1544    } // updatePointBox
1545
1546    public void updateLink() {
1547        if (editorCombo.getSelectedIndex() == 0 || linkPointsBox.getSelectedIndex() == -1) {
1548            if (getLinkedPoint() != null && getConnect2() != null) {
1549                String removeremote = null;
1550                String removelocal = null;
1551                if (getConnect1Dir() == Path.EAST || getConnect1Dir() == Path.SOUTH) {
1552                    removeremote = getLinkedPoint().getEastBoundSignal();
1553                    removelocal = getWestBoundSignal();
1554                    getLinkedPoint().setEastBoundSignal("");
1555                } else {
1556                    removeremote = getLinkedPoint().getWestBoundSignal();
1557                    removelocal = getEastBoundSignal();
1558                    getLinkedPoint().setWestBoundSignal("");
1559
1560                }
1561                // removelocal and removeremote have been set here.
1562                if (!removeremote.isEmpty()) {
1563                    jmri.SignalHead sh = InstanceManager.getDefault(jmri.SignalHeadManager.class
1564                    ).getSignalHead(removeremote);
1565                    getLinkedEditor().removeSignalHead(sh);
1566                    jmri.jmrit.blockboss.BlockBossLogic.getStoppedObject(removeremote);
1567
1568                }
1569                if (!removelocal.isEmpty()) {
1570                    jmri.SignalHead sh = InstanceManager.getDefault(jmri.SignalHeadManager.class
1571                    ).getSignalHead(removelocal);
1572                    layoutEditor.removeSignalHead(sh);
1573                    jmri.jmrit.blockboss.BlockBossLogic.getStoppedObject(removelocal);
1574                }
1575            }
1576            setLinkedPoint(null);
1577        } else {
1578            setLinkedPoint(pointList.get(linkPointsBox.getSelectedIndex()));
1579        }
1580        editLink.setVisible(false);
1581
1582    }
1583
1584    /**
1585     * {@inheritDoc}
1586     */
1587    @Override
1588    protected HitPointType findHitPointType(Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) {
1589        HitPointType result = HitPointType.NONE;  // assume point not on connection
1590        //note: optimization here: instead of creating rectangles for all the
1591        // points to check below, we create a rectangle for the test point
1592        // and test if the points below are in that rectangle instead.
1593        Rectangle2D r = layoutEditor.layoutEditorControlCircleRectAt(hitPoint);
1594        Point2D p, minPoint = MathUtil.zeroPoint2D;
1595
1596        double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
1597        double distance, minDistance = Float.POSITIVE_INFINITY;
1598
1599        if (!requireUnconnected || (getConnect1() == null)
1600                || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) {
1601            // test point control rectangle
1602            p = getCoordsCenter();
1603            distance = MathUtil.distance(p, hitPoint);
1604            if (distance < minDistance) {
1605                minDistance = distance;
1606                minPoint = p;
1607                result = HitPointType.POS_POINT;
1608            }
1609        }
1610        if ((useRectangles && !r.contains(minPoint))
1611                || (!useRectangles && (minDistance > circleRadius))) {
1612            result = HitPointType.NONE;
1613        }
1614        return result;
1615    }   // findHitPointType
1616
1617    /**
1618     * return the coordinates for a specified connection type
1619     *
1620     * @param connectionType the connection type
1621     * @return the coordinates for the specified connection type
1622     */
1623    @Override
1624    public Point2D getCoordsForConnectionType(HitPointType connectionType) {
1625        Point2D result = getCoordsCenter();
1626        if (connectionType != HitPointType.POS_POINT) {
1627            log.error("{}.getCoordsForConnectionType({}); Invalid Connection Type",
1628                    getName(), connectionType); //I18IN
1629        }
1630        return result;
1631    }
1632
1633    /**
1634     * {@inheritDoc}
1635     */
1636    @Override
1637    public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
1638        LayoutTrack result = null;
1639        if (connectionType == HitPointType.POS_POINT) {
1640            result = getConnect1();
1641            if (null == result) {
1642                result = getConnect2();
1643            }
1644        } else {
1645            String errString = MessageFormat.format("{0}.getConnection({1}); Invalid Connection Type",
1646                    getName(), connectionType); //I18IN
1647            log.error("will throw {}", errString);
1648            throw new jmri.JmriException(errString);
1649        }
1650        return result;
1651    }
1652
1653    /**
1654     * {@inheritDoc}
1655     */
1656    @Override
1657    public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException {
1658        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
1659            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); unexpected type",
1660                    getName(), connectionType, (o == null) ? "null" : o.getName(), type); //I18IN
1661            log.error("will throw {}", errString); //I18IN
1662            throw new jmri.JmriException(errString);
1663        }
1664        if (connectionType != HitPointType.POS_POINT) {
1665            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid Connection 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    }
1671
1672    /**
1673     * return true if this connection type is disconnected
1674     *
1675     * @param connectionType the connection type to test
1676     * @return true if the connection for this connection type is free
1677     */
1678    @Override
1679    public boolean isDisconnected(HitPointType connectionType) {
1680        boolean result = false;
1681        if (connectionType == HitPointType.POS_POINT) {
1682            result = ((getConnect1() == null) || (getConnect2() == null));
1683        } else {
1684            log.error("{}.isDisconnected({}); Invalid Connection Type",
1685                    getName(), connectionType); //I18IN
1686        }
1687        return result;
1688    }
1689
1690    /**
1691     * Draw track decorations.
1692     * <p>
1693     * This type of track has none, so this method is empty.
1694     */
1695    @Override
1696    protected void drawDecorations(Graphics2D g2) {
1697        log.trace("PositionablePointView::drawDecorations");
1698    }
1699
1700    /**
1701     * {@inheritDoc}
1702     */
1703    @Override
1704    protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) {
1705        //nothing to do here... move along...
1706        log.trace("PositionablePointView::draw1");
1707    }   // draw1
1708
1709    /**
1710     * {@inheritDoc}
1711     */
1712    @Override
1713    protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) {
1714        //nothing to do here... move along...
1715        log.trace("PositionablePointView::draw2");
1716    }
1717
1718    /**
1719     * {@inheritDoc}
1720     */
1721    @Override
1722    protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) {
1723        log.trace("PositionablePointView::highlightUnconnected");
1724        if ((specificType == HitPointType.NONE) || (specificType == HitPointType.POS_POINT)) {
1725            if ((getConnect1() == null)
1726                    || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) {
1727                g2.fill(trackControlCircleAt(getCoordsCenter()));
1728            }
1729        }
1730    }
1731
1732    /**
1733     * {@inheritDoc}
1734     */
1735    @Override
1736    protected void drawEditControls(Graphics2D g2) {
1737        log.trace("PositionablePointView::drawEditControls c1:{} c2:{} {}", getConnect1(), getConnect2(), getType());
1738        TrackSegment ts1 = getConnect1();
1739        if (ts1 == null) {
1740            g2.setColor(Color.red);
1741        } else {
1742            TrackSegment ts2 = null;
1743            if (getType() == PointType.ANCHOR) {
1744                ts2 = getConnect2();
1745            } else if (getType() == PointType.EDGE_CONNECTOR) {
1746                if (getLinkedPoint() != null) {
1747                    ts2 = getLinkedPoint().getConnect1();
1748                }
1749            }
1750            if ((getType() != PointType.END_BUMPER) && (ts2 == null)) {
1751                g2.setColor(Color.yellow);
1752            } else {
1753                g2.setColor(Color.green);
1754            }
1755        }
1756        log.trace("      at {} in {} draw {}",
1757                getCoordsCenter(), g2.getColor(),
1758                layoutEditor.layoutEditorControlRectAt(getCoordsCenter()));
1759
1760        g2.draw(layoutEditor.layoutEditorControlRectAt(getCoordsCenter()));
1761    }   // drawEditControls
1762
1763    /**
1764     * {@inheritDoc}
1765     */
1766    @Override
1767    protected void drawTurnoutControls(Graphics2D g2) {
1768        log.trace("PositionablePointView::drawTurnoutControls");
1769        // PositionablePoints don't have turnout controls...
1770        // nothing to see here... move along...
1771    }
1772
1773    /**
1774     * {@inheritDoc}
1775     */
1776    @Override
1777    public void reCheckBlockBoundary() {
1778        if (getType() == PointType.END_BUMPER) {
1779            return;
1780        }
1781        if (getConnect1() == null && getConnect2() == null) {
1782            //This is no longer a block boundary, therefore will remove signal masts and sensors if present
1783            if (westBoundSignalMastNamed != null) {
1784                removeSML(getWestBoundSignalMast());
1785            }
1786            if (eastBoundSignalMastNamed != null) {
1787                removeSML(getEastBoundSignalMast());
1788            }
1789            westBoundSignalMastNamed = null;
1790            eastBoundSignalMastNamed = null;
1791            setWestBoundSensor("");
1792            setEastBoundSensor("");
1793            //TODO: May want to look at a method to remove the assigned mast
1794            //from the panel and potentially any SignalMast logics generated
1795        } else if (getConnect1() == null || getConnect2() == null) {
1796            //could still be in the process of rebuilding the point details
1797        } else if (getConnect1().getLayoutBlock() == getConnect2().getLayoutBlock()) {
1798            //We are no longer a block bounardy
1799            if (westBoundSignalMastNamed != null) {
1800                removeSML(getWestBoundSignalMast());
1801            }
1802            if (eastBoundSignalMastNamed != null) {
1803                removeSML(getEastBoundSignalMast());
1804            }
1805            westBoundSignalMastNamed = null;
1806            eastBoundSignalMastNamed = null;
1807            setWestBoundSensor("");
1808            setEastBoundSensor("");
1809            //TODO: May want to look at a method to remove the assigned mast
1810            //from the panel and potentially any SignalMast logics generated
1811        }
1812    }   // reCheckBlockBoundary
1813
1814    /**
1815     * {@inheritDoc}
1816     */
1817    @Override
1818    protected List<LayoutConnectivity> getLayoutConnectivity() {
1819        return positionablePoint.getLayoutConnectivity();
1820    }
1821
1822    /**
1823     * {@inheritDoc}
1824     */
1825    @Override
1826    public List<HitPointType> checkForFreeConnections() {
1827        List<HitPointType> result = new ArrayList<>();
1828
1829        if ((getConnect1() == null)
1830                || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) {
1831            result.add(HitPointType.POS_POINT);
1832        }
1833        return result;
1834    }
1835
1836    /**
1837     * {@inheritDoc}
1838     */
1839    @Override
1840    public boolean checkForUnAssignedBlocks() {
1841        // Positionable Points don't have blocks so...
1842        // nothing to see here... move along...
1843        return true;
1844    }
1845
1846    /**
1847     * {@inheritDoc}
1848     */
1849    @Override
1850    public void checkForNonContiguousBlocks(
1851            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
1852        /*
1853        * For each (non-null) blocks of this track do:
1854        * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
1855        * #2) If this track is not in one of the TrackNameSets for this block
1856        * #3) add a new set (with this block/track) to
1857        *     blockNamesToTrackNameSetMap and
1858        * #4) check all the connections in this
1859        *     block (by calling the 2nd method below)
1860        * <p>
1861        *     Basically, we're maintaining contiguous track sets for each block found
1862        *     (in blockNamesToTrackNameSetMap)
1863         */
1864        //check the 1st connection points block
1865        TrackSegment ts1 = getConnect1();
1866        String blk1 = null;
1867        List<Set<String>> TrackNameSets = null;
1868        Set<String> TrackNameSet = null;    // assume not found (pessimist!)
1869
1870        // this should never be null... but just in case...
1871        if (ts1 != null) {
1872            blk1 = ts1.getBlockName();
1873            if (!blk1.isEmpty()) {
1874                TrackNameSets = blockNamesToTrackNameSetsMap.get(blk1);
1875                if (TrackNameSets != null) { // (#1)
1876                    for (Set<String> checkTrackNameSet : TrackNameSets) {
1877                        if (checkTrackNameSet.contains(getName())) { // (#2)
1878                            TrackNameSet = checkTrackNameSet;
1879                            break;
1880                        }
1881                    }
1882                } else {    // (#3)
1883                    log.debug("*New block (''{}'') trackNameSets", blk1);
1884                    TrackNameSets = new ArrayList<>();
1885                    blockNamesToTrackNameSetsMap.put(blk1, TrackNameSets);
1886                }
1887                if (TrackNameSet == null) {
1888                    TrackNameSet = new LinkedHashSet<>();
1889                    log.debug("*    Add track ''{}'' to trackNameSet for block ''{}''", getName(), blk1);
1890                    TrackNameSet.add(getName());
1891                    TrackNameSets.add(TrackNameSet);
1892                }
1893                if (getConnect1() != null) { // (#4)
1894                    getConnect1().collectContiguousTracksNamesInBlockNamed(blk1, TrackNameSet);
1895                }
1896            }
1897        }
1898
1899        if (getType() == PointType.ANCHOR) {
1900            //check the 2nd connection points block
1901            TrackSegment ts2 = getConnect2();
1902            // this should never be null... but just in case...
1903            if (ts2 != null) {
1904                String blk2 = ts2.getBlockName();
1905                if (!blk2.isEmpty()) {
1906                    TrackNameSet = null;    // assume not found (pessimist!)
1907                    TrackNameSets = blockNamesToTrackNameSetsMap.get(blk2);
1908                    if (TrackNameSets != null) { // (#1)
1909                        for (Set<String> checkTrackNameSet : TrackNameSets) {
1910                            if (checkTrackNameSet.contains(getName())) { // (#2)
1911                                TrackNameSet = checkTrackNameSet;
1912                                break;
1913                            }
1914                        }
1915                    } else {    // (#3)
1916                        log.debug("*New block (''{}'') trackNameSets", blk2);
1917                        TrackNameSets = new ArrayList<>();
1918                        blockNamesToTrackNameSetsMap.put(blk2, TrackNameSets);
1919                    }
1920                    if (TrackNameSet == null) {
1921                        TrackNameSet = new LinkedHashSet<>();
1922                        log.debug("*    Add track ''{}'' to TrackNameSet for block ''{}''", getName(), blk2);
1923                        TrackNameSets.add(TrackNameSet);
1924                        TrackNameSet.add(getName());
1925                    }
1926                    if (getConnect2() != null) { // (#4)
1927                        getConnect2().collectContiguousTracksNamesInBlockNamed(blk2, TrackNameSet);
1928                    }
1929                }
1930            }
1931        }
1932    } // collectContiguousTracksNamesInBlockNamed
1933
1934    /**
1935     * {@inheritDoc}
1936     */
1937    @Override
1938    public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName,
1939            @Nonnull Set<String> TrackNameSet) {
1940        if (!TrackNameSet.contains(getName())) {
1941            TrackSegment ts1 = getConnect1();
1942            // this should never be null... but just in case...
1943            if (ts1 != null) {
1944                String blk1 = ts1.getBlockName();
1945                // is this the blockName we're looking for?
1946                if (blk1.equals(blockName)) {
1947                    // if we are added to the TrackNameSet
1948                    if (TrackNameSet.add(getName())) {
1949                        log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
1950                    }
1951                    // this should never be null... but just in case...
1952                    if (getConnect1() != null) {
1953                        getConnect1().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1954                    }
1955                }
1956            }
1957            if (getType() == PointType.ANCHOR) {
1958                TrackSegment ts2 = getConnect2();
1959                // this should never be null... but just in case...
1960                if (ts2 != null) {
1961                    String blk2 = ts2.getBlockName();
1962                    // is this the blockName we're looking for?
1963                    if (blk2.equals(blockName)) {
1964                        // if we are added to the TrackNameSet
1965                        if (TrackNameSet.add(getName())) {
1966                            log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
1967                        }
1968                        // this should never be null... but just in case...
1969                        if (getConnect2() != null) {
1970                            // it's time to play... flood your neighbour!
1971                            getConnect2().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1972                        }
1973                    }
1974                }
1975            }
1976        }
1977    }
1978
1979    /**
1980     * {@inheritDoc}
1981     */
1982    @Override
1983    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
1984        // positionable points don't have blocks...
1985        // nothing to see here, move along...
1986    }
1987
1988    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PositionablePointView.class);
1989}