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