001package jmri.jmrit.display.layoutEditor;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.awt.geom.*;
006import java.beans.PropertyVetoException;
007import java.util.List;
008import java.util.*;
009
010import javax.annotation.*;
011import javax.swing.*;
012
013import jmri.*;
014import jmri.jmrit.logixng.InlineLogixNG;
015import jmri.jmrit.logixng.LogixNG;
016import jmri.jmrit.logixng.LogixNG_Manager;
017import jmri.jmrit.logixng.tools.swing.DeleteBean;
018import jmri.jmrit.logixng.tools.swing.LogixNGEditor;
019import jmri.util.*;
020import jmri.util.swing.JmriJOptionPane;
021import jmri.util.swing.JmriMouseEvent;
022
023/**
024 * MVC View component abstract base for the LayoutTrack hierarchy.
025 * <p>
026 * This contains the display information, including screen geometry, for a
027 * LayoutEditor panel. The geometry/connectivity information is held in
028 * {@link LayoutTrack} subclasses.
029 * <ul>
030 * <li>Position(s) of the screen icons and its parts, typically the center;
031 * scaling and translation; size and bounds
032 * <li>Line colors
033 * <li>Flipped status; drawing details like bezier curve points
034 * <li>Various decorations: arrows, tunnels, bridges
035 * <li>Hidden status
036 * </ul>
037 *
038 * @author Bob Jacobsen Copyright (c) 2020
039 *
040 */
041abstract public class LayoutTrackView implements InlineLogixNG {
042
043    /**
044     * Constructor method.
045     *
046     * @param track        the layout track to view
047     * @param layoutEditor the panel in which to place the view
048     */
049    public LayoutTrackView(@Nonnull LayoutTrack track, @Nonnull LayoutEditor layoutEditor) {
050        this.layoutTrack = track;
051        this.layoutEditor = layoutEditor;
052    }
053
054    /**
055     * constructor method
056     *
057     * @param track        the track to view
058     * @param c            display location
059     * @param layoutEditor for reference to tools
060     */
061    public LayoutTrackView(@Nonnull LayoutTrack track, @Nonnull Point2D c, @Nonnull LayoutEditor layoutEditor) {
062        this.layoutTrack = track;
063        this.layoutEditor = layoutEditor;
064        this.center = c;
065    }
066
067    final private LayoutTrack layoutTrack;
068
069    final protected LayoutEditor layoutEditor;
070
071    private LogixNG _logixNG;
072    private String _logixNG_SystemName;
073
074    private boolean _inEditInlineLogixNGMode = false;
075    private LogixNGEditor _inlineLogixNGEdit;
076
077    // Accessor Methods
078
079    @Nonnull
080    final public String getId() {  // temporary Id vs name; is one for the View?
081        return layoutTrack.getId();
082    }
083
084    @Nonnull
085    final public String getName() {
086        return layoutTrack.getName();
087    }
088
089    @Nonnull
090    @CheckReturnValue
091    public LayoutEditor getLayoutEditor() {
092        return layoutEditor;
093    }
094
095    final protected void setIdent(@Nonnull String ident) {
096        layoutTrack.setIdent(ident);
097    }
098
099    // temporary accessor?  Or is this a long term thing?
100    // @Nonnull temporary until we gigure out if can be null or not
101    public LayoutTrack getLayoutTrack() {
102        return layoutTrack;
103    }
104
105    /**
106     * Set center coordinates
107     *
108     * @return The center coordinates
109     */
110    public Point2D getCoordsCenter() { // should be final for efficiency, temporary not to allow redirction overrides.
111        return center;
112    }
113
114    /**
115     * Set center coordinates.
116     * <p>
117     * Some subtypes may reimplement this is "center" is a more complicated
118     * idea, i.e. for Bezier curves
119     *
120     * @param p the coordinates to set
121     */
122    public void setCoordsCenter(@Nonnull Point2D p) {  // temporary = want to make protected after migration
123        center = p;
124    }
125
126    private Point2D center = new Point2D.Double(50.0, 50.0);
127
128    /**
129     * @return true if this track segment has decorations
130     */
131    public boolean hasDecorations() {
132        return false;
133    }
134
135    /**
136     * Get current decorations
137     *
138     * @return the decorations
139     */
140    public Map<String, String> getDecorations() {
141        return decorations;
142    }
143
144    /**
145     * Set new decorations
146     *
147     * This is a complete replacement of the decorations, not an appending.
148     *
149     * @param decorations A map from strings ("arrow", "bridge", "bumper",..) to
150     *                    specific value strings ("single", "entry;right", ),
151     *                    perhaps including multiple values separated by
152     *                    semicolons.
153     */
154    public void setDecorations(Map<String, String> decorations) {
155        this.decorations = decorations;
156    }
157    protected Map<String, String> decorations = null;
158
159    /**
160     * convenience method for accessing...
161     *
162     * @return the layout editor's toolbar panel
163     */
164    @Nonnull
165    final public LayoutEditorToolBarPanel getLayoutEditorToolBarPanel() {
166        return layoutEditor.getLayoutEditorToolBarPanel();
167    }
168
169    // these are convenience methods to return circles & rectangle used to draw onscreen
170    //
171    // compute the control point rect at inPoint; use the turnout circle size
172    final public Ellipse2D trackEditControlCircleAt(@Nonnull Point2D inPoint) {
173        return trackControlCircleAt(inPoint);
174    }
175
176    // compute the turnout circle at inPoint (used for drawing)
177    final public Ellipse2D trackControlCircleAt(@Nonnull Point2D inPoint) {
178        return new Ellipse2D.Double(inPoint.getX() - layoutEditor.circleRadius,
179                inPoint.getY() - layoutEditor.circleRadius,
180                layoutEditor.circleDiameter, layoutEditor.circleDiameter);
181    }
182
183    // compute the turnout circle control rect at inPoint
184    final public Rectangle2D trackControlCircleRectAt(@Nonnull Point2D inPoint) {
185        return new Rectangle2D.Double(inPoint.getX() - layoutEditor.circleRadius,
186                inPoint.getY() - layoutEditor.circleRadius,
187                layoutEditor.circleDiameter, layoutEditor.circleDiameter);
188    }
189
190    final protected Color getColorForTrackBlock(
191            @CheckForNull LayoutBlock layoutBlock, boolean forceBlockTrackColor) {
192        Color result = ColorUtil.CLEAR;  // transparent
193        if (layoutBlock != null) {
194            if (forceBlockTrackColor) {
195                result = layoutBlock.getBlockTrackColor();
196            } else {
197                result = layoutBlock.getBlockColor();
198            }
199        }
200        return result;
201    }
202
203    // optional parameter forceTrack = false
204    final protected Color getColorForTrackBlock(@CheckForNull LayoutBlock lb) {
205        return getColorForTrackBlock(lb, false);
206    }
207
208    final protected Color setColorForTrackBlock(Graphics2D g2,
209            @CheckForNull LayoutBlock layoutBlock, boolean forceBlockTrackColor) {
210        Color result = getColorForTrackBlock(layoutBlock, forceBlockTrackColor);
211        g2.setColor(result);
212        return result;
213    }
214
215    // optional parameter forceTrack = false
216    final protected Color setColorForTrackBlock(Graphics2D g2, @CheckForNull LayoutBlock lb) {
217        return setColorForTrackBlock(g2, lb, false);
218    }
219
220    /**
221     * draw one line (Ballast, ties, center or 3rd rail, block lines)
222     *
223     * @param g2      the graphics context
224     * @param isMain  true if drawing mainlines
225     * @param isBlock true if drawing block lines
226     */
227    abstract protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock);
228
229    /**
230     * draw two lines (rails)
231     *
232     * @param g2               the graphics context
233     * @param isMain           true if drawing mainlines
234     * @param railDisplacement the offset from center to draw the lines
235     */
236    abstract protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement);
237
238    /**
239     * draw hidden track
240     *
241     * @param g2 the graphics context
242     */
243    // abstract protected void drawHidden(Graphics2D g2);
244    // note: placeholder until I get this implemented in all sub-classes
245    // TODO: replace with abstract declaration (above)
246    final protected void drawHidden(Graphics2D g2) {
247        // nothing to do here... move along...
248    }
249
250    /**
251     * draw the text for this layout track
252     * @param g
253     * note: currently can't override (final); change this if you need to
254     */
255    final protected void drawLayoutTrackText(Graphics2D g) {
256        // get the center coordinates
257        int x = (int) center.getX(), y = (int) center.getY();
258
259        // get the name of this track
260        String name = getName();
261
262        // get the FontMetrics
263        FontMetrics metrics = g.getFontMetrics(g.getFont());
264
265        // determine the X coordinate for the text
266        x -= metrics.stringWidth(name) / 2;
267
268        // determine the Y coordinate for the text
269        y += metrics.getHeight() / 2;
270
271        // (note we add the ascent, as in java 2d 0 is top of the screen)
272        //y += (int) metrics.getAscent();
273
274        g.drawString(name, x, y);
275    }
276
277    /**
278     * Load a file for a specific arrow ending.
279     *
280     * @param n               The arrow type as a number
281     * @param arrowsCountMenu menu containing the arrows to set visible
282     *                        selection
283     * @return An item for the arrow menu
284     */
285    public JCheckBoxMenuItem loadArrowImageToJCBItem(int n, JMenu arrowsCountMenu) {
286        ImageIcon imageIcon = new ImageIcon(FileUtil.findURL("program:resources/icons/decorations/ArrowStyle" + n + ".png"));
287        JCheckBoxMenuItem jcbmi = new JCheckBoxMenuItem(imageIcon);
288        arrowsCountMenu.add(jcbmi);
289        jcbmi.setToolTipText(Bundle.getMessage("DecorationStyleMenuToolTip"));
290        // can't set selected here because the ActionListener has to be set first
291        return jcbmi;
292    }
293    protected static final int NUM_ARROW_TYPES = 6;
294
295    /**
296     * highlight unconnected connections
297     *
298     * @param g2           the graphics context
299     * @param specificType the specific connection to draw (or NONE for all)
300     */
301    abstract protected void highlightUnconnected(Graphics2D g2, HitPointType specificType);
302
303    // optional parameter specificType = NONE
304    final protected void highlightUnconnected(Graphics2D g2) {
305        highlightUnconnected(g2, HitPointType.NONE);
306    }
307
308    /**
309     * draw the edit controls
310     *
311     * @param g2 the graphics context
312     */
313    abstract protected void drawEditControls(Graphics2D g2);
314
315    /**
316     * Draw the turnout controls
317     *
318     * @param g2 the graphics context
319     */
320    abstract protected void drawTurnoutControls(Graphics2D g2);
321
322    /**
323     * Draw track decorations
324     *
325     * @param g2 the graphics context
326     */
327    abstract protected void drawDecorations(Graphics2D g2);
328
329    /**
330     * Get the hidden state of the track element.
331     *
332     * @return true if hidden; false otherwise
333     */
334    final public boolean isHidden() {
335        return hidden;
336    }
337
338    final public void setHidden(boolean hide) {
339        if (hidden != hide) {
340            hidden = hide;
341            if (layoutEditor != null) {
342                layoutEditor.redrawPanel();
343            }
344        }
345    }
346
347    private boolean hidden = false;
348
349    /*
350    * non-accessor methods
351     */
352    /**
353     * get turnout state string
354     *
355     * @param turnoutState of the turnout
356     * @return the turnout state string
357     */
358    final public String getTurnoutStateString(int turnoutState) {
359        String result = "";
360        if (turnoutState == Turnout.CLOSED) {
361            result = Bundle.getMessage("TurnoutStateClosed");
362        } else if (turnoutState == Turnout.THROWN) {
363            result = Bundle.getMessage("TurnoutStateThrown");
364        } else {
365            result = Bundle.getMessage("BeanStateUnknown");
366        }
367        return result;
368    }
369
370    /**
371     * Check for active block boundaries.
372     * <p>
373     * If any connection point of a layout track object has attached objects,
374     * such as signal masts, signal heads or NX sensors, the layout track object
375     * cannot be deleted.
376     *
377     * @return true if the layout track object can be deleted.
378     */
379    abstract public boolean canRemove();
380
381    /**
382     * Display the attached items that prevent removing the layout track item.
383     *
384     * @param itemList A list of the attached heads, masts and/or sensors.
385     * @param typeKey  The object type such as Turnout, Level Crossing, etc.
386     */
387    final public void displayRemoveWarningDialog(List<String> itemList, String typeKey) {
388        itemList.sort(null);
389        StringBuilder msg = new StringBuilder(Bundle.getMessage("MakeLabel", // NOI18N
390                Bundle.getMessage("DeleteTrackItem", Bundle.getMessage(typeKey))));  // NOI18N
391        for (String item : itemList) {
392            msg.append("\n    " + item);  // NOI18N
393        }
394        JmriJOptionPane.showMessageDialog(layoutEditor,
395                msg.toString(),
396                Bundle.getMessage("WarningTitle"), // NOI18N
397                JmriJOptionPane.WARNING_MESSAGE);
398    }
399
400    /**
401     * scale this LayoutTrack's coordinates by the x and y factors
402     *
403     * @param xFactor the amount to scale X coordinates
404     * @param yFactor the amount to scale Y coordinates
405     */
406    abstract public void scaleCoords(double xFactor, double yFactor);
407
408    /**
409     * translate this LayoutTrack's coordinates by the x and y factors
410     *
411     * @param xFactor the amount to translate X coordinates
412     * @param yFactor the amount to translate Y coordinates
413     */
414    abstract public void translateCoords(double xFactor, double yFactor);
415
416    /**
417     * rotate this LayoutTrack's coordinates by angleDEG's
418     *
419     * @param angleDEG the amount to rotate in degrees
420     */
421    abstract public void rotateCoords(double angleDEG);
422
423    final protected Point2D rotatePoint(@Nonnull Point2D p, double sineRot, double cosineRot) {
424        double cX = center.getX();
425        double cY = center.getY();
426
427        double deltaX = p.getX() - cX;
428        double deltaY = p.getY() - cY;
429
430        double x = cX + cosineRot * deltaX - sineRot * deltaY;
431        double y = cY + sineRot * deltaX + cosineRot * deltaY;
432
433        return new Point2D.Double(x, y);
434    }
435
436    /**
437     * find the hit (location) type for a point
438     *
439     * @param hitPoint           the point
440     * @param useRectangles      whether to use (larger) rectangles or (smaller)
441     *                           circles for hit testing
442     * @param requireUnconnected whether to only return hit types for free
443     *                           connections
444     * @return the location type for the point (or NONE)
445     * @since 7.4.3
446     */
447    abstract protected HitPointType findHitPointType(@Nonnull Point2D hitPoint,
448                                                    boolean useRectangles,
449                                                    boolean requireUnconnected);
450
451    // optional useRectangles & requireUnconnected parameters default to false
452    final protected HitPointType findHitPointType(@Nonnull Point2D p) {
453        return findHitPointType(p, false, false);
454    }
455
456    // optional requireUnconnected parameter defaults to false
457    final protected HitPointType findHitPointType(@Nonnull Point2D p, boolean useRectangles) {
458        return findHitPointType(p, useRectangles, false);
459    }
460
461    /**
462     * return the coordinates for a specified connection type (abstract: should
463     * be overridden by ALL subclasses)
464     *
465     * @param connectionType the connection type
466     * @return the coordinates for the specified connection type
467     */
468    abstract public Point2D getCoordsForConnectionType(HitPointType connectionType);
469
470    /**
471     * @return the bounds of this track
472     */
473    abstract public Rectangle2D getBounds();
474
475    /**
476     * show the popup menu for this layout track
477     *
478     * @param mouseEvent the mouse down event that triggered this popup
479     * @return the popup menu for this layout track
480     */
481    @Nonnull
482    abstract protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent);
483
484    /**
485     * Att items to the popup menu that's common to all implementing classes.
486     * @param mouseEvent  the mouse down event that triggered this popup
487     * @param popup       the popup menu
488     */
489    protected void addCommonPopupItems(@Nonnull JmriMouseEvent mouseEvent, @Nonnull JPopupMenu popup) {
490        popup.addSeparator();
491        setLogixNGPositionableMenu(popup);
492        layoutEditor.addPopupItems(popup, mouseEvent);
493    }
494
495    @Override
496    public String getNameString() {
497        return getName();
498    }
499
500    @Override
501    public String getEditorName() {
502        return getLayoutEditor().getName();
503    }
504
505    @Override
506    public int getX() {
507        return (int) center.getX();
508    }
509
510    @Override
511    public int getY() {
512        return (int) center.getY();
513    }
514
515    @Override
516    public String getTypeName() {
517        return layoutTrack.getTypeName();
518    }
519
520    /**
521     * Check if edit of a conditional is in progress.
522     *
523     * @return true if this is the case, after showing dialog to user
524     */
525    private boolean checkEditConditionalNG() {
526        if (_inEditInlineLogixNGMode) {
527            // Already editing a LogixNG, ask for completion of that edit
528            JmriJOptionPane.showMessageDialog(null,
529                    Bundle.getMessage("Error_InlineLogixNGInEditMode"), // NOI18N
530                    Bundle.getMessage("ErrorTitle"), // NOI18N
531                    JmriJOptionPane.ERROR_MESSAGE);
532            _inlineLogixNGEdit.bringToFront();
533            return true;
534        }
535        return false;
536    }
537
538    /**
539     * Add a menu entry to edit Id of the Positionable item
540     *
541     * @param popup the menu to add the entry to
542     */
543    public void setLogixNGPositionableMenu(JPopupMenu popup) {
544        JMenu logixNG_Menu = new JMenu("LogixNG");
545        popup.add(logixNG_Menu);
546
547        logixNG_Menu.add(new AbstractAction(Bundle.getMessage("LogixNG_Inline")) {
548            @Override
549            public void actionPerformed(ActionEvent e) {
550                if (checkEditConditionalNG()) return;
551
552                if (getLogixNG() == null) {
553                    LogixNG logixNG = InstanceManager.getDefault(LogixNG_Manager.class)
554                            .createLogixNG(null, true);
555                    logixNG.setInlineLogixNG(LayoutTrackView.this);
556                    logixNG.activate();
557                    logixNG.setEnabled(true);
558                    logixNG.clearStartup();
559                    setLogixNG(logixNG);
560                }
561                LogixNGEditor logixNGEditor = new LogixNGEditor(null, getLogixNG().getSystemName());
562                logixNGEditor.addEditorEventListener((HashMap<String, String> data) -> {
563                    _inEditInlineLogixNGMode = false;
564                    data.forEach((key, value) -> {
565                        if (key.equals("Finish")) {                  // NOI18N
566                            _inlineLogixNGEdit = null;
567                            _inEditInlineLogixNGMode = false;
568                        } else if (key.equals("Delete")) {           // NOI18N
569                            _inEditInlineLogixNGMode = false;
570                            deleteLogixNG(getLogixNG());
571                        } else if (key.equals("chgUname")) {         // NOI18N
572                            getLogixNG().setUserName(value);
573                        }
574                    });
575                    if (getLogixNG() != null && getLogixNG().getNumConditionalNGs() == 0) {
576                        deleteLogixNG_Internal(getLogixNG());
577                    }
578                });
579                logixNGEditor.bringToFront();
580                _inEditInlineLogixNGMode = true;
581                _inlineLogixNGEdit = logixNGEditor;
582            }
583        });
584    }
585
586    private void deleteLogixNG(LogixNG logixNG) {
587        DeleteBean<LogixNG> deleteBean = new DeleteBean<>(
588                InstanceManager.getDefault(LogixNG_Manager.class));
589
590        boolean hasChildren = logixNG.getNumConditionalNGs() > 0;
591
592        deleteBean.delete(logixNG, hasChildren, (t)->{deleteLogixNG_Internal(t);},
593                (t,list)->{logixNG.getListenerRefsIncludingChildren(list);},
594                jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName());
595    }
596
597    private void deleteLogixNG_Internal(LogixNG logixNG) {
598        logixNG.setEnabled(false);
599        try {
600            InstanceManager.getDefault(LogixNG_Manager.class).deleteBean(logixNG, "DoDelete");
601            logixNG.getInlineLogixNG().setLogixNG(null);
602        } catch (PropertyVetoException e) {
603            //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
604            log.error("{} : Could not Delete.", e.getMessage());
605        }
606    }
607
608    /**
609     * Get the LogixNG of this Positionable.
610     * @return the LogixNG or null if it has no LogixNG
611     */
612    @Override
613    public LogixNG getLogixNG() {
614        return _logixNG;
615    }
616
617    /**
618     * Set the LogixNG of this Positionable.
619     * @param logixNG the LogixNG or null if remove the LogixNG from the Positionable
620     */
621    @Override
622    public void setLogixNG(LogixNG logixNG) {
623        this._logixNG = logixNG;
624    }
625
626    @Override
627    public void setLogixNG_SystemName(String systemName) {
628        this._logixNG_SystemName = systemName;
629    }
630
631    @Override
632    public void setupLogixNG() {
633        _logixNG = InstanceManager.getDefault(LogixNG_Manager.class)
634                .getBySystemName(_logixNG_SystemName);
635        if (_logixNG == null) {
636            throw new RuntimeException(String.format(
637                    "LogixNG %s is not found for LayoutTrackView %s in panel %s",
638                    _logixNG_SystemName, getName(), layoutEditor.getName()));
639        }
640        _logixNG.setInlineLogixNG(this);
641    }
642
643    /**
644     * show the popup menu for this layout track
645     *
646     * @param where to show the popup
647     * @return the popup menu for this layout track
648     */
649    @Nonnull
650    final protected JPopupMenu showPopup(Point2D where) {
651        return this.showPopup(new JmriMouseEvent(
652                layoutEditor.getTargetPanel(), // source
653                JmriMouseEvent.MOUSE_CLICKED, // id
654                System.currentTimeMillis(), // when
655                0, // modifiers
656                (int) where.getX(), (int) where.getY(), // where
657                0, // click count
658                true));                         // popup trigger
659
660    }
661
662    /**
663     * show the popup menu for this layout track
664     *
665     * @return the popup menu for this layout track
666     */
667    @Nonnull
668    final protected JPopupMenu showPopup() {
669        Point2D where = MathUtil.multiply(getCoordsCenter(),
670                layoutEditor.getZoom());
671        return this.showPopup(where);
672    }
673
674    /**
675     * get the LayoutTrack connected at the specified connection type
676     *
677     * @param connectionType where on us to get the connection
678     * @return the LayoutTrack connected at the specified connection type
679     * @throws JmriException - if the connectionType is invalid
680     */
681    abstract public LayoutTrack getConnection(HitPointType connectionType) throws JmriException;
682
683    /**
684     * set the LayoutTrack connected at the specified connection type
685     *
686     * @param connectionType where on us to set the connection
687     * @param o              the LayoutTrack that is to be connected
688     * @param type           where on the LayoutTrack we are connected
689     * @throws JmriException - if connectionType or type are invalid
690     */
691    abstract public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws JmriException;
692
693    /**
694     * abstract method... subclasses should implement _IF_ they need to recheck
695     * their block boundaries
696     */
697    abstract protected void reCheckBlockBoundary();
698
699    /**
700     * get the layout connectivity for this track
701     *
702     * @return the list of Layout Connectivity objects
703     */
704    abstract protected List<LayoutConnectivity> getLayoutConnectivity();
705
706    /**
707     * return true if this connection type is disconnected
708     *
709     * @param connectionType the connection type to test
710     * @return true if the connection for this connection type is free
711     */
712    public boolean isDisconnected(HitPointType connectionType) {
713        throw new IllegalArgumentException("should have called in Object instead of View (temporary)");
714    }
715
716    /**
717     * return a list of the available connections for this layout track
718     *
719     * @return the list of available connections
720     */
721    // note: used by LayoutEditorChecks.setupCheckUnConnectedTracksMenu()
722    //
723    // This could have just returned a boolean but I thought a list might be
724    // more useful (eventually... not currently being used; we just check to see
725    // if it's not empty.)
726    @Nonnull
727    abstract public List<HitPointType> checkForFreeConnections();
728
729    /**
730     * determine if all the appropriate blocks have been assigned to this track
731     *
732     * @return true if all appropriate blocks have been assigned
733     */
734    // note: used by LayoutEditorChecks.setupCheckUnBlockedTracksMenu()
735    //
736    abstract public boolean checkForUnAssignedBlocks();
737
738    /**
739     * check this track and its neighbors for non-contiguous blocks
740     * <p>
741     * For each (non-null) blocks of this track do: #1) If it's got an entry in
742     * the blockNamesToTrackNameSetMap then #2) If this track is not in one of
743     * the TrackNameSets for this block #3) add a new set (with this
744     * block/track) to blockNamesToTrackNameSetMap and #4) check all the
745     * connections in this block (by calling the 2nd method below)
746     * <p>
747     * Basically, we're maintaining contiguous track sets for each block found
748     * (in blockNamesToTrackNameSetMap)
749     *
750     * @param blockNamesToTrackNameSetMaps hashmap of key:block names to lists
751     *                                     of track name sets for those blocks
752     */
753    // note: used by LayoutEditorChecks.setupCheckNonContiguousBlocksMenu()
754    //
755    abstract public void checkForNonContiguousBlocks(
756            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetMaps);
757
758    /**
759     * recursive routine to check for all contiguous tracks in this blockName
760     *
761     * @param blockName    the block that we're checking for
762     * @param TrackNameSet the set of track names in this block
763     */
764    abstract public void collectContiguousTracksNamesInBlockNamed(
765            @Nonnull String blockName,
766            @Nonnull Set<String> TrackNameSet);
767
768    /**
769     * Assign all the layout blocks in this track
770     *
771     * @param layoutBlock to this layout block (used by the Tools menu's "Assign
772     *                    block to selection" item)
773     */
774    abstract public void setAllLayoutBlocks(LayoutBlock layoutBlock);
775
776    protected boolean removeInlineLogixNG() {
777        LogixNG logixNG = getLogixNG();
778
779        if (logixNG == null) return true;
780
781        DeleteBean<LogixNG> deleteBean = new DeleteBean<>(
782                InstanceManager.getDefault(LogixNG_Manager.class));
783
784        boolean hasChildren = logixNG.getNumConditionalNGs() > 0;
785
786        return deleteBean.delete(logixNG, hasChildren, (t)->{deleteLogixNG_Internal(t);},
787                (t,list)->{logixNG.getListenerRefsIncludingChildren(list);},
788                jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName(),
789                true);
790    }
791
792    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTrackView.class);
793}