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