001package jmri.jmrit.display;
002
003import java.awt.*;
004import java.awt.datatransfer.DataFlavor;
005import java.awt.event.*;
006import java.awt.geom.Rectangle2D;
007import java.beans.PropertyChangeEvent;
008import java.beans.PropertyVetoException;
009import java.beans.VetoableChangeListener;
010import java.lang.reflect.InvocationTargetException;
011import java.text.MessageFormat;
012import java.util.*;
013import java.util.List;
014
015import javax.annotation.Nonnull;
016import javax.swing.*;
017import javax.swing.Timer;
018import javax.swing.border.Border;
019import javax.swing.border.CompoundBorder;
020import javax.swing.border.LineBorder;
021import javax.swing.event.ListSelectionEvent;
022
023import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
024
025import javax.annotation.CheckForNull;
026
027import jmri.*;
028import jmri.jmrit.catalog.CatalogPanel;
029import jmri.jmrit.catalog.DirectorySearcher;
030import jmri.jmrit.catalog.ImageIndexEditor;
031import jmri.jmrit.catalog.NamedIcon;
032import jmri.jmrit.display.controlPanelEditor.shape.PositionableShape;
033import jmri.jmrit.logixng.*;
034import jmri.jmrit.logixng.tools.swing.DeleteBean;
035import jmri.jmrit.logixng.tools.swing.LogixNGEditor;
036import jmri.jmrit.operations.trains.TrainIcon;
037import jmri.jmrit.picker.PickListModel;
038import jmri.jmrit.roster.Roster;
039import jmri.jmrit.roster.RosterEntry;
040import jmri.jmrit.roster.swing.RosterEntrySelectorPanel;
041import jmri.util.DnDStringImportHandler;
042import jmri.util.JmriJFrame;
043import jmri.util.ThreadingUtil;
044import jmri.util.swing.JmriColorChooser;
045import jmri.util.swing.JmriJOptionPane;
046import jmri.util.swing.JmriMouseEvent;
047import jmri.util.swing.JmriMouseListener;
048import jmri.util.swing.JmriMouseMotionListener;
049
050/**
051 * This is the Model and a Controller for panel editor Views. (Panel Editor,
052 * Layout Editor or any subsequent editors) The Model is simply a list of
053 * Positionable objects added to a "target panel". Control of the display
054 * attributes of the Positionable objects is done here. However, control of
055 * mouse events is passed to the editor views, so control is also done by the
056 * editor views.
057 * <p>
058 * The "contents" List keeps track of all the objects added to the target frame
059 * for later manipulation. This class only locates and moves "target panel"
060 * items, and does not control their appearance - that is left for the editor
061 * views.
062 * <p>
063 * The Editor has tri-state "flags" to control the display of Positionable
064 * object attributes globally - i.e. "on" or "off" for all - or as a third
065 * state, permits the display control "locally" by corresponding flags in each
066 * Positionable object
067 * <p>
068 * The title of the target and the editor panel are kept consistent via the
069 * {#setTitle} method.
070 * <p>
071 * Mouse events are initial handled here, rather than in the individual
072 * displayed objects, so that selection boxes for moving multiple objects can be
073 * provided.
074 * <p>
075 * This class also implements an effective ToolTipManager replacement, because
076 * the standard Swing one can't deal with the coordinate changes used to zoom a
077 * panel. It works by controlling the contents of the _tooltip instance
078 * variable, and triggering repaint of the target window when the tooltip
079 * changes. The window painting then explicitly draws the tooltip for the
080 * underlying object.
081 *
082 * @author Bob Jacobsen Copyright: Copyright (c) 2002, 2003, 2007
083 * @author Dennis Miller 2004
084 * @author Howard G. Penny Copyright: Copyright (c) 2005
085 * @author Matthew Harris Copyright: Copyright (c) 2009
086 * @author Pete Cressman Copyright: Copyright (c) 2009, 2010, 2011
087 *
088 */
089public abstract class Editor extends JmriJFrameWithPermissions
090        implements JmriMouseListener, JmriMouseMotionListener, ActionListener,
091                KeyListener, VetoableChangeListener {
092
093    public static final int BKG = 1;
094    public static final int TEMP = 2;
095    public static final int ICONS = 3;
096    public static final int LABELS = 4;
097    public static final int MEMORIES = 5;
098    public static final int REPORTERS = 5;
099    public static final int SECURITY = 6;
100    public static final int TURNOUTS = 7;
101    public static final int LIGHTS = 8;
102    public static final int SIGNALS = 9;
103    public static final int SENSORS = 10;
104    public static final int CLOCK = 10;
105    public static final int MARKERS = 10;
106    public static final int NUM_LEVELS = 10;
107
108    public static final int SCROLL_NONE = 0;
109    public static final int SCROLL_BOTH = 1;
110    public static final int SCROLL_HORIZONTAL = 2;
111    public static final int SCROLL_VERTICAL = 3;
112
113    public static final Color HIGHLIGHT_COLOR = new Color(204, 207, 88);
114
115    public static final String POSITIONABLE_FLAVOR = DataFlavor.javaJVMLocalObjectMimeType
116            + ";class=jmri.jmrit.display.Positionable";
117
118    private boolean _loadFailed = false;
119
120    private ArrayList<Positionable> _contents = new ArrayList<>();
121    private Map<String, Positionable> _idContents = new HashMap<>();
122    private Map<String, Set<Positionable>> _classContents = new HashMap<>();
123    protected JLayeredPane _targetPanel;
124    private JFrame _targetFrame;
125    private JScrollPane _panelScrollPane;
126
127    // Option menu items
128    protected int _scrollState = SCROLL_NONE;
129    protected boolean _editable = true;
130    private boolean _positionable = true;
131    private boolean _controlLayout = true;
132    private boolean _showHidden = true;
133    private boolean _showToolTip = true;
134//    private boolean _showCoordinates = true;
135
136    public static final int OPTION_POSITION = 1;
137    public static final int OPTION_CONTROLS = 2;
138    public static final int OPTION_HIDDEN = 3;
139    public static final int OPTION_TOOLTIP = 4;
140//    public static final int OPTION_COORDS = 5;
141
142    private boolean _globalSetsLocal = true;    // pre 2.9.6 behavior
143    private boolean _useGlobalFlag = false;     // pre 2.9.6 behavior
144
145    // mouse methods variables
146    protected int _lastX;
147    protected int _lastY;
148    BasicStroke DASHED_LINE = new BasicStroke(1f, BasicStroke.CAP_BUTT,
149            BasicStroke.JOIN_BEVEL,
150            10f, new float[]{10f, 10f}, 0f);
151
152    protected Rectangle _selectRect = null;
153    protected Rectangle _highlightcomponent = null;
154    protected boolean _dragging = false;
155    protected ArrayList<Positionable> _selectionGroup = null;  // items gathered inside fence
156
157    protected Positionable _currentSelection;
158    private ToolTip _defaultToolTip;
159    private ToolTip _tooltip = null;
160
161    // Accessible to editor views
162    protected int xLoc = 0;     // x coord of selected Positionable
163    protected int yLoc = 0;     // y coord of selected Positionable
164    protected int _anchorX;     // x coord when mousePressed
165    protected int _anchorY;     // y coord when mousePressed
166
167//    private boolean delayedPopupTrigger = false; // Used to delay the request of a popup, on a mouse press as this may conflict with a drag event
168    protected double _paintScale = 1.0;   // scale for _targetPanel drawing
169
170    protected Color defaultBackgroundColor = Color.lightGray;
171    protected boolean _pastePending = false;
172
173    // map of icon editor frames (incl, icon editor) keyed by name
174    protected HashMap<String, JFrameItem> _iconEditorFrame = new HashMap<>();
175
176    // store panelMenu state so preference is retained on headless systems
177    private boolean panelMenuIsVisible = true;
178
179    private boolean _inEditInlineLogixNGMode = false;
180    private LogixNGEditor _inlineLogixNGEdit;
181
182    public Editor() {
183    }
184
185    public Editor(String name, boolean saveSize, boolean savePosition) {
186        super(name, saveSize, savePosition);
187        setName(name);
188        _defaultToolTip = new ToolTip(null, 0, 0, null);
189        setVisible(false);
190        InstanceManager.getDefault(SignalHeadManager.class).addVetoableChangeListener(this);
191        InstanceManager.getDefault(SignalMastManager.class).addVetoableChangeListener(this);
192        InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this);
193        InstanceManager.sensorManagerInstance().addVetoableChangeListener(this);
194        InstanceManager.memoryManagerInstance().addVetoableChangeListener(this);
195        InstanceManager.getDefault(BlockManager.class).addVetoableChangeListener(this);
196        InstanceManager.getDefault(EditorManager.class).add(this);
197    }
198
199    public Editor(String name) {
200        this(name, true, true);
201    }
202
203    /**
204     * Set <strong>white</strong> as the default background color for panels created using the <strong>New Panel</strong> menu item.
205     * Overriden by LE to use a different default background color and set other initial defaults.
206     */
207    public void newPanelDefaults() {
208        setBackgroundColor(Color.WHITE);
209    }
210
211    public void loadFailed() {
212        _loadFailed = true;
213    }
214
215    NamedIcon _newIcon;
216    boolean _ignore = false;
217    boolean _delete;
218    HashMap<String, String> _urlMap = new HashMap<>();
219
220    public NamedIcon loadFailed(String msg, String url) {
221        log.debug("loadFailed _ignore= {} {}", _ignore, msg);
222        if (_urlMap == null) {
223            _urlMap = new HashMap<>();
224        }
225        String goodUrl = _urlMap.get(url);
226        if (goodUrl != null) {
227            return NamedIcon.getIconByName(goodUrl);
228        }
229        if (_ignore) {
230            _loadFailed = true;
231            return NamedIcon.getIconByName(url);
232        }
233        _newIcon = null;
234        _delete = false;
235        (new UrlErrorDialog(msg, url)).setVisible(true);
236
237        if (_delete) {
238            return null;
239        }
240        if (_newIcon == null) {
241            _loadFailed = true;
242            _newIcon = NamedIcon.getIconByName(url);
243        }
244        return _newIcon;
245    }
246
247    private String getDisableLocoMarkerPopupRef() {
248        return "DisableLocoMarkerPopup__"+getWindowFrameRef();
249    }
250
251    public void setLocoMarkerPopupDisabled(boolean value) {
252        var prefsMgr = InstanceManager.getOptionalDefault(UserPreferencesManager.class);
253        if (prefsMgr.isPresent()) {
254            prefsMgr.get().setCheckboxPreferenceState(getDisableLocoMarkerPopupRef(), value);
255        }
256    }
257
258    public boolean isLocoMarkerPopupDisabled() {
259        var prefsMgr = InstanceManager.getOptionalDefault(UserPreferencesManager.class);
260        if (prefsMgr.isPresent()) {
261            return prefsMgr.get().getCheckboxPreferenceState(getDisableLocoMarkerPopupRef(), false);
262        } else {
263            return false;
264        }
265    }
266
267    public class UrlErrorDialog extends JDialog {
268
269        private final JTextField _urlField;
270        private final CatalogPanel _catalog;
271        private final String _badUrl;
272
273        UrlErrorDialog(String msg, String url) {
274            super(_targetFrame, Bundle.getMessage("BadIcon"), true);
275            _badUrl = url;
276            JPanel content = new JPanel();
277            JPanel panel = new JPanel();
278            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
279            panel.add(Box.createVerticalStrut(10));
280            panel.add(new JLabel(MessageFormat.format(Bundle.getMessage("IconUrlError"), msg)));
281            panel.add(new JLabel(Bundle.getMessage("UrlErrorPrompt1")));
282            panel.add(new JLabel(Bundle.getMessage("UrlErrorPrompt1A")));
283            panel.add(new JLabel(Bundle.getMessage("UrlErrorPrompt1B")));
284            panel.add(Box.createVerticalStrut(10));
285            panel.add(new JLabel(Bundle.getMessage("UrlErrorPrompt2", Bundle.getMessage("ButtonContinue"))));
286            panel.add(new JLabel(Bundle.getMessage("UrlErrorPrompt3", Bundle.getMessage("ButtonDelete"))));
287            panel.add(new JLabel(Bundle.getMessage("UrlErrorPrompt3A")));
288            panel.add(Box.createVerticalStrut(10));
289            panel.add(new JLabel(Bundle.getMessage("UrlErrorPrompt4", Bundle.getMessage("ButtonIgnore"))));
290            panel.add(Box.createVerticalStrut(10));
291            _urlField = new JTextField(url);
292            _urlField.setDragEnabled(true);
293            _urlField.setTransferHandler(new DnDStringImportHandler());
294            panel.add(_urlField);
295            panel.add(makeDoneButtonPanel());
296            _urlField.setToolTipText(Bundle.getMessage("TooltipFixUrl"));
297            panel.setToolTipText(Bundle.getMessage("TooltipFixUrl"));
298            _catalog = CatalogPanel.makeDefaultCatalog();
299            _catalog.setToolTipText(Bundle.getMessage("ToolTipDragIconToText"));
300            panel.add(_catalog);
301            content.add(panel);
302            setContentPane(content);
303            setLocation(200, 100);
304            pack();
305        }
306
307        private JPanel makeDoneButtonPanel() {
308            JPanel result = new JPanel();
309            result.setLayout(new FlowLayout());
310            JButton doneButton = new JButton(Bundle.getMessage("ButtonContinue"));
311            doneButton.addActionListener(a -> {
312                _newIcon = NamedIcon.getIconByName(_urlField.getText());
313                if (_newIcon != null) {
314                    _urlMap.put(_badUrl, _urlField.getText());
315                }
316                UrlErrorDialog.this.dispose();
317            });
318            doneButton.setToolTipText(Bundle.getMessage("TooltipContinue"));
319            result.add(doneButton);
320
321            JButton deleteButton = new JButton(Bundle.getMessage("ButtonDelete"));
322            deleteButton.addActionListener(a -> {
323                _delete = true;
324                UrlErrorDialog.this.dispose();
325            });
326            result.add(deleteButton);
327            deleteButton.setToolTipText(Bundle.getMessage("TooltipDelete"));
328
329            JButton cancelButton = new JButton(Bundle.getMessage("ButtonIgnore"));
330            cancelButton.addActionListener(a -> {
331                _ignore = true;
332                UrlErrorDialog.this.dispose();
333            });
334            result.add(cancelButton);
335            cancelButton.setToolTipText(Bundle.getMessage("TooltipIgnore"));
336            return result;
337        }
338    }
339
340    public void disposeLoadData() {
341        _urlMap = null;
342    }
343
344    public boolean loadOK() {
345        return !_loadFailed;
346    }
347
348    public List<Positionable> getContents() {
349        return Collections.unmodifiableList(_contents);
350    }
351
352    public Map<String, Positionable> getIdContents() {
353        return Collections.unmodifiableMap(_idContents);
354    }
355
356    public Set<String> getClassNames() {
357        return Collections.unmodifiableSet(_classContents.keySet());
358    }
359
360    public Set<Positionable> getPositionablesByClassName(String className) {
361        Set<Positionable> set = _classContents.get(className);
362        if (set == null) {
363            return null;
364        }
365        return Collections.unmodifiableSet(set);
366    }
367
368    public void setDefaultToolTip(ToolTip dtt) {
369        _defaultToolTip = dtt;
370    }
371
372    //
373    // *************** setting the main panel and frame ***************
374    //
375    /**
376     * Set the target panel.
377     * <p>
378     * An Editor may or may not choose to use 'this' as its frame or the
379     * interior class 'TargetPane' for its targetPanel.
380     *
381     * @param targetPanel the panel to be edited
382     * @param frame       the frame to embed the panel in
383     */
384    protected void setTargetPanel(JLayeredPane targetPanel, JmriJFrame frame) {
385        if (targetPanel == null) {
386            _targetPanel = new TargetPane();
387        } else {
388            _targetPanel = targetPanel;
389        }
390        // If on a headless system, set heavyweight components to null
391        // and don't attach mouse and keyboard listeners to the panel
392        if (GraphicsEnvironment.isHeadless()) {
393            _panelScrollPane = null;
394            _targetFrame = null;
395            return;
396        }
397        if (frame == null) {
398            _targetFrame = this;
399        } else {
400            _targetFrame = frame;
401        }
402        _targetFrame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
403        _panelScrollPane = new JScrollPane(_targetPanel);
404        Container contentPane = _targetFrame.getContentPane();
405        contentPane.add(_panelScrollPane);
406        _targetFrame.addWindowListener(new WindowAdapter() {
407            @Override
408            public void windowClosing(WindowEvent e) {
409                targetWindowClosingEvent(e);
410            }
411        });
412        _targetPanel.addMouseListener(JmriMouseListener.adapt(this));
413        _targetPanel.addMouseMotionListener(JmriMouseMotionListener.adapt(this));
414        _targetPanel.setFocusable(true);
415        _targetPanel.addKeyListener(this);
416        //_targetFrame.pack();
417    }
418
419    protected void setTargetPanelSize(int w, int h) {
420//        log.debug("setTargetPanelSize now w={}, h={}", w, h);
421        _targetPanel.setSize(w, h);
422        _targetPanel.invalidate();
423    }
424
425    protected Dimension getTargetPanelSize() {
426        return _targetPanel.getSize();
427    }
428
429    /**
430     * Allow public access to the target (content) panel for external
431     * modification, particularly from scripts.
432     *
433     * @return the target panel
434     */
435    public final JComponent getTargetPanel() {
436        return _targetPanel;
437    }
438
439    /**
440     * Allow public access to the scroll pane for external control of position,
441     * particularly from scripts.
442     *
443     * @return the scroll pane containing the target panel
444     */
445    public final JScrollPane getPanelScrollPane() {
446        return _panelScrollPane;
447    }
448
449    public final JFrame getTargetFrame() {
450        return _targetFrame;
451    }
452
453    public Color getBackgroundColor() {
454        if (_targetPanel instanceof TargetPane) {
455            TargetPane tmp = (TargetPane) _targetPanel;
456            return tmp.getBackgroundColor();
457        } else {
458            return null;
459        }
460    }
461
462    public void setBackgroundColor(Color col) {
463        if (_targetPanel instanceof TargetPane) {
464            TargetPane tmp = (TargetPane) _targetPanel;
465            tmp.setBackgroundColor(col);
466        }
467        JmriColorChooser.addRecentColor(col);
468    }
469
470    public void clearBackgroundColor() {
471        if (_targetPanel instanceof TargetPane) {
472            TargetPane tmp = (TargetPane) _targetPanel;
473            tmp.clearBackgroundColor();
474        }
475    }
476
477    /**
478     * Get scale for TargetPane drawing.
479     *
480     * @return the scale
481     */
482    public final double getPaintScale() {
483        return _paintScale;
484    }
485
486    protected final void setPaintScale(double newScale) {
487        double ratio = newScale / _paintScale;
488        _paintScale = newScale;
489        setScrollbarScale(ratio);
490    }
491
492    /**
493     * Repaint a limited area of the target panel.
494     * <p>
495     * The rectangle is given in panel (unscaled) coordinates, e.g. the bounds
496     * of a child of the target panel: children are laid out in unscaled
497     * coordinates while painting is scaled by the paint scale, so the dirty
498     * region is scaled here. The region is grown by a pixel on each side to
499     * cover rounding and antialiasing.
500     * <p>
501     * Use this instead of a full {@code repaint()} of the Editor frame when
502     * the changed area is known, e.g. to show a state change of a single
503     * icon. Safe to call from any thread, as it only posts a dirty region.
504     *
505     * @param rect area to repaint, in unscaled target panel coordinates
506     */
507    public void repaintTargetPanel(@Nonnull Rectangle rect) {
508        JLayeredPane panel = _targetPanel;
509        if (panel == null) { // too early in construction, fall back to a full repaint
510            repaint();
511            return;
512        }
513        double scale = _paintScale;
514        int x = (int) Math.floor(rect.x * scale) - 1;
515        int y = (int) Math.floor(rect.y * scale) - 1;
516        // +2 for the margin pixels, +1 for the flooring of x and y
517        int width = (int) Math.ceil(rect.width * scale) + 3;
518        int height = (int) Math.ceil(rect.height * scale) + 3;
519        panel.repaint(x, y, width, height);
520    }
521
522    private ToolTipTimer _tooltipTimer;
523
524    protected void setToolTip(ToolTip tt) {
525        if (tt != null) {
526            var pos = tt.getPositionable();
527            if (pos != null) {  // LE turnout tooltips do not have a Positionable
528                if (pos.isHidden() && !isEditable()) {
529                    // Skip hidden objects
530                    return;
531                }
532            }
533        }
534
535        if (tt == null) {
536            _tooltip = null;
537            if (_tooltipTimer != null) {
538                _tooltipTimer.stop();
539                _tooltipTimer = null;
540                _targetPanel.repaint();
541            }
542
543        } else if (_tooltip == null && _tooltipTimer == null) {
544            log.debug("start :: tt = {}, tooltip = {}, timer = {}", tt, _tooltip, _tooltipTimer);
545            _tooltipTimer = new ToolTipTimer(TOOLTIPSHOWDELAY, this, tt);
546            _tooltipTimer.setRepeats(false);
547            _tooltipTimer.start();
548        }
549    }
550
551    static int TOOLTIPSHOWDELAY = 1000; // msec
552    static int TOOLTIPDISMISSDELAY = 4000;  // msec
553
554    /*
555     * Wait TOOLTIPSHOWDELAY then show tooltip. Wait TOOLTIPDISMISSDELAY and
556     * disappear.
557     */
558    @Override
559    public void actionPerformed(ActionEvent event) {
560        //log.debug("_tooltipTimer actionPerformed: Timer on= {}", (_tooltipTimer!=null));
561        if (_tooltipTimer != null) {
562            _tooltip = _tooltipTimer.getToolTip();
563            _tooltipTimer.stop();
564        }
565        if (_tooltip != null) {
566            _tooltipTimer = new ToolTipTimer(TOOLTIPDISMISSDELAY, this, null);
567            _tooltipTimer.setRepeats(false);
568            _tooltipTimer.start();
569        } else {
570            _tooltipTimer = null;
571        }
572        _targetPanel.repaint();
573    }
574
575    static class ToolTipTimer extends Timer {
576
577        private final ToolTip tooltip;
578
579        ToolTipTimer(int delay, ActionListener listener, ToolTip tip) {
580            super(delay, listener);
581            tooltip = tip;
582        }
583
584        ToolTip getToolTip() {
585            return tooltip;
586        }
587    }
588
589    /**
590     * Special internal class to allow drawing of layout to a JLayeredPane. This
591     * is the 'target' pane where the layout is displayed.
592     */
593    public class TargetPane extends JLayeredPane {
594
595        private int h = 100;
596        private int w = 150;
597
598        public TargetPane() {
599            setLayout(null);
600        }
601
602        @Override
603        public void setSize(int width, int height) {
604//            log.debug("size now w={}, h={}", width, height);
605            this.h = height;
606            this.w = width;
607            super.setSize(width, height);
608        }
609
610        @Override
611        public Dimension getSize() {
612            return new Dimension(w, h);
613        }
614
615        @Override
616        public Dimension getPreferredSize() {
617            return new Dimension(w, h);
618        }
619
620        @Override
621        public Dimension getMinimumSize() {
622            return getPreferredSize();
623        }
624
625        @Override
626        public Dimension getMaximumSize() {
627            return getPreferredSize();
628        }
629
630        @Override
631        public Component add(@Nonnull Component c, int i) {
632            int hnew = Math.max(this.h, c.getLocation().y + c.getSize().height);
633            int wnew = Math.max(this.w, c.getLocation().x + c.getSize().width);
634            if (hnew > h || wnew > w) {
635//                log.debug("size was {},{} - i ={}", w, h, i);
636                setSize(wnew, hnew);
637            }
638            return super.add(c, i);
639        }
640
641        @Override
642        public void add(@Nonnull Component c, Object o) {
643            super.add(c, o);
644            int hnew = Math.max(h, c.getLocation().y + c.getSize().height);
645            int wnew = Math.max(w, c.getLocation().x + c.getSize().width);
646            if (hnew > h || wnew > w) {
647                // log.debug("adding of {} with Object - i=", c.getSize(), o);
648                setSize(wnew, hnew);
649            }
650        }
651
652        private Color _highlightColor = HIGHLIGHT_COLOR;
653        private Color _selectGroupColor = HIGHLIGHT_COLOR;
654        private Color _selectRectColor = Color.red;
655        private transient Stroke _selectRectStroke = DASHED_LINE;
656
657        public void setHighlightColor(Color color) {
658            _highlightColor = color;
659        }
660
661        public Color getHighlightColor() {
662            return _highlightColor;
663        }
664
665        public void setSelectGroupColor(Color color) {
666            _selectGroupColor = color;
667        }
668
669        public void setSelectRectColor(Color color) {
670            _selectRectColor = color;
671        }
672
673        public void setSelectRectStroke(Stroke stroke) {
674            _selectRectStroke = stroke;
675        }
676
677        public void setDefaultColors() {
678            _highlightColor = HIGHLIGHT_COLOR;
679            _selectGroupColor = HIGHLIGHT_COLOR;
680            _selectRectColor = Color.red;
681            _selectRectStroke = DASHED_LINE;
682        }
683
684        @Override
685        public void paint(Graphics g) {
686            Graphics2D g2d = null;
687            if (g instanceof Graphics2D) {
688                g2d = (Graphics2D) g;
689                g2d.scale(_paintScale, _paintScale);
690            }
691            super.paint(g);
692
693            Stroke stroke = new BasicStroke();
694            if (g2d != null) {
695                stroke = g2d.getStroke();
696            }
697            Color color = g.getColor();
698            if (_selectRect != null) {
699                //Draw a rectangle on top of the image.
700                if (g2d != null) {
701                    g2d.setStroke(_selectRectStroke);
702                }
703                g.setColor(_selectRectColor);
704                g.drawRect(_selectRect.x, _selectRect.y, _selectRect.width, _selectRect.height);
705            }
706            if (_selectionGroup != null) {
707                g.setColor(_selectGroupColor);
708                if (g2d != null) {
709                    g2d.setStroke(new BasicStroke(2.0f));
710                }
711                for (Positionable p : _selectionGroup) {
712                    if (p != null) {
713                        if (!(p instanceof PositionableShape)) {
714                            g.drawRect(p.getX(), p.getY(), p.maxWidth(), p.maxHeight());
715                        } else {
716                            PositionableShape s = (PositionableShape) p;
717                            s.drawHandles();
718                        }
719                    }
720                }
721            }
722            //Draws a border around the highlighted component
723            if (_highlightcomponent != null) {
724                g.setColor(_highlightColor);
725                if (g2d != null) {
726                    g2d.setStroke(new BasicStroke(2.0f));
727                }
728                g.drawRect(_highlightcomponent.x, _highlightcomponent.y,
729                        _highlightcomponent.width, _highlightcomponent.height);
730            }
731            paintTargetPanel(g);
732
733            g.setColor(color);
734            if (g2d != null) {
735                g2d.setStroke(stroke);
736            }
737            if (_tooltip != null) {
738                _tooltip.paint(g2d, _paintScale);
739            }
740        }
741
742        public void setBackgroundColor(Color col) {
743            setBackground(col);
744            setOpaque(true);
745            JmriColorChooser.addRecentColor(col);
746        }
747
748        public void clearBackgroundColor() {
749            setOpaque(false);
750        }
751
752        public Color getBackgroundColor() {
753            if (isOpaque()) {
754                return getBackground();
755            }
756            return null;
757        }
758    }
759
760    private void setScrollbarScale(double ratio) {
761        //resize the panel to reflect scaling
762        Dimension dim = _targetPanel.getSize();
763        int tpWidth = (int) ((dim.width) * ratio);
764        int tpHeight = (int) ((dim.height) * ratio);
765        _targetPanel.setSize(tpWidth, tpHeight);
766        log.debug("setScrollbarScale: ratio= {}, tpWidth= {}, tpHeight= {}", ratio, tpWidth, tpHeight);
767        // compute new scroll bar positions to keep upper left same
768        JScrollBar horScroll = _panelScrollPane.getHorizontalScrollBar();
769        JScrollBar vertScroll = _panelScrollPane.getVerticalScrollBar();
770        int hScroll = (int) (horScroll.getValue() * ratio);
771        int vScroll = (int) (vertScroll.getValue() * ratio);
772        // set scrollbars maximum range (otherwise setValue may fail);
773        horScroll.setMaximum((int) ((horScroll.getMaximum()) * ratio));
774        vertScroll.setMaximum((int) ((vertScroll.getMaximum()) * ratio));
775        // set scroll bar positions
776        horScroll.setValue(hScroll);
777        vertScroll.setValue(vScroll);
778    }
779
780    /*
781     * ********************** Options setup *********************
782     */
783    /**
784     * Control whether target panel items are editable. Does this by invoke the
785     * {@link Positionable#setEditable(boolean)} function of each item on the
786     * target panel. This also controls the relevant pop-up menu items (which
787     * are the primary way that items are edited).
788     *
789     * @param state true for editable.
790     */
791    public void setAllEditable(boolean state) {
792        _editable = state;
793        for (Positionable _content : _contents) {
794            _content.setEditable(state);
795        }
796        if (!_editable) {
797            _highlightcomponent = null;
798            deselectSelectionGroup();
799        }
800    }
801
802    public void deselectSelectionGroup() {
803        if (_selectionGroup == null) {
804            return;
805        }
806        for (Positionable p : _selectionGroup) {
807            if (p instanceof PositionableShape) {
808                PositionableShape s = (PositionableShape) p;
809                s.removeHandles();
810            }
811        }
812        _selectionGroup = null;
813    }
814
815    // accessor routines for persistent information
816    public boolean isEditable() {
817        return _editable;
818    }
819
820    /**
821     * Set which flag should be used, global or local for Positioning and
822     * Control of individual items. Items call getFlag() to return the
823     * appropriate flag it should use.
824     *
825     * @param set True if global flags should be used for positioning.
826     */
827    public void setUseGlobalFlag(boolean set) {
828        _useGlobalFlag = set;
829    }
830
831    public boolean useGlobalFlag() {
832        return _useGlobalFlag;
833    }
834
835    /**
836     * Get the setting for the specified option.
837     *
838     * @param whichOption The option to get
839     * @param localFlag   is the current setting of the item
840     * @return The setting for the option
841     */
842    public boolean getFlag(int whichOption, boolean localFlag) {
843        //log.debug("getFlag Option= {}, _useGlobalFlag={} localFlag={}", whichOption, _useGlobalFlag, localFlag);
844        if (_useGlobalFlag) {
845            switch (whichOption) {
846                case OPTION_POSITION:
847                    return _positionable;
848                case OPTION_CONTROLS:
849                    return _controlLayout;
850                case OPTION_HIDDEN:
851                    return _showHidden;
852                case OPTION_TOOLTIP:
853                    return _showToolTip;
854//                case OPTION_COORDS:
855//                    return _showCoordinates;
856                default:
857                    log.warn("Unhandled which option code: {}", whichOption);
858                    break;
859            }
860        }
861        return localFlag;
862    }
863
864    /**
865     * Set if {@link #setAllControlling(boolean)} and
866     * {@link #setAllPositionable(boolean)} are set for existing as well as new
867     * items.
868     *
869     * @param set true if setAllControlling() and setAllPositionable() are set
870     *            for existing items
871     */
872    public void setGlobalSetsLocalFlag(boolean set) {
873        _globalSetsLocal = set;
874    }
875
876    /**
877     * Control whether panel items can be positioned. Markers can always be
878     * positioned.
879     *
880     * @param state true to set all items positionable; false otherwise
881     */
882    public void setAllPositionable(boolean state) {
883        _positionable = state;
884        if (_globalSetsLocal) {
885            for (Positionable p : _contents) {
886                // don't allow backgrounds to be set positionable by global flag
887                if (!state || p.getDisplayLevel() != BKG) {
888                    p.setPositionable(state);
889                }
890            }
891        }
892    }
893
894    public boolean allPositionable() {
895        return _positionable;
896    }
897
898    /**
899     * Control whether target panel items are controlling layout items.
900     * <p>
901     * Does this by invoking the {@link Positionable#setControlling} function of
902     * each item on the target panel. This also controls the relevant pop-up
903     * menu items.
904     *
905     * @param state true for controlling.
906     */
907    public void setAllControlling(boolean state) {
908        _controlLayout = state;
909        if (_globalSetsLocal) {
910            for (Positionable _content : _contents) {
911                _content.setControlling(state);
912            }
913        }
914    }
915
916    public boolean allControlling() {
917        return _controlLayout;
918    }
919
920    /**
921     * Control whether target panel hidden items are visible or not. Does this
922     * by invoke the {@link Positionable#setHidden} function of each item on the
923     * target panel.
924     *
925     * @param state true for Visible.
926     */
927    public void setShowHidden(boolean state) {
928        _showHidden = state;
929        if (_showHidden) {
930            for (Positionable _content : _contents) {
931                _content.setVisible(true);
932            }
933        } else {
934            for (Positionable _content : _contents) {
935                _content.showHidden();
936            }
937        }
938    }
939
940    public boolean showHidden() {
941        return _showHidden;
942    }
943
944    public void setAllShowToolTip(boolean state) {
945        _showToolTip = state;
946        for (Positionable _content : _contents) {
947            _content.setShowToolTip(state);
948        }
949    }
950
951    public boolean showToolTip() {
952        return _showToolTip;
953    }
954
955    /*
956     * Control whether target panel items will show their coordinates in their
957     * popup menu.
958     *
959     * @param state true for show coordinates.
960     */
961 /*
962     public void setShowCoordinates(boolean state) {
963     _showCoordinates = state;
964     for (int i = 0; i<_contents.size(); i++) {
965     _contents.get(i).setViewCoordinates(state);
966     }
967     }
968     public boolean showCoordinates() {
969     return _showCoordinates;
970     }
971     */
972
973    /**
974     * Hide or show menus on the target panel.
975     *
976     * @param state true to show menus; false to hide menus
977     * @since 3.9.5
978     */
979    public void setPanelMenuVisible(boolean state) {
980        this.panelMenuIsVisible = state;
981        if (!GraphicsEnvironment.isHeadless() && this._targetFrame != null) {
982            _targetFrame.getJMenuBar().setVisible(state);
983            this.revalidate();
984        }
985    }
986
987    /**
988     * Is the menu on the target panel shown?
989     *
990     * @return true if menu is visible
991     * @since 3.9.5
992     */
993    public boolean isPanelMenuVisible() {
994        if (!GraphicsEnvironment.isHeadless() && this._targetFrame != null) {
995            this.panelMenuIsVisible = _targetFrame.getJMenuBar().isVisible();
996        }
997        return this.panelMenuIsVisible;
998    }
999
1000    protected void setScroll(int state) {
1001        log.debug("setScroll {}", state);
1002        switch (state) {
1003            case SCROLL_NONE:
1004                _panelScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
1005                _panelScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
1006                break;
1007            case SCROLL_BOTH:
1008                _panelScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
1009                _panelScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
1010                break;
1011            case SCROLL_HORIZONTAL:
1012                _panelScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
1013                _panelScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
1014                break;
1015            case SCROLL_VERTICAL:
1016                _panelScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
1017                _panelScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
1018                break;
1019            default:
1020                log.warn("Unexpected  setScroll state of {}", state);
1021                break;
1022        }
1023        _scrollState = state;
1024    }
1025
1026    public void setScroll(String strState) {
1027        int state = SCROLL_BOTH;
1028        if (strState.equalsIgnoreCase("none") || strState.equalsIgnoreCase("no")) {
1029            state = SCROLL_NONE;
1030        } else if (strState.equals("horizontal")) {
1031            state = SCROLL_HORIZONTAL;
1032        } else if (strState.equals("vertical")) {
1033            state = SCROLL_VERTICAL;
1034        }
1035        log.debug("setScroll: strState= {}, state= {}", strState, state);
1036        setScroll(state);
1037    }
1038
1039    public String getScrollable() {
1040        String value = "";
1041        switch (_scrollState) {
1042            case SCROLL_NONE:
1043                value = "none";
1044                break;
1045            case SCROLL_BOTH:
1046                value = "both";
1047                break;
1048            case SCROLL_HORIZONTAL:
1049                value = "horizontal";
1050                break;
1051            case SCROLL_VERTICAL:
1052                value = "vertical";
1053                break;
1054            default:
1055                log.warn("Unexpected _scrollState of {}", _scrollState);
1056                break;
1057        }
1058        return value;
1059    }
1060    /*
1061     * *********************** end Options setup **********************
1062     */
1063    /*
1064     * Handle closing (actually hiding due to HIDE_ON_CLOSE) the target window.
1065     * <p>
1066     * The target window has been requested to close, don't delete it at this
1067     * time. Deletion must be accomplished via the Delete this panel menu item.
1068     */
1069    protected void targetWindowClosing() {
1070        String name = _targetFrame.getTitle();
1071        if (!InstanceManager.getDefault(ShutDownManager.class).isShuttingDown()) {
1072            InstanceManager.getDefault(UserPreferencesManager.class).showInfoMessage(
1073                    Bundle.getMessage("PanelHideTitle"), Bundle.getMessage("PanelHideNotice", name),  // NOI18N
1074                    "jmri.jmrit.display.EditorManager", "skipHideDialog"); // NOI18N
1075            InstanceManager.getDefault(UserPreferencesManager.class).setPreferenceItemDetails(
1076                    "jmri.jmrit.display.EditorManager", "skipHideDialog", Bundle.getMessage("PanelHideSkip"));  // NOI18N
1077        }
1078    }
1079
1080    protected Editor changeView(String className) {
1081        JFrame frame = getTargetFrame();
1082
1083        try {
1084            Editor ed = (Editor) Class.forName(className).getDeclaredConstructor().newInstance();
1085
1086            ed.setName(getName());
1087            ed.init(getName());
1088
1089            ed._contents = new ArrayList<>(_contents);
1090            ed._idContents = new HashMap<>(_idContents);
1091            ed._classContents = new HashMap<>(_classContents);
1092
1093            for (Positionable p : _contents) {
1094                p.setEditor(ed);
1095                ed.addToTarget(p);
1096                if (log.isDebugEnabled()) {
1097                    log.debug("changeView: {} addToTarget class= {}", p.getNameString(), p.getClass().getName());
1098                }
1099            }
1100            ed.setAllEditable(isEditable());
1101            //ed.setAllPositionable(allPositionable());
1102            //ed.setShowCoordinates(showCoordinates());
1103            ed.setAllShowToolTip(showToolTip());
1104            //ed.setAllControlling(allControlling());
1105            ed.setShowHidden(isVisible());
1106            ed.setPanelMenuVisible(frame.getJMenuBar().isVisible());
1107            ed.setScroll(getScrollable());
1108            ed.setTitle();
1109            ed.setBackgroundColor(getBackgroundColor());
1110            ed.getTargetFrame().setLocation(frame.getLocation());
1111            ed.getTargetFrame().setSize(frame.getSize());
1112            ed.setSize(getSize());
1113//            ed.pack();
1114            ed.setVisible(true);
1115            dispose();
1116            InstanceManager.getDefault(EditorManager.class).add(ed);
1117            return ed;
1118        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException cnfe) {
1119            log.error("changeView exception {}", cnfe.toString());
1120        }
1121        return null;
1122    }
1123
1124    /*
1125     * *********************** Popup Item Methods **********************
1126     *
1127     * These methods are to be called from the editor view's showPopUp method
1128     */
1129    /**
1130     * Add a checkbox to lock the position of the Positionable item.
1131     *
1132     * @param p     the item
1133     * @param popup the menu to add the lock menu item to
1134     */
1135    public void setPositionableMenu(Positionable p, JPopupMenu popup) {
1136        JCheckBoxMenuItem lockItem = new JCheckBoxMenuItem(Bundle.getMessage("LockPosition"));
1137        lockItem.setSelected(!p.isPositionable());
1138        lockItem.addActionListener(new ActionListener() {
1139            private Positionable comp;
1140            private JCheckBoxMenuItem checkBox;
1141
1142            @Override
1143            public void actionPerformed(ActionEvent e) {
1144                comp.setPositionable(!checkBox.isSelected());
1145                setSelectionsPositionable(!checkBox.isSelected(), comp);
1146            }
1147
1148            ActionListener init(Positionable pos, JCheckBoxMenuItem cb) {
1149                comp = pos;
1150                checkBox = cb;
1151                return this;
1152            }
1153        }.init(p, lockItem));
1154        popup.add(lockItem);
1155    }
1156
1157    /**
1158     * Display the {@literal X & Y} coordinates of the Positionable item and
1159     * provide a dialog menu item to edit them.
1160     *
1161     * @param p     The item to add the menu item to
1162     * @param popup The menu item to add the action to
1163     * @return always returns true
1164     */
1165    public boolean setShowCoordinatesMenu(Positionable p, JPopupMenu popup) {
1166
1167        JMenuItem edit;
1168        if ((p instanceof MemoryOrGVIcon) && (p.getPopupUtility().getFixedWidth() == 0)) {
1169            MemoryOrGVIcon pm = (MemoryOrGVIcon) p;
1170
1171            edit = new JMenuItem(Bundle.getMessage(
1172                "EditLocationXY", pm.getOriginalX(), pm.getOriginalY()));
1173
1174            edit.addActionListener(MemoryIconCoordinateEdit.getCoordinateEditAction(pm));
1175        } else {
1176            edit = new JMenuItem(Bundle.getMessage(
1177                "EditLocationXY", p.getX(), p.getY()));
1178            edit.addActionListener(CoordinateEdit.getCoordinateEditAction(p));
1179        }
1180        popup.add(edit);
1181        return true;
1182    }
1183
1184    /**
1185     * Offer actions to align the selected Positionable items either
1186     * Horizontally (at average y coordinates) or Vertically (at average x
1187     * coordinates).
1188     *
1189     * @param p     The positionable item
1190     * @param popup The menu to add entries to
1191     * @return true if entries added to menu
1192     */
1193    public boolean setShowAlignmentMenu(Positionable p, JPopupMenu popup) {
1194        if (showAlignPopup(p)) {
1195            JMenu edit = new JMenu(Bundle.getMessage("EditAlignment"));
1196            edit.add(new AbstractAction(Bundle.getMessage("AlignX")) {
1197                private int _x;
1198
1199                @Override
1200                public void actionPerformed(ActionEvent e) {
1201                    if (_selectionGroup == null) {
1202                        return;
1203                    }
1204                    for (Positionable comp : _selectionGroup) {
1205                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1206                            continue;
1207                        }
1208                        comp.setLocation(_x, comp.getY());
1209                    }
1210                }
1211
1212                AbstractAction init(int x) {
1213                    _x = x;
1214                    return this;
1215                }
1216            }.init(p.getX()));
1217            edit.add(new AbstractAction(Bundle.getMessage("AlignMiddleX")) {
1218                private int _x;
1219
1220                @Override
1221                public void actionPerformed(ActionEvent e) {
1222                    if (_selectionGroup == null) {
1223                        return;
1224                    }
1225                    for (Positionable comp : _selectionGroup) {
1226                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1227                            continue;
1228                        }
1229                        comp.setLocation(_x - comp.getWidth() / 2, comp.getY());
1230                    }
1231                }
1232
1233                AbstractAction init(int x) {
1234                    _x = x;
1235                    return this;
1236                }
1237            }.init(p.getX() + p.getWidth() / 2));
1238            edit.add(new AbstractAction(Bundle.getMessage("AlignOtherX")) {
1239                private int _x;
1240
1241                @Override
1242                public void actionPerformed(ActionEvent e) {
1243                    if (_selectionGroup == null) {
1244                        return;
1245                    }
1246                    for (Positionable comp : _selectionGroup) {
1247                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1248                            continue;
1249                        }
1250                        comp.setLocation(_x - comp.getWidth(), comp.getY());
1251                    }
1252                }
1253
1254                AbstractAction init(int x) {
1255                    _x = x;
1256                    return this;
1257                }
1258            }.init(p.getX() + p.getWidth()));
1259            edit.add(new AbstractAction(Bundle.getMessage("AlignY")) {
1260                private int _y;
1261
1262                @Override
1263                public void actionPerformed(ActionEvent e) {
1264                    if (_selectionGroup == null) {
1265                        return;
1266                    }
1267                    for (Positionable comp : _selectionGroup) {
1268                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1269                            continue;
1270                        }
1271                        comp.setLocation(comp.getX(), _y);
1272                    }
1273                }
1274
1275                AbstractAction init(int y) {
1276                    _y = y;
1277                    return this;
1278                }
1279            }.init(p.getY()));
1280            edit.add(new AbstractAction(Bundle.getMessage("AlignMiddleY")) {
1281                private int _y;
1282
1283                @Override
1284                public void actionPerformed(ActionEvent e) {
1285                    if (_selectionGroup == null) {
1286                        return;
1287                    }
1288                    for (Positionable comp : _selectionGroup) {
1289                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1290                            continue;
1291                        }
1292                        comp.setLocation(comp.getX(), _y - comp.getHeight() / 2);
1293                    }
1294                }
1295
1296                AbstractAction init(int y) {
1297                    _y = y;
1298                    return this;
1299                }
1300            }.init(p.getY() + p.getHeight() / 2));
1301            edit.add(new AbstractAction(Bundle.getMessage("AlignOtherY")) {
1302                private int _y;
1303
1304                @Override
1305                public void actionPerformed(ActionEvent e) {
1306                    if (_selectionGroup == null) {
1307                        return;
1308                    }
1309                    for (Positionable comp : _selectionGroup) {
1310                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1311                            continue;
1312                        }
1313                        comp.setLocation(comp.getX(), _y - comp.getHeight());
1314                    }
1315                }
1316
1317                AbstractAction init(int y) {
1318                    _y = y;
1319                    return this;
1320                }
1321            }.init(p.getY() + p.getHeight()));
1322            edit.add(new AbstractAction(Bundle.getMessage("AlignXFirst")) {
1323
1324                @Override
1325                public void actionPerformed(ActionEvent e) {
1326                    if (_selectionGroup == null) {
1327                        return;
1328                    }
1329                    int x = _selectionGroup.get(0).getX();
1330                    for (int i = 1; i < _selectionGroup.size(); i++) {
1331                        Positionable comp = _selectionGroup.get(i);
1332                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1333                            continue;
1334                        }
1335                        comp.setLocation(x, comp.getY());
1336                    }
1337                }
1338            });
1339            edit.add(new AbstractAction(Bundle.getMessage("AlignYFirst")) {
1340
1341                @Override
1342                public void actionPerformed(ActionEvent e) {
1343                    if (_selectionGroup == null) {
1344                        return;
1345                    }
1346                    int y = _selectionGroup.get(0).getX();
1347                    for (int i = 1; i < _selectionGroup.size(); i++) {
1348                        Positionable comp = _selectionGroup.get(i);
1349                        if (!getFlag(OPTION_POSITION, comp.isPositionable())) {
1350                            continue;
1351                        }
1352                        comp.setLocation(comp.getX(), y);
1353                    }
1354                }
1355            });
1356            popup.add(edit);
1357            return true;
1358        }
1359        return false;
1360    }
1361
1362    /**
1363     * Display 'z' level of the Positionable item and provide a dialog
1364     * menu item to edit it.
1365     *
1366     * @param p     The item
1367     * @param popup the menu to add entries to
1368     */
1369    public void setDisplayLevelMenu(Positionable p, JPopupMenu popup) {
1370        JMenuItem edit = new JMenuItem(Bundle.getMessage("EditLevel_", p.getDisplayLevel()));
1371        edit.addActionListener(CoordinateEdit.getLevelEditAction(p));
1372        popup.add(edit);
1373    }
1374
1375    /**
1376     * Add a menu entry to set visibility of the Positionable item
1377     *
1378     * @param p     the item
1379     * @param popup the menu to add the entry to
1380     */
1381    public void setHiddenMenu(Positionable p, JPopupMenu popup) {
1382        if (p.getDisplayLevel() == BKG) {
1383            return;
1384        }
1385        JCheckBoxMenuItem hideItem = new JCheckBoxMenuItem(Bundle.getMessage("SetHidden"));
1386        hideItem.setSelected(p.isHidden());
1387        hideItem.addActionListener(new ActionListener() {
1388            private Positionable comp;
1389            private JCheckBoxMenuItem checkBox;
1390
1391            @Override
1392            public void actionPerformed(ActionEvent e) {
1393                comp.setHidden(checkBox.isSelected());
1394                setSelectionsHidden(checkBox.isSelected(), comp);
1395            }
1396
1397            ActionListener init(Positionable pos, JCheckBoxMenuItem cb) {
1398                comp = pos;
1399                checkBox = cb;
1400                return this;
1401            }
1402        }.init(p, hideItem));
1403        popup.add(hideItem);
1404    }
1405
1406    /**
1407     * Add a menu entry to set visibility of the Positionable item based on the presence of contents.
1408     * If the value is null or empty, the icon is not visible.
1409     * This is applicable to memory,  block content and LogixNG global variable labels.
1410     *
1411     * @param p     the item
1412     * @param popup the menu to add the entry to
1413     */
1414    public void setEmptyHiddenMenu(Positionable p, JPopupMenu popup) {
1415        if (p.getDisplayLevel() == BKG) {
1416            return;
1417        }
1418        if (p instanceof BlockContentsIcon || p instanceof MemoryIcon
1419                || p instanceof GlobalVariableIcon || p instanceof LogixNGTableIcon) {
1420            JCheckBoxMenuItem hideEmptyItem = new JCheckBoxMenuItem(Bundle.getMessage("SetEmptyHidden"));
1421            hideEmptyItem.setSelected(p.isEmptyHidden());
1422            hideEmptyItem.addActionListener(new ActionListener() {
1423                private Positionable comp;
1424                private JCheckBoxMenuItem checkBox;
1425
1426                @Override
1427                public void actionPerformed(ActionEvent e) {
1428                    comp.setEmptyHidden(checkBox.isSelected());
1429                }
1430
1431                ActionListener init(Positionable pos, JCheckBoxMenuItem cb) {
1432                    comp = pos;
1433                    checkBox = cb;
1434                    return this;
1435                }
1436            }.init(p, hideEmptyItem));
1437            popup.add(hideEmptyItem);
1438        }
1439    }
1440
1441    /**
1442     * Add a menu entry to disable double click value edits.  This applies when not in panel edit mode.
1443     * This is applicable to memory,  block content and LogixNG global variable labels.
1444     *
1445     * @param p     the item
1446     * @param popup the menu to add the entry to
1447     */
1448    public void setValueEditDisabledMenu(Positionable p, JPopupMenu popup) {
1449        if (p.getDisplayLevel() == BKG) {
1450            return;
1451        }
1452        if (p instanceof BlockContentsIcon || p instanceof MemoryIcon
1453                || p instanceof GlobalVariableIcon || p instanceof LogixNGTableIcon) {
1454            JCheckBoxMenuItem valueEditDisableItem = new JCheckBoxMenuItem(Bundle.getMessage("SetValueEditDisabled"));
1455            valueEditDisableItem.setSelected(p.isValueEditDisabled());
1456            valueEditDisableItem.addActionListener(new ActionListener() {
1457                private Positionable comp;
1458                private JCheckBoxMenuItem checkBox;
1459
1460                @Override
1461                public void actionPerformed(ActionEvent e) {
1462                    comp.setValueEditDisabled(checkBox.isSelected());
1463                }
1464
1465                ActionListener init(Positionable pos, JCheckBoxMenuItem cb) {
1466                    comp = pos;
1467                    checkBox = cb;
1468                    return this;
1469                }
1470            }.init(p, valueEditDisableItem));
1471            popup.add(valueEditDisableItem);
1472        }
1473    }
1474
1475    /**
1476     * Add a menu entry to edit Id of the Positionable item
1477     *
1478     * @param p     the item
1479     * @param popup the menu to add the entry to
1480     */
1481    public void setEditIdMenu(Positionable p, JPopupMenu popup) {
1482        if (p.getDisplayLevel() == BKG) {
1483            return;
1484        }
1485
1486        popup.add(CoordinateEdit.getIdEditAction(p, "EditId", this));
1487    }
1488
1489    /**
1490     * Add a menu entry to edit Classes of the Positionable item
1491     *
1492     * @param p     the item
1493     * @param popup the menu to add the entry to
1494     */
1495    public void setEditClassesMenu(Positionable p, JPopupMenu popup) {
1496        if (p.getDisplayLevel() == BKG) {
1497            return;
1498        }
1499
1500        popup.add(CoordinateEdit.getClassesEditAction(p, "EditClasses", this));
1501    }
1502
1503    /**
1504     * Check if edit of a conditional is in progress.
1505     *
1506     * @return true if this is the case, after showing dialog to user
1507     */
1508    private boolean checkEditConditionalNG() {
1509        if (_inEditInlineLogixNGMode) {
1510            // Already editing a LogixNG, ask for completion of that edit
1511            JmriJOptionPane.showMessageDialog(null,
1512                    Bundle.getMessage("Error_InlineLogixNGInEditMode"), // NOI18N
1513                    Bundle.getMessage("ErrorTitle"), // NOI18N
1514                    JmriJOptionPane.ERROR_MESSAGE);
1515            _inlineLogixNGEdit.bringToFront();
1516            return true;
1517        }
1518        return false;
1519    }
1520
1521    /**
1522     * Add a menu entry to edit Id of the Positionable item
1523     *
1524     * @param p     the item
1525     * @param popup the menu to add the entry to
1526     */
1527    public void setLogixNGPositionableMenu(Positionable p, JPopupMenu popup) {
1528        if (p.getDisplayLevel() == BKG) {
1529            return;
1530        }
1531
1532        JMenu logixNG_Menu = new JMenu("LogixNG");
1533        popup.add(logixNG_Menu);
1534
1535        logixNG_Menu.add(new AbstractAction(Bundle.getMessage("LogixNG_Inline")) {
1536            @Override
1537            public void actionPerformed(ActionEvent e) {
1538                if (checkEditConditionalNG()) {
1539                    return;
1540                }
1541
1542                if (p.getLogixNG() == null) {
1543                    LogixNG logixNG = InstanceManager.getDefault(LogixNG_Manager.class)
1544                            .createLogixNG(null, true);
1545                    logixNG.setInlineLogixNG(p);
1546                    logixNG.activate();
1547                    logixNG.setEnabled(true);
1548                    logixNG.clearStartup();
1549                    p.setLogixNG(logixNG);
1550                }
1551                LogixNGEditor logixNGEditor = new LogixNGEditor(null, p.getLogixNG().getSystemName());
1552                logixNGEditor.addEditorEventListener((HashMap<String, String> data) -> {
1553                    _inEditInlineLogixNGMode = false;
1554                    data.forEach((key, value) -> {
1555                        if (key.equals("Finish")) {                  // NOI18N
1556                            _inlineLogixNGEdit = null;
1557                            _inEditInlineLogixNGMode = false;
1558                        } else if (key.equals("Delete")) {           // NOI18N
1559                            _inEditInlineLogixNGMode = false;
1560                            deleteLogixNG(p.getLogixNG());
1561                        } else if (key.equals("chgUname")) {         // NOI18N
1562                            p.getLogixNG().setUserName(value);
1563                        }
1564                    });
1565                    if (p.getLogixNG() != null && p.getLogixNG().getNumConditionalNGs() == 0) {
1566                        deleteLogixNG_Internal(p.getLogixNG());
1567                    }
1568                });
1569                logixNGEditor.bringToFront();
1570                _inEditInlineLogixNGMode = true;
1571                _inlineLogixNGEdit = logixNGEditor;
1572            }
1573        });
1574    }
1575
1576    private void deleteLogixNG(LogixNG logixNG) {
1577        DeleteBean<LogixNG> deleteBean = new DeleteBean<>(
1578                InstanceManager.getDefault(LogixNG_Manager.class));
1579
1580        boolean hasChildren = logixNG.getNumConditionalNGs() > 0;
1581
1582        deleteBean.delete(logixNG, hasChildren, t -> deleteLogixNG_Internal(t),
1583                (t,list) -> logixNG.getListenerRefsIncludingChildren(list),
1584                jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName());
1585    }
1586
1587    private void deleteLogixNG_Internal(LogixNG logixNG) {
1588        logixNG.setEnabled(false);
1589        try {
1590            InstanceManager.getDefault(LogixNG_Manager.class).deleteBean(logixNG, "DoDelete");
1591            logixNG.getInlineLogixNG().setLogixNG(null);
1592        } catch (PropertyVetoException e) {
1593            //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
1594            log.error("{} : Could not Delete.", e.getMessage());
1595        }
1596    }
1597
1598    /**
1599     * Check if it's possible to change the id of the Positionable to the
1600     * desired string.
1601     * @param p the Positionable
1602     * @param newId the desired new id
1603     * @throws jmri.jmrit.display.Positionable.DuplicateIdException if another
1604     *         Positionable in the editor already has this id
1605     */
1606    public void positionalIdChange(Positionable p, String newId)
1607            throws Positionable.DuplicateIdException {
1608
1609        if (Objects.equals(newId, p.getId())) {
1610            return;
1611        }
1612
1613        if ((newId != null) && (_idContents.containsKey(newId))) {
1614            throw new Positionable.DuplicateIdException();
1615        }
1616
1617        if (p.getId() != null) {
1618            _idContents.remove(p.getId());
1619        }
1620        if (newId != null) {
1621            _idContents.put(newId, p);
1622        }
1623    }
1624
1625    /**
1626     * Add a class name to the Positionable
1627     * @param p the Positionable
1628     * @param className the class name
1629     * @throws IllegalArgumentException if the name contains a comma
1630     */
1631    public void positionalAddClass(Positionable p, String className) {
1632
1633        if (className == null) {
1634            throw new IllegalArgumentException("Class name must not be null");
1635        }
1636        if (className.isBlank()) {
1637            throw new IllegalArgumentException("Class name must not be blank");
1638        }
1639        if (className.contains(",")) {
1640            throw new IllegalArgumentException("Class name must not contain a comma");
1641        }
1642
1643        if (p.getClasses().contains(className)) {
1644            return;
1645        }
1646
1647        _classContents.computeIfAbsent(className, o -> new HashSet<>()).add(p);
1648    }
1649
1650    /**
1651     * Removes a class name from the Positionable
1652     * @param p the Positionable
1653     * @param className the class name
1654     */
1655    public void positionalRemoveClass(Positionable p, String className) {
1656
1657        if (p.getClasses().contains(className)) {
1658            return;
1659        }
1660        _classContents.get(className).remove(p);
1661    }
1662
1663    /**
1664     * Add a checkbox to display a tooltip for the Positionable item and if
1665     * showable, provide a dialog menu to edit it.
1666     *
1667     * @param p     the item to set the menu for
1668     * @param popup the menu to add for p
1669     */
1670    public void setShowToolTipMenu(Positionable p, JPopupMenu popup) {
1671        if (p.getDisplayLevel() == BKG) {
1672            return;
1673        }
1674
1675        JMenu edit = new JMenu(Bundle.getMessage("EditTooltip"));
1676
1677        JCheckBoxMenuItem showToolTipItem = new JCheckBoxMenuItem(Bundle.getMessage("ShowTooltip"));
1678        showToolTipItem.setSelected(p.showToolTip());
1679        showToolTipItem.addActionListener(new ActionListener() {
1680            private Positionable comp;
1681            private JCheckBoxMenuItem checkBox;
1682
1683            @Override
1684            public void actionPerformed(ActionEvent e) {
1685                comp.setShowToolTip(checkBox.isSelected());
1686            }
1687
1688            ActionListener init(Positionable pos, JCheckBoxMenuItem cb) {
1689                comp = pos;
1690                checkBox = cb;
1691                return this;
1692            }
1693        }.init(p, showToolTipItem));
1694        edit.add(showToolTipItem);
1695
1696        edit.add(CoordinateEdit.getToolTipEditAction(p));
1697
1698        JCheckBoxMenuItem prependToolTipWithDisplayNameItem =
1699            new JCheckBoxMenuItem(Bundle.getMessage("PrependTooltipWithDisplayName"));
1700        prependToolTipWithDisplayNameItem.setSelected(p.getToolTip().getPrependToolTipWithDisplayName());
1701        prependToolTipWithDisplayNameItem.addActionListener(new ActionListener() {
1702            private Positionable comp;
1703            private JCheckBoxMenuItem checkBox;
1704
1705            @Override
1706            public void actionPerformed(ActionEvent e) {
1707                comp.getToolTip().setPrependToolTipWithDisplayName(checkBox.isSelected());
1708            }
1709
1710            ActionListener init(Positionable pos, JCheckBoxMenuItem cb) {
1711                comp = pos;
1712                checkBox = cb;
1713                return this;
1714            }
1715        }.init(p, prependToolTipWithDisplayNameItem));
1716        edit.add(prependToolTipWithDisplayNameItem);
1717
1718        popup.add(edit);
1719    }
1720
1721    /**
1722     * Add an action to remove the Positionable item.
1723     *
1724     * @param p     the item to set the menu for
1725     * @param popup the menu to add for p
1726     */
1727    public void setRemoveMenu(Positionable p, JPopupMenu popup) {
1728        popup.add(new AbstractAction(Bundle.getMessage("Remove")) {
1729            private Positionable comp;
1730
1731            @Override
1732            public void actionPerformed(ActionEvent e) {
1733                comp.remove();
1734                removeSelections(comp);
1735            }
1736
1737            AbstractAction init(Positionable pos) {
1738                comp = pos;
1739                return this;
1740            }
1741        }.init(p));
1742    }
1743
1744    /*
1745     * *********************** End Popup Methods **********************
1746     */
1747 /*
1748     * ****************** Marker Menu ***************************
1749     */
1750    protected void locoMarkerFromRoster() {
1751        final JmriJFrame locoRosterFrame = new JmriJFrame();
1752        locoRosterFrame.getContentPane().setLayout(new FlowLayout());
1753        locoRosterFrame.setTitle(Bundle.getMessage("LocoFromRoster"));
1754        JLabel mtext = new JLabel();
1755        mtext.setText(Bundle.getMessage("SelectLoco") + ":");
1756        locoRosterFrame.getContentPane().add(mtext);
1757        final RosterEntrySelectorPanel rosterBox = new RosterEntrySelectorPanel();
1758        rosterBox.addPropertyChangeListener("selectedRosterEntries", pce -> {
1759            if (rosterBox.getSelectedRosterEntries().length != 0) {
1760                selectLoco(rosterBox.getSelectedRosterEntries()[0]);
1761            }
1762        });
1763        locoRosterFrame.getContentPane().add(rosterBox);
1764        locoRosterFrame.addWindowListener(new WindowAdapter() {
1765            @Override
1766            public void windowClosing(WindowEvent e) {
1767                locoRosterFrame.dispose();
1768            }
1769        });
1770        locoRosterFrame.pack();
1771        locoRosterFrame.setVisible(true);
1772    }
1773
1774    protected LocoIcon selectLoco(String rosterEntryTitle) {
1775        if ("".equals(rosterEntryTitle)) {
1776            return null;
1777        }
1778        return selectLoco(Roster.getDefault().entryFromTitle(rosterEntryTitle));
1779    }
1780
1781    protected LocoIcon selectLoco(RosterEntry entry) {
1782        LocoIcon l = null;
1783        if (entry == null) {
1784            return null;
1785        }
1786        // try getting road number, else use DCC address
1787        String rn = entry.getRoadNumber();
1788        if ((rn == null) || rn.equals("")) {
1789            rn = entry.getDccAddress();
1790        }
1791        if (rn != null) {
1792            l = addLocoIcon(rn);
1793            l.setRosterEntry(entry);
1794        }
1795        return l;
1796    }
1797
1798    protected void locoMarkerFromInput() {
1799        final JmriJFrame locoFrame = new JmriJFrame();
1800        locoFrame.getContentPane().setLayout(new FlowLayout());
1801        locoFrame.setTitle(Bundle.getMessage("EnterLocoMarker"));
1802
1803        JLabel textId = new JLabel();
1804        textId.setText(Bundle.getMessage("LocoID") + ":");
1805        locoFrame.getContentPane().add(textId);
1806
1807        final JTextField locoId = new JTextField(7);
1808        locoFrame.getContentPane().add(locoId);
1809        locoId.setText("");
1810        locoId.setToolTipText(Bundle.getMessage("EnterLocoID"));
1811        JButton okay = new JButton();
1812        okay.setText(Bundle.getMessage("ButtonOK"));
1813        okay.addActionListener(e -> {
1814            String nameID = locoId.getText();
1815            if ((nameID != null) && !(nameID.trim().equals(""))) {
1816                addLocoIcon(nameID.trim());
1817            } else {
1818                JmriJOptionPane.showMessageDialog(locoFrame, Bundle.getMessage("ErrorEnterLocoID"),
1819                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1820            }
1821        });
1822        locoFrame.getContentPane().add(okay);
1823        locoFrame.addWindowListener(new WindowAdapter() {
1824            @Override
1825            public void windowClosing(WindowEvent e) {
1826                locoFrame.dispose();
1827            }
1828        });
1829        locoFrame.pack();
1830        if (_targetFrame != null) {
1831            locoFrame.setLocation(_targetFrame.getLocation());
1832        }
1833        locoFrame.setVisible(true);
1834    }
1835
1836    /**
1837     * Remove marker icons from panel
1838     */
1839    protected void removeMarkers() {
1840        log.debug("Remove markers");
1841        for (int i = _contents.size() - 1; i >= 0; i--) {
1842            Positionable il = _contents.get(i);
1843            if (il instanceof LocoIcon) {
1844                il.remove();
1845                if (il.getId() != null) {
1846                    _idContents.remove(il.getId());
1847                }
1848                for (String className : il.getClasses()) {
1849                    _classContents.get(className).remove(il);
1850                }
1851            }
1852        }
1853    }
1854
1855    /*
1856     * *********************** End Marker Menu Methods **********************
1857     */
1858 /*
1859     * ************ Adding content to the panel **********************
1860     */
1861    public PositionableLabel setUpBackground(String name) {
1862        NamedIcon icon = NamedIcon.getIconByName(name);
1863        PositionableLabel l = new PositionableLabel(icon, this);
1864        l.setPopupUtility(null);        // no text
1865        l.setPositionable(false);
1866        l.setShowToolTip(false);
1867        l.setSize(icon.getIconWidth(), icon.getIconHeight());
1868        l.setDisplayLevel(BKG);
1869        l.setLocation(getNextBackgroundLeft(), 0);
1870        try {
1871            putItem(l);
1872        } catch (Positionable.DuplicateIdException e) {
1873            // This should never happen
1874            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1875        }
1876        return l;
1877    }
1878
1879    protected PositionableLabel addLabel(String text) {
1880        PositionableLabel l = new PositionableLabel(text, this);
1881        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
1882        l.setDisplayLevel(LABELS);
1883        setNextLocation(l);
1884        try {
1885            putItem(l);
1886        } catch (Positionable.DuplicateIdException e) {
1887            // This should never happen
1888            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1889        }
1890        return l;
1891    }
1892
1893    /**
1894     * Determine right side x of furthest right background
1895     */
1896    private int getNextBackgroundLeft() {
1897        int left = 0;
1898        // place to right of background images, if any
1899        for (Positionable p : _contents) {
1900            if (p instanceof PositionableLabel) {
1901                PositionableLabel l = (PositionableLabel) p;
1902                if (l.isBackground()) {
1903                    int test = l.getX() + l.maxWidth();
1904                    if (test > left) {
1905                        left = test;
1906                    }
1907                }
1908            }
1909        }
1910        return left;
1911    }
1912
1913    /**
1914     * Positionable has set a new level.
1915     * Editor must change it in the target panel.
1916     * @param l the positionable to display.
1917     */
1918    public void displayLevelChange(Positionable l) {
1919        ThreadingUtil.runOnGUI( () -> {
1920            removeFromTarget(l);
1921            addToTarget(l);
1922        });
1923    }
1924
1925    public TrainIcon addTrainIcon(String name) {
1926        TrainIcon l = new TrainIcon(this);
1927        putLocoIcon(l, name);
1928        return l;
1929    }
1930
1931    public LocoIcon addLocoIcon(String name) {
1932        LocoIcon l = new LocoIcon(this);
1933        putLocoIcon(l, name);
1934        return l;
1935    }
1936
1937    public void putLocoIcon(LocoIcon l, String name) {
1938        l.setText(name);
1939        l.setHorizontalTextPosition(SwingConstants.CENTER);
1940        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
1941        l.setEditable(isEditable());    // match popup mode to editor mode
1942        l.setLocation(75, 75);  // fixed location
1943        try {
1944            putItem(l);
1945        } catch (Positionable.DuplicateIdException e) {
1946            // This should never happen
1947            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1948        }
1949    }
1950
1951    public void putItem(@Nonnull Positionable l) throws Positionable.DuplicateIdException {
1952        putItem(l, false);
1953    }
1954
1955    public void putItem(@Nonnull Positionable l, boolean factoryPositionable)
1956            throws Positionable.DuplicateIdException {
1957
1958        ThreadingUtil.runOnGUI( () -> {
1959            l.invalidate();
1960            l.setPositionable(true);
1961            l.setVisible(true);
1962            if (l.getToolTip() == null) {
1963                l.setToolTip(new ToolTip(_defaultToolTip, l));
1964            }
1965            addToTarget(l);
1966        });
1967        if (!_contents.add(l)) {
1968            log.error("Unable to add {} to _contents", l.getNameString());
1969        }
1970        if (l.getId() != null) {
1971            if (_idContents.containsKey(l.getId())) {
1972                throw new Positionable.DuplicateIdException();
1973            }
1974            _idContents.put(l.getId(), l);
1975        }
1976        for (String className : l.getClasses()) {
1977            _classContents.get(className).add(l);
1978        }
1979        if (log.isDebugEnabled()) {
1980            log.debug("putItem {} to _contents. level= {}", l.getNameString(), l.getDisplayLevel());
1981        }
1982    }
1983
1984    protected void addToTarget(Positionable l) {
1985        JComponent c = (JComponent) l;
1986        c.invalidate();
1987        _targetPanel.remove(c);
1988        _targetPanel.add(c, Integer.valueOf(l.getDisplayLevel()));
1989        _targetPanel.moveToFront(c);
1990        c.repaint();
1991        _targetPanel.revalidate();
1992    }
1993
1994    /*
1995     * ************ Icon editors for adding content ***********
1996     */
1997    static final String[] ICON_EDITORS = {"Sensor", "RightTurnout", "LeftTurnout",
1998        "SlipTOEditor", "SignalHead", "SignalMast", "Memory", "Light",
1999        "Reporter", "Background", "MultiSensor", "Icon", "Text", "Block Contents"};
2000
2001    /**
2002     * Create editor for a given item type.
2003     * Paths to default icons are fixed in code. Compare to respective icon package,
2004     * eg. {@link #addSensorEditor()} and {@link SensorIcon}
2005     *
2006     * @param name Icon editor's name
2007     * @return a window
2008     */
2009    public JFrameItem getIconFrame(String name) {
2010        JFrameItem frame = _iconEditorFrame.get(name);
2011        if (frame == null) {
2012            if ("Sensor".equals(name)) {
2013                addSensorEditor();
2014            } else if ("RightTurnout".equals(name)) {
2015                addRightTOEditor();
2016            } else if ("LeftTurnout".equals(name)) {
2017                addLeftTOEditor();
2018            } else if ("SlipTOEditor".equals(name)) {
2019                addSlipTOEditor();
2020            } else if ("SignalHead".equals(name)) {
2021                addSignalHeadEditor();
2022            } else if ("SignalMast".equals(name)) {
2023                addSignalMastEditor();
2024            } else if ("Memory".equals(name)) {
2025                addMemoryEditor();
2026            } else if ("GlobalVariable".equals(name)) {
2027                addGlobalVariableEditor();
2028            } else if ("LogixNGTable".equals(name)) {
2029                addLogixNGTableEditor();
2030            } else if ("Reporter".equals(name)) {
2031                addReporterEditor();
2032            } else if ("Light".equals(name)) {
2033                addLightEditor();
2034            } else if ("Background".equals(name)) {
2035                addBackgroundEditor();
2036            } else if ("MultiSensor".equals(name)) {
2037                addMultiSensorEditor();
2038            } else if ("Icon".equals(name)) {
2039                addIconEditor();
2040            } else if ("Text".equals(name)) {
2041                addTextEditor();
2042            } else if ("BlockLabel".equals(name)) {
2043                addBlockContentsEditor();
2044            } else if ("Audio".equals(name)) {
2045                addAudioEditor();
2046            } else if ("LogixNG".equals(name)) {
2047                addLogixNGEditor();
2048            } else {
2049                // log.error("No such Icon Editor \"{}\"", name);
2050                return null;
2051            }
2052            // frame added in the above switch
2053            frame = _iconEditorFrame.get(name);
2054
2055            if (frame == null) { // addTextEditor does not create a usable frame
2056                return null;
2057            }
2058            //frame.setLocationRelativeTo(this);
2059            frame.setLocation(frameLocationX, frameLocationY);
2060            frameLocationX += DELTA;
2061            frameLocationY += DELTA;
2062        }
2063        frame.setVisible(true);
2064        return frame;
2065    }
2066    public int frameLocationX = 0;
2067    public int frameLocationY = 0;
2068    static final int DELTA = 20;
2069
2070    public IconAdder getIconEditor(String name) {
2071        return _iconEditorFrame.get(name).getEditor();
2072    }
2073
2074    public void putIconEditor(String name, Editor.JFrameItem frame) {
2075        _iconEditorFrame.put(name, frame);
2076    }
2077
2078    /**
2079     * Add a label to the target.
2080     */
2081    protected void addTextEditor() {
2082        String newLabel = JmriJOptionPane.showInputDialog(this, Bundle.getMessage("PromptNewLabel"),"");
2083        if (newLabel == null) {
2084            return;  // canceled
2085        }
2086        PositionableLabel l = addLabel(newLabel);
2087        // always allow new items to be moved
2088        l.setPositionable(true);
2089    }
2090
2091    protected void addRightTOEditor() {
2092        IconAdder editor = new IconAdder("RightTurnout");
2093        editor.setIcon(3, "TurnoutStateClosed",
2094                "resources/icons/smallschematics/tracksegments/os-righthand-west-closed.gif");
2095        editor.setIcon(2, "TurnoutStateThrown",
2096                "resources/icons/smallschematics/tracksegments/os-righthand-west-thrown.gif");
2097        editor.setIcon(0, "BeanStateInconsistent",
2098                "resources/icons/smallschematics/tracksegments/os-righthand-west-error.gif");
2099        editor.setIcon(1, "BeanStateUnknown",
2100                "resources/icons/smallschematics/tracksegments/os-righthand-west-unknown.gif");
2101
2102        JFrameItem frame = makeAddIconFrame("RightTurnout", true, true, editor);
2103        _iconEditorFrame.put("RightTurnout", frame);
2104        editor.setPickList(PickListModel.turnoutPickModelInstance());
2105
2106        ActionListener addIconAction = a -> addTurnoutR();
2107        editor.makeIconPanel(true);
2108        editor.complete(addIconAction, true, true, false);
2109        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2110    }
2111
2112    protected void addLeftTOEditor() {
2113        IconAdder editor = new IconAdder("LeftTurnout");
2114        editor.setIcon(3, "TurnoutStateClosed",
2115                "resources/icons/smallschematics/tracksegments/os-lefthand-east-closed.gif");
2116        editor.setIcon(2, "TurnoutStateThrown",
2117                "resources/icons/smallschematics/tracksegments/os-lefthand-east-thrown.gif");
2118        editor.setIcon(0, "BeanStateInconsistent",
2119                "resources/icons/smallschematics/tracksegments/os-lefthand-east-error.gif");
2120        editor.setIcon(1, "BeanStateUnknown",
2121                "resources/icons/smallschematics/tracksegments/os-lefthand-east-unknown.gif");
2122
2123        JFrameItem frame = makeAddIconFrame("LeftTurnout", true, true, editor);
2124        _iconEditorFrame.put("LeftTurnout", frame);
2125        editor.setPickList(PickListModel.turnoutPickModelInstance());
2126
2127        ActionListener addIconAction = a -> addTurnoutL();
2128        editor.makeIconPanel(true);
2129        editor.complete(addIconAction, true, true, false);
2130        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2131    }
2132
2133    protected void addSlipTOEditor() {
2134        SlipIconAdder editor = new SlipIconAdder("SlipTOEditor");
2135        editor.setIcon(3, "LowerWestToUpperEast",
2136                "resources/icons/smallschematics/tracksegments/os-slip-lower-west-upper-east.gif");
2137        editor.setIcon(2, "UpperWestToLowerEast",
2138                "resources/icons/smallschematics/tracksegments/os-slip-upper-west-lower-east.gif");
2139        editor.setIcon(4, "LowerWestToLowerEast",
2140                "resources/icons/smallschematics/tracksegments/os-slip-lower-west-lower-east.gif");
2141        editor.setIcon(5, "UpperWestToUpperEast",
2142                "resources/icons/smallschematics/tracksegments/os-slip-upper-west-upper-east.gif");
2143        editor.setIcon(0, "BeanStateInconsistent",
2144                "resources/icons/smallschematics/tracksegments/os-slip-error-full.gif");
2145        editor.setIcon(1, "BeanStateUnknown",
2146                "resources/icons/smallschematics/tracksegments/os-slip-unknown-full.gif");
2147        editor.setTurnoutType(SlipTurnoutIcon.DOUBLESLIP);
2148        JFrameItem frame = makeAddIconFrame("SlipTOEditor", true, true, editor);
2149        _iconEditorFrame.put("SlipTOEditor", frame);
2150        editor.setPickList(PickListModel.turnoutPickModelInstance());
2151
2152        ActionListener addIconAction = a -> addSlip();
2153        editor.makeIconPanel(true);
2154        editor.complete(addIconAction, true, true, false);
2155        frame.addHelpMenu("package.jmri.jmrit.display.SlipTurnoutIcon", true);
2156    }
2157
2158    protected void addSensorEditor() {
2159        IconAdder editor = new IconAdder("Sensor");
2160        editor.setIcon(3, "SensorStateActive",
2161                "resources/icons/smallschematics/tracksegments/circuit-occupied.gif");
2162        editor.setIcon(2, "SensorStateInactive",
2163                "resources/icons/smallschematics/tracksegments/circuit-empty.gif");
2164        editor.setIcon(0, "BeanStateInconsistent",
2165                "resources/icons/smallschematics/tracksegments/circuit-error.gif");
2166        editor.setIcon(1, "BeanStateUnknown",
2167                "resources/icons/smallschematics/tracksegments/circuit-error.gif");
2168
2169        JFrameItem frame = makeAddIconFrame("Sensor", true, true, editor);
2170        _iconEditorFrame.put("Sensor", frame);
2171        editor.setPickList(PickListModel.sensorPickModelInstance());
2172
2173        ActionListener addIconAction = a -> putSensor();
2174        editor.makeIconPanel(true);
2175        editor.complete(addIconAction, true, true, false);
2176        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2177    }
2178
2179    protected void addSignalHeadEditor() {
2180        IconAdder editor = getSignalHeadEditor();
2181        JFrameItem frame = makeAddIconFrame("SignalHead", true, true, editor);
2182        _iconEditorFrame.put("SignalHead", frame);
2183        editor.setPickList(PickListModel.signalHeadPickModelInstance());
2184
2185        ActionListener addIconAction = a -> putSignalHead();
2186        editor.makeIconPanel(true);
2187        editor.complete(addIconAction, true, false, false);
2188        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2189    }
2190
2191    protected IconAdder getSignalHeadEditor() {
2192        // note that all these icons will be refreshed when user clicks a specific signal head in the table
2193        IconAdder editor = new IconAdder("SignalHead");
2194        editor.setIcon(0, "SignalHeadStateRed",
2195                "resources/icons/smallschematics/searchlights/left-red-marker.gif");
2196        editor.setIcon(1, "SignalHeadStateYellow",
2197                "resources/icons/smallschematics/searchlights/left-yellow-marker.gif");
2198        editor.setIcon(2, "SignalHeadStateGreen",
2199                "resources/icons/smallschematics/searchlights/left-green-marker.gif");
2200        editor.setIcon(3, "SignalHeadStateDark",
2201                "resources/icons/smallschematics/searchlights/left-dark-marker.gif");
2202        editor.setIcon(4, "SignalHeadStateHeld",
2203                "resources/icons/smallschematics/searchlights/left-held-marker.gif");
2204        editor.setIcon(5, "SignalHeadStateLunar",
2205                "resources/icons/smallschematics/searchlights/left-lunar-marker.gif");
2206        editor.setIcon(6, "SignalHeadStateFlashingRed",
2207                "resources/icons/smallschematics/searchlights/left-flashred-marker.gif");
2208        editor.setIcon(7, "SignalHeadStateFlashingYellow",
2209                "resources/icons/smallschematics/searchlights/left-flashyellow-marker.gif");
2210        editor.setIcon(8, "SignalHeadStateFlashingGreen",
2211                "resources/icons/smallschematics/searchlights/left-flashgreen-marker.gif");
2212        editor.setIcon(9, "SignalHeadStateFlashingLunar",
2213                "resources/icons/smallschematics/searchlights/left-flashlunar-marker.gif");
2214        return editor;
2215    }
2216
2217    protected void addSignalMastEditor() {
2218        IconAdder editor = new IconAdder("SignalMast");
2219
2220        JFrameItem frame = makeAddIconFrame("SignalMast", true, true, editor);
2221        _iconEditorFrame.put("SignalMast", frame);
2222        editor.setPickList(PickListModel.signalMastPickModelInstance());
2223
2224        ActionListener addIconAction = a -> putSignalMast();
2225        editor.makeIconPanel(true);
2226        editor.complete(addIconAction, true, false, false);
2227        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2228    }
2229
2230    private final SpinnerNumberModel _spinCols = new SpinnerNumberModel(3, 1, 100, 1);
2231
2232    protected void addMemoryEditor() {
2233        IconAdder editor = new IconAdder("Memory") {
2234            final JButton bSpin = new JButton(Bundle.getMessage("AddSpinner"));
2235            final JButton bBox = new JButton(Bundle.getMessage("AddInputBox"));
2236            final JSpinner spinner = new JSpinner(_spinCols);
2237
2238            @Override
2239            protected void addAdditionalButtons(JPanel p) {
2240                bSpin.addActionListener(a -> addMemorySpinner());
2241                JPanel p1 = new JPanel();
2242                //p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
2243                bBox.addActionListener(a -> addMemoryInputBox());
2244                ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().setColumns(2);
2245                spinner.setMaximumSize(spinner.getPreferredSize());
2246                JPanel p2 = new JPanel();
2247                p2.add(new JLabel(Bundle.getMessage("NumColsLabel")));
2248                p2.add(spinner);
2249                p1.add(p2);
2250                p1.add(bBox);
2251                p.add(p1);
2252                p1 = new JPanel();
2253                p1.add(bSpin);
2254                p.add(p1);
2255            }
2256
2257            @Override
2258            public void valueChanged(ListSelectionEvent e) {
2259                super.valueChanged(e);
2260                bSpin.setEnabled(addIconIsEnabled());
2261                bBox.setEnabled(addIconIsEnabled());
2262            }
2263        };
2264        ActionListener addIconAction = a -> putMemory();
2265        JFrameItem frame = makeAddIconFrame("Memory", true, true, editor);
2266        _iconEditorFrame.put("Memory", frame);
2267        editor.setPickList(PickListModel.memoryPickModelInstance());
2268        editor.makeIconPanel(true);
2269        editor.complete(addIconAction, false, true, false);
2270        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2271    }
2272
2273    protected void addGlobalVariableEditor() {
2274        IconAdder editor = new IconAdder("GlobalVariable") {
2275            final JButton bSpin = new JButton(Bundle.getMessage("AddSpinner"));
2276            final JButton bBox = new JButton(Bundle.getMessage("AddInputBox"));
2277            final JSpinner spinner = new JSpinner(_spinCols);
2278
2279            @Override
2280            protected void addAdditionalButtons(JPanel p) {
2281                bSpin.addActionListener(a -> addGlobalVariableSpinner());
2282                JPanel p1 = new JPanel();
2283                //p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
2284                bBox.addActionListener(a -> addGlobalVariableInputBox());
2285                ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().setColumns(2);
2286                spinner.setMaximumSize(spinner.getPreferredSize());
2287                JPanel p2 = new JPanel();
2288                p2.add(new JLabel(Bundle.getMessage("NumColsLabel")));
2289                p2.add(spinner);
2290                p1.add(p2);
2291                p1.add(bBox);
2292                p.add(p1);
2293                p1 = new JPanel();
2294                p1.add(bSpin);
2295                p.add(p1);
2296            }
2297
2298            @Override
2299            public void valueChanged(ListSelectionEvent e) {
2300                super.valueChanged(e);
2301                bSpin.setEnabled(addIconIsEnabled());
2302                bBox.setEnabled(addIconIsEnabled());
2303            }
2304        };
2305        ActionListener addIconAction = a -> putGlobalVariable();
2306        JFrameItem frame = makeAddIconFrame("GlobalVariable", true, true, editor);
2307        _iconEditorFrame.put("GlobalVariable", frame);
2308        editor.setPickList(PickListModel.globalVariablePickModelInstance());
2309        editor.makeIconPanel(true);
2310        editor.complete(addIconAction, false, false, false);
2311        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2312    }
2313
2314    protected void addLogixNGTableEditor() {
2315        IconAdder editor = new IconAdder("LogixNGTable");
2316        ActionListener addIconAction = a -> addLogixNGTable();
2317        JFrameItem frame = makeAddIconFrame("LogixNGTable", true, true, editor);
2318        _iconEditorFrame.put("LogixNGTable", frame);
2319        editor.setPickList(PickListModel.namedTablePickModelInstance());
2320        editor.makeIconPanel(true);
2321        editor.complete(addIconAction, false, false, false);
2322        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2323    }
2324
2325    protected void addBlockContentsEditor() {
2326        IconAdder editor = new IconAdder("Block Contents");
2327        ActionListener addIconAction = a -> putBlockContents();
2328        JFrameItem frame = makeAddIconFrame("BlockLabel", true, true, editor);
2329        _iconEditorFrame.put("BlockLabel", frame);
2330        editor.setPickList(PickListModel.blockPickModelInstance());
2331        editor.makeIconPanel(true);
2332        editor.complete(addIconAction, false, true, false);
2333        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2334    }
2335
2336    protected void addReporterEditor() {
2337        IconAdder editor = new IconAdder("Reporter");
2338        ActionListener addIconAction = a -> addReporter();
2339        JFrameItem frame = makeAddIconFrame("Reporter", true, true, editor);
2340        _iconEditorFrame.put("Reporter", frame);
2341        editor.setPickList(PickListModel.reporterPickModelInstance());
2342        editor.makeIconPanel(true);
2343        editor.complete(addIconAction, false, true, false);
2344        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2345    }
2346
2347    protected void addLightEditor() {
2348        IconAdder editor = new IconAdder("Light");
2349        editor.setIcon(3, "StateOff",
2350                "resources/icons/smallschematics/lights/cross-on.png");
2351        editor.setIcon(2, "StateOn",
2352                "resources/icons/smallschematics/lights/cross-off.png");
2353        editor.setIcon(0, "BeanStateInconsistent",
2354                "resources/icons/smallschematics/lights/cross-inconsistent.png");
2355        editor.setIcon(1, "BeanStateUnknown",
2356                "resources/icons/smallschematics/lights/cross-unknown.png");
2357
2358        JFrameItem frame = makeAddIconFrame("Light", true, true, editor);
2359        _iconEditorFrame.put("Light", frame);
2360        editor.setPickList(PickListModel.lightPickModelInstance());
2361
2362        ActionListener addIconAction = a -> addLight();
2363        editor.makeIconPanel(true);
2364        editor.complete(addIconAction, true, true, false);
2365        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2366    }
2367
2368    protected void addBackgroundEditor() {
2369        IconAdder editor = new IconAdder("Background");
2370        editor.setIcon(0, "background", "resources/PanelPro.gif");
2371
2372        JFrameItem frame = makeAddIconFrame("Background", true, false, editor);
2373        _iconEditorFrame.put("Background", frame);
2374
2375        ActionListener addIconAction = a -> putBackground();
2376        editor.makeIconPanel(true);
2377        editor.complete(addIconAction, true, false, false);
2378        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2379    }
2380
2381    protected JFrameItem addMultiSensorEditor() {
2382        MultiSensorIconAdder editor = new MultiSensorIconAdder("MultiSensor");
2383        editor.setIcon(0, "BeanStateInconsistent",
2384                "resources/icons/USS/plate/levers/l-inconsistent.gif");
2385        editor.setIcon(1, "BeanStateUnknown",
2386                "resources/icons/USS/plate/levers/l-unknown.gif");
2387        editor.setIcon(2, "SensorStateInactive",
2388                "resources/icons/USS/plate/levers/l-inactive.gif");
2389        editor.setIcon(3, "MultiSensorPosition 0",
2390                "resources/icons/USS/plate/levers/l-left.gif");
2391        editor.setIcon(4, "MultiSensorPosition 1",
2392                "resources/icons/USS/plate/levers/l-vertical.gif");
2393        editor.setIcon(5, "MultiSensorPosition 2",
2394                "resources/icons/USS/plate/levers/l-right.gif");
2395
2396        JFrameItem frame = makeAddIconFrame("MultiSensor", true, false, editor);
2397        _iconEditorFrame.put("MultiSensor", frame);
2398        frame.addHelpMenu("package.jmri.jmrit.display.MultiSensorIconAdder", true);
2399
2400        editor.setPickList(PickListModel.sensorPickModelInstance());
2401
2402        ActionListener addIconAction = a -> addMultiSensor();
2403        editor.makeIconPanel(true);
2404        editor.complete(addIconAction, true, true, false);
2405        return frame;
2406    }
2407
2408    protected void addIconEditor() {
2409        IconAdder editor = new IconAdder("Icon");
2410        editor.setIcon(0, "plainIcon", "resources/icons/smallschematics/tracksegments/block.gif");
2411        JFrameItem frame = makeAddIconFrame("Icon", true, false, editor);
2412        _iconEditorFrame.put("Icon", frame);
2413
2414        ActionListener addIconAction = a -> putIcon();
2415        editor.makeIconPanel(true);
2416        editor.complete(addIconAction, true, false, false);
2417        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2418    }
2419
2420    protected void addAudioEditor() {
2421        IconAdder editor = new IconAdder("Audio");
2422        editor.setIcon(0, "plainIcon", "resources/icons/audio_icon.gif");
2423        JFrameItem frame = makeAddIconFrame("Audio", true, false, editor);
2424        _iconEditorFrame.put("Audio", frame);
2425        editor.setPickList(PickListModel.audioPickModelInstance());
2426
2427        ActionListener addIconAction = a -> putAudio();
2428        editor.makeIconPanel(true);
2429        editor.complete(addIconAction, true, false, false);
2430        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2431    }
2432
2433    protected void addLogixNGEditor() {
2434        IconAdder editor = new IconAdder("LogixNG");
2435        editor.setIcon(0, "plainIcon", "resources/icons/logixng/logixng_icon.gif");
2436        JFrameItem frame = makeAddIconFrame("LogixNG", true, false, editor);
2437        _iconEditorFrame.put("LogixNG", frame);
2438
2439        ActionListener addIconAction = a -> putLogixNG();
2440        editor.makeIconPanel(true);
2441        editor.complete(addIconAction, true, false, false);
2442        frame.addHelpMenu("package.jmri.jmrit.display.IconAdder", true);
2443    }
2444
2445    /*
2446     * ************** add content items from Icon Editors *******************
2447     */
2448    /**
2449     * Add a sensor indicator to the target.
2450     *
2451     * @return The sensor that was added to the panel.
2452     */
2453    protected SensorIcon putSensor() {
2454        SensorIcon result = new SensorIcon(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
2455                "resources/icons/smallschematics/tracksegments/circuit-error.gif"), this);
2456        IconAdder editor = getIconEditor("Sensor");
2457        Hashtable<String, NamedIcon> map = editor.getIconMap();
2458        Enumeration<String> e = map.keys();
2459        while (e.hasMoreElements()) {
2460            String key = e.nextElement();
2461            result.setIcon(key, map.get(key));
2462        }
2463//        l.setActiveIcon(editor.getIcon("SensorStateActive"));
2464//        l.setInactiveIcon(editor.getIcon("SensorStateInactive"));
2465//        l.setInconsistentIcon(editor.getIcon("BeanStateInconsistent"));
2466//        l.setUnknownIcon(editor.getIcon("BeanStateUnknown"));
2467        NamedBean b = editor.getTableSelection();
2468        if (b != null) {
2469            result.setSensor(b.getDisplayName());
2470        }
2471        result.setDisplayLevel(SENSORS);
2472        setNextLocation(result);
2473        try {
2474            putItem(result);
2475        } catch (Positionable.DuplicateIdException ex) {
2476            // This should never happen
2477            log.error("Editor.putItem() with null id has thrown DuplicateIdException", ex);
2478        }
2479        return result;
2480    }
2481
2482    /**
2483     * Add a turnout indicator to the target
2484     */
2485    void addTurnoutR() {
2486        IconAdder editor = getIconEditor("RightTurnout");
2487        addTurnout(editor);
2488    }
2489
2490    void addTurnoutL() {
2491        IconAdder editor = getIconEditor("LeftTurnout");
2492        addTurnout(editor);
2493    }
2494
2495    protected TurnoutIcon addTurnout(IconAdder editor) {
2496        TurnoutIcon result = new TurnoutIcon(this);
2497        result.setTurnout(editor.getTableSelection().getDisplayName());
2498        Hashtable<String, NamedIcon> map = editor.getIconMap();
2499        Enumeration<String> e = map.keys();
2500        while (e.hasMoreElements()) {
2501            String key = e.nextElement();
2502            result.setIcon(key, map.get(key));
2503        }
2504        result.setDisplayLevel(TURNOUTS);
2505        setNextLocation(result);
2506        try {
2507            putItem(result);
2508        } catch (Positionable.DuplicateIdException ex) {
2509            // This should never happen
2510            log.error("Editor.putItem() with null id has thrown DuplicateIdException", ex);
2511        }
2512        return result;
2513    }
2514
2515    @SuppressFBWarnings(value="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification="iconEditor requested as exact type")
2516    SlipTurnoutIcon addSlip() {
2517        SlipTurnoutIcon result = new SlipTurnoutIcon(this);
2518        SlipIconAdder editor = (SlipIconAdder) getIconEditor("SlipTOEditor");
2519        result.setSingleSlipRoute(editor.getSingleSlipRoute());
2520
2521        switch (editor.getTurnoutType()) {
2522            case SlipTurnoutIcon.DOUBLESLIP:
2523                result.setLowerWestToUpperEastIcon(editor.getIcon("LowerWestToUpperEast"));
2524                result.setUpperWestToLowerEastIcon(editor.getIcon("UpperWestToLowerEast"));
2525                result.setLowerWestToLowerEastIcon(editor.getIcon("LowerWestToLowerEast"));
2526                result.setUpperWestToUpperEastIcon(editor.getIcon("UpperWestToUpperEast"));
2527                break;
2528            case SlipTurnoutIcon.SINGLESLIP:
2529                result.setLowerWestToUpperEastIcon(editor.getIcon("LowerWestToUpperEast"));
2530                result.setUpperWestToLowerEastIcon(editor.getIcon("UpperWestToLowerEast"));
2531                result.setLowerWestToLowerEastIcon(editor.getIcon("Slip"));
2532                result.setSingleSlipRoute(editor.getSingleSlipRoute());
2533                break;
2534            case SlipTurnoutIcon.THREEWAY:
2535                result.setLowerWestToUpperEastIcon(editor.getIcon("Upper"));
2536                result.setUpperWestToLowerEastIcon(editor.getIcon("Middle"));
2537                result.setLowerWestToLowerEastIcon(editor.getIcon("Lower"));
2538                result.setSingleSlipRoute(editor.getSingleSlipRoute());
2539                break;
2540            case SlipTurnoutIcon.SCISSOR: //Scissor is the same as a Double for icon storing.
2541                result.setLowerWestToUpperEastIcon(editor.getIcon("LowerWestToUpperEast"));
2542                result.setUpperWestToLowerEastIcon(editor.getIcon("UpperWestToLowerEast"));
2543                result.setLowerWestToLowerEastIcon(editor.getIcon("LowerWestToLowerEast"));
2544                //l.setUpperWestToUpperEastIcon(editor.getIcon("UpperWestToUpperEast"));
2545                break;
2546            default:
2547                log.warn("Unexpected addSlip editor.getTurnoutType() of {}", editor.getTurnoutType());
2548                break;
2549        }
2550
2551        if ((editor.getTurnoutType() == SlipTurnoutIcon.SCISSOR) && (!editor.getSingleSlipRoute())) {
2552            result.setTurnout(editor.getTurnout("lowerwest").getName(), SlipTurnoutIcon.LOWERWEST);
2553            result.setTurnout(editor.getTurnout("lowereast").getName(), SlipTurnoutIcon.LOWEREAST);
2554        }
2555        result.setInconsistentIcon(editor.getIcon("BeanStateInconsistent"));
2556        result.setUnknownIcon(editor.getIcon("BeanStateUnknown"));
2557        result.setTurnoutType(editor.getTurnoutType());
2558        result.setTurnout(editor.getTurnout("west").getName(), SlipTurnoutIcon.WEST);
2559        result.setTurnout(editor.getTurnout("east").getName(), SlipTurnoutIcon.EAST);
2560        result.setDisplayLevel(TURNOUTS);
2561        setNextLocation(result);
2562        try {
2563            putItem(result);
2564        } catch (Positionable.DuplicateIdException e) {
2565            // This should never happen
2566            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2567        }
2568        return result;
2569    }
2570
2571    /**
2572     * Add a signal head to the target.
2573     *
2574     * @return The signal head that was added to the target.
2575     */
2576    protected SignalHeadIcon putSignalHead() {
2577        SignalHeadIcon result = new SignalHeadIcon(this);
2578        IconAdder editor = getIconEditor("SignalHead");
2579        result.setSignalHead(editor.getTableSelection().getDisplayName());
2580        Hashtable<String, NamedIcon> map = editor.getIconMap();
2581        Enumeration<String> e = map.keys();
2582        while (e.hasMoreElements()) {
2583            String key = e.nextElement();
2584            result.setIcon(key, map.get(key));
2585        }
2586        result.setDisplayLevel(SIGNALS);
2587        setNextLocation(result);
2588        try {
2589            putItem(result);
2590        } catch (Positionable.DuplicateIdException ex) {
2591            // This should never happen
2592            log.error("Editor.putItem() with null id has thrown DuplicateIdException", ex);
2593        }
2594        return result;
2595    }
2596
2597    /**
2598     * Add a signal mast to the target.
2599     *
2600     * @return The signal mast that was added to the target.
2601     */
2602    protected SignalMastIcon putSignalMast() {
2603        SignalMastIcon result = new SignalMastIcon(this);
2604        IconAdder editor = _iconEditorFrame.get("SignalMast").getEditor();
2605        result.setSignalMast(editor.getTableSelection().getDisplayName());
2606        result.setDisplayLevel(SIGNALS);
2607        setNextLocation(result);
2608        try {
2609            putItem(result);
2610        } catch (Positionable.DuplicateIdException e) {
2611            // This should never happen
2612            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2613        }
2614        return result;
2615    }
2616
2617    protected MemoryIcon putMemory() {
2618        MemoryIcon result = new MemoryIcon(new NamedIcon("resources/icons/misc/X-red.gif",
2619                "resources/icons/misc/X-red.gif"), this);
2620        IconAdder memoryIconEditor = getIconEditor("Memory");
2621        result.setMemory(memoryIconEditor.getTableSelection().getDisplayName());
2622        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2623        result.setDisplayLevel(MEMORIES);
2624        setNextLocation(result);
2625        try {
2626            putItem(result);
2627        } catch (Positionable.DuplicateIdException e) {
2628            // This should never happen
2629            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2630        }
2631        return result;
2632    }
2633
2634    protected MemorySpinnerIcon addMemorySpinner() {
2635        MemorySpinnerIcon result = new MemorySpinnerIcon(this);
2636        IconAdder memoryIconEditor = getIconEditor("Memory");
2637        result.setMemory(memoryIconEditor.getTableSelection().getDisplayName());
2638        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2639        result.setDisplayLevel(MEMORIES);
2640        setNextLocation(result);
2641        try {
2642            putItem(result);
2643        } catch (Positionable.DuplicateIdException e) {
2644            // This should never happen
2645            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2646        }
2647        return result;
2648    }
2649
2650    protected MemoryInputIcon addMemoryInputBox() {
2651        MemoryInputIcon result = new MemoryInputIcon(_spinCols.getNumber().intValue(), this);
2652        IconAdder memoryIconEditor = getIconEditor("Memory");
2653        result.setMemory(memoryIconEditor.getTableSelection().getDisplayName());
2654        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2655        result.setDisplayLevel(MEMORIES);
2656        setNextLocation(result);
2657        try {
2658            putItem(result);
2659        } catch (Positionable.DuplicateIdException e) {
2660            // This should never happen
2661            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2662        }
2663        return result;
2664    }
2665
2666    protected GlobalVariableIcon putGlobalVariable() {
2667        GlobalVariableIcon result = new GlobalVariableIcon(new NamedIcon("resources/icons/misc/X-red.gif",
2668                "resources/icons/misc/X-red.gif"), this);
2669        IconAdder globalVariableIconEditor = getIconEditor("GlobalVariable");
2670        result.setGlobalVariable(globalVariableIconEditor.getTableSelection().getDisplayName());
2671        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2672        result.setDisplayLevel(MEMORIES);
2673        setNextLocation(result);
2674        try {
2675            putItem(result);
2676        } catch (Positionable.DuplicateIdException e) {
2677            // This should never happen
2678            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2679        }
2680        return result;
2681    }
2682
2683    protected GlobalVariableSpinnerIcon addGlobalVariableSpinner() {
2684        GlobalVariableSpinnerIcon result = new GlobalVariableSpinnerIcon(this);
2685        IconAdder globalVariableIconEditor = getIconEditor("GlobalVariable");
2686        result.setGlobalVariable(globalVariableIconEditor.getTableSelection().getDisplayName());
2687        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2688        result.setDisplayLevel(MEMORIES);
2689        setNextLocation(result);
2690        try {
2691            putItem(result);
2692        } catch (Positionable.DuplicateIdException e) {
2693            // This should never happen
2694            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2695        }
2696        return result;
2697    }
2698
2699    protected GlobalVariableInputIcon addGlobalVariableInputBox() {
2700        GlobalVariableInputIcon result = new GlobalVariableInputIcon(_spinCols.getNumber().intValue(), this);
2701        IconAdder globalVariableIconEditor = getIconEditor("GlobalVariable");
2702        result.setGlobalVariable(globalVariableIconEditor.getTableSelection().getDisplayName());
2703        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2704        result.setDisplayLevel(MEMORIES);
2705        setNextLocation(result);
2706        try {
2707            putItem(result);
2708        } catch (Positionable.DuplicateIdException e) {
2709            // This should never happen
2710            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2711        }
2712        return result;
2713    }
2714
2715    protected LogixNGTableIcon addLogixNGTable() {
2716        IconAdder logixNGTableIconEditor = getIconEditor("LogixNGTable");
2717        LogixNGTableIcon result = new LogixNGTableIcon(logixNGTableIconEditor.getTableSelection().getDisplayName(), this);
2718        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2719        result.setDisplayLevel(MEMORIES);
2720        setNextLocation(result);
2721        try {
2722            putItem(result);
2723        } catch (Positionable.DuplicateIdException e) {
2724            // This should never happen
2725            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2726        }
2727        return result;
2728    }
2729
2730    protected BlockContentsIcon putBlockContents() {
2731        BlockContentsIcon result = new BlockContentsIcon(new NamedIcon("resources/icons/misc/X-red.gif",
2732                "resources/icons/misc/X-red.gif"), this);
2733        IconAdder blockIconEditor = getIconEditor("BlockLabel");
2734        result.setBlock(blockIconEditor.getTableSelection().getDisplayName());
2735        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2736        result.setDisplayLevel(MEMORIES);
2737        setNextLocation(result);
2738        try {
2739            putItem(result);
2740        } catch (Positionable.DuplicateIdException e) {
2741            // This should never happen
2742            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2743        }
2744        return result;
2745    }
2746
2747    /**
2748     * Add a Light indicator to the target
2749     *
2750     * @return The light indicator that was added to the target.
2751     */
2752    protected LightIcon addLight() {
2753        LightIcon result = new LightIcon(this);
2754        IconAdder editor = getIconEditor("Light");
2755        result.setOffIcon(editor.getIcon("StateOff"));
2756        result.setOnIcon(editor.getIcon("StateOn"));
2757        result.setInconsistentIcon(editor.getIcon("BeanStateInconsistent"));
2758        result.setUnknownIcon(editor.getIcon("BeanStateUnknown"));
2759        result.setLight((Light) editor.getTableSelection());
2760        result.setDisplayLevel(LIGHTS);
2761        setNextLocation(result);
2762        try {
2763            putItem(result);
2764        } catch (Positionable.DuplicateIdException e) {
2765            // This should never happen
2766            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2767        }
2768        return result;
2769    }
2770
2771    protected ReporterIcon addReporter() {
2772        ReporterIcon result = new ReporterIcon(this);
2773        IconAdder reporterIconEditor = getIconEditor("Reporter");
2774        result.setReporter((Reporter) reporterIconEditor.getTableSelection());
2775        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2776        result.setDisplayLevel(REPORTERS);
2777        setNextLocation(result);
2778        try {
2779            putItem(result);
2780        } catch (Positionable.DuplicateIdException e) {
2781            // This should never happen
2782            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2783        }
2784        return result;
2785    }
2786
2787    /**
2788     * Button pushed, add a background image. Note that a background image
2789     * differs from a regular icon only in the level at which it's presented.
2790     */
2791    void putBackground() {
2792        // most likely the image is scaled.  get full size from URL
2793        IconAdder bkgrndEditor = getIconEditor("Background");
2794        String url = bkgrndEditor.getIcon("background").getURL();
2795        setUpBackground(url);
2796    }
2797
2798    /**
2799     * Add an icon to the target.
2800     *
2801     * @return The icon that was added to the target.
2802     */
2803    protected Positionable putIcon() {
2804        IconAdder iconEditor = getIconEditor("Icon");
2805        String url = iconEditor.getIcon("plainIcon").getURL();
2806        NamedIcon icon = NamedIcon.getIconByName(url);
2807        if (log.isDebugEnabled()) {
2808            log.debug("putIcon: {} url= {}", (icon == null ? "null" : "icon"), url);
2809        }
2810        PositionableLabel result = new PositionableLabel(icon, this);
2811//        l.setPopupUtility(null);        // no text
2812        result.setDisplayLevel(ICONS);
2813        setNextLocation(result);
2814        try {
2815            putItem(result);
2816        } catch (Positionable.DuplicateIdException e) {
2817            // This should never happen
2818            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2819        }
2820        result.updateSize();
2821        return result;
2822    }
2823
2824    /**
2825     * Add a LogixNG icon to the target.
2826     *
2827     * @return The LogixNG icon that was added to the target.
2828     */
2829    protected Positionable putAudio() {
2830        IconAdder iconEditor = getIconEditor("Audio");
2831        String url = iconEditor.getIcon("plainIcon").getURL();
2832        NamedIcon icon = NamedIcon.getIconByName(url);
2833        if (log.isDebugEnabled()) {
2834            log.debug("putAudio: {} url= {}", (icon == null ? "null" : "icon"), url);
2835        }
2836        AudioIcon result = new AudioIcon(icon, this);
2837        NamedBean b = iconEditor.getTableSelection();
2838        if (b != null) {
2839            result.setAudio(b.getDisplayName());
2840        }
2841//        l.setPopupUtility(null);        // no text
2842        result.setDisplayLevel(ICONS);
2843        setNextLocation(result);
2844        try {
2845            putItem(result);
2846        } catch (Positionable.DuplicateIdException e) {
2847            // This should never happen
2848            log.error("Editor.putAudio() with null id has thrown DuplicateIdException", e);
2849        }
2850        result.updateSize();
2851        return result;
2852    }
2853
2854    /**
2855     * Add a LogixNG icon to the target.
2856     *
2857     * @return The LogixNG icon that was added to the target.
2858     */
2859    protected Positionable putLogixNG() {
2860        IconAdder iconEditor = getIconEditor("LogixNG");
2861        String url = iconEditor.getIcon("plainIcon").getURL();
2862        NamedIcon icon = NamedIcon.getIconByName(url);
2863        if (log.isDebugEnabled()) {
2864            log.debug("putLogixNG: {} url= {}", (icon == null ? "null" : "icon"), url);
2865        }
2866        LogixNGIcon result = new LogixNGIcon(icon, this);
2867//        l.setPopupUtility(null);        // no text
2868        result.setDisplayLevel(ICONS);
2869        setNextLocation(result);
2870        try {
2871            putItem(result);
2872        } catch (Positionable.DuplicateIdException e) {
2873            // This should never happen
2874            log.error("Editor.putLogixNG() with null id has thrown DuplicateIdException", e);
2875        }
2876        result.updateSize();
2877        return result;
2878    }
2879
2880    @SuppressFBWarnings(value="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification="iconEditor requested as exact type")
2881    public MultiSensorIcon addMultiSensor() {
2882        MultiSensorIcon result = new MultiSensorIcon(this);
2883        MultiSensorIconAdder editor = (MultiSensorIconAdder) getIconEditor("MultiSensor");
2884        result.setUnknownIcon(editor.getIcon("BeanStateUnknown"));
2885        result.setInconsistentIcon(editor.getIcon("BeanStateInconsistent"));
2886        result.setInactiveIcon(editor.getIcon("SensorStateInactive"));
2887        int numPositions = editor.getNumIcons();
2888        for (int i = 3; i < numPositions; i++) {
2889            NamedIcon icon = editor.getIcon(i);
2890            String sensor = editor.getSensor(i).getName();
2891            result.addEntry(sensor, icon);
2892        }
2893        result.setUpDown(editor.getUpDown());
2894        result.setDisplayLevel(SENSORS);
2895        setNextLocation(result);
2896        try {
2897            putItem(result);
2898        } catch (Positionable.DuplicateIdException e) {
2899            // This should never happen
2900            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2901        }
2902        return result;
2903    }
2904
2905    protected AnalogClock2Display addClock() {
2906        AnalogClock2Display result = new AnalogClock2Display(this);
2907        result.setOpaque(false);
2908        result.update();
2909        result.setDisplayLevel(CLOCK);
2910        setNextLocation(result);
2911        try {
2912            putItem(result);
2913        } catch (Positionable.DuplicateIdException e) {
2914            // This should never happen
2915            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2916        }
2917        return result;
2918    }
2919
2920    protected RpsPositionIcon addRpsReporter() {
2921        RpsPositionIcon result = new RpsPositionIcon(this);
2922        result.setSize(result.getPreferredSize().width, result.getPreferredSize().height);
2923        result.setDisplayLevel(SENSORS);
2924        setNextLocation(result);
2925        try {
2926            putItem(result);
2927        } catch (Positionable.DuplicateIdException e) {
2928            // This should never happen
2929            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
2930        }
2931        return result;
2932    }
2933
2934    /*
2935     * ****************** end adding content ********************
2936     */
2937 /*
2938     * ********************* Icon Editors utils ***************************
2939     */
2940    public static class JFrameItem extends JmriJFrame {
2941
2942        private final IconAdder _editor;
2943
2944        JFrameItem(String name, IconAdder editor) {
2945            super(name);
2946            _editor = editor;
2947            setName(name);
2948        }
2949
2950        public IconAdder getEditor() {
2951            return _editor;
2952        }
2953
2954        @Override
2955        public String toString() {
2956            return this.getName();
2957        }
2958    }
2959
2960    public void setTitle() {
2961        String name = _targetFrame.getTitle();
2962        if (name == null || name.equals("")) {
2963            super.setTitle(Bundle.getMessage("LabelEditor"));
2964        } else {
2965            super.setTitle(name + " " + Bundle.getMessage("LabelEditor"));
2966        }
2967        for (JFrameItem frame : _iconEditorFrame.values()) {
2968            frame.setTitle(frame.getName() + " (" + name + ")");
2969        }
2970        setName(name);
2971    }
2972
2973    /**
2974     * Create a frame showing all images in the set used for an icon.
2975     * Opened when editItemInPanel button is clicked in the Edit Icon Panel,
2976     * shown after icon's context menu Edit Icon... item is selected.
2977     *
2978     * @param name bean type name
2979     * @param add true when used to add a new item on panel, false when used to edit an item already on the panel
2980     * @param table true for bean types presented as table instead of icons
2981     * @param editor parent frame of the image frame
2982     * @return JFrame connected to the editor,  to be filled with icons
2983     */
2984    public JFrameItem makeAddIconFrame(String name, boolean add, boolean table, IconAdder editor) {
2985        log.debug("makeAddIconFrame for {}, add= {}, table= {}", name, add, table);
2986        String txt;
2987        String bundleName;
2988        JFrameItem frame = new JFrameItem(name, editor);
2989        // use NamedBeanBundle property for basic beans like "Turnout" I18N
2990        if ("Sensor".equals(name)) {
2991            bundleName = "BeanNameSensor";
2992        } else if ("SignalHead".equals(name)) {
2993            bundleName = "BeanNameSignalHead";
2994        } else if ("SignalMast".equals(name)) {
2995            bundleName = "BeanNameSignalMast";
2996        } else if ("Memory".equals(name)) {
2997            bundleName = "BeanNameMemory";
2998        } else if ("Reporter".equals(name)) {
2999            bundleName = "BeanNameReporter";
3000        } else if ("Light".equals(name)) {
3001            bundleName = "BeanNameLight";
3002        } else if ("Turnout".equals(name)) {
3003            bundleName = "BeanNameTurnout"; // called by RightTurnout and LeftTurnout objects in TurnoutIcon.java edit() method
3004        } else if ("Block".equals(name)) {
3005            bundleName = "BeanNameBlock";
3006        } else if ("GlobalVariable".equals(name)) {
3007            bundleName = "BeanNameGlobalVariable";
3008        } else if ("LogixNGTable".equals(name)) {
3009            bundleName = "BeanNameLogixNGTable";
3010        } else if ("Audio".equals(name)) {
3011            bundleName = "BeanNameAudio";
3012        } else {
3013            bundleName = name;
3014        }
3015        if (editor != null) {
3016            JPanel p = new JPanel();
3017            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
3018            if (add) {
3019                txt = MessageFormat.format(Bundle.getMessage("addItemToPanel"), Bundle.getMessage(bundleName));
3020            } else {
3021                txt = MessageFormat.format(Bundle.getMessage("editItemInPanel"), Bundle.getMessage(bundleName));
3022            }
3023            p.add(new JLabel(txt));
3024            if (table) {
3025                txt = MessageFormat.format(Bundle.getMessage("TableSelect"), Bundle.getMessage(bundleName),
3026                        (add ? Bundle.getMessage("ButtonAddIcon") : Bundle.getMessage("ButtonUpdateIcon")));
3027            } else {
3028                if ("MultiSensor".equals(name)) {
3029                    txt = MessageFormat.format(Bundle.getMessage("SelectMultiSensor", Bundle.getMessage("ButtonAddIcon")),
3030                            (add ? Bundle.getMessage("ButtonAddIcon") : Bundle.getMessage("ButtonUpdateIcon")));
3031                } else {
3032                    txt = MessageFormat.format(Bundle.getMessage("IconSelect"), Bundle.getMessage(bundleName),
3033                            (add ? Bundle.getMessage("ButtonAddIcon") : Bundle.getMessage("ButtonUpdateIcon")));
3034                }
3035            }
3036            p.add(new JLabel(txt));
3037            p.add(new JLabel("    ")); // add a bit of space on pane above icons
3038            frame.getContentPane().add(p, BorderLayout.NORTH);
3039            frame.getContentPane().add(editor);
3040
3041            JMenuBar menuBar = new JMenuBar();
3042            JMenu findIcon = new JMenu(Bundle.getMessage("findIconMenu"));
3043            menuBar.add(findIcon);
3044
3045            JMenuItem editItem = new JMenuItem(Bundle.getMessage("editIndexMenu"));
3046            editItem.addActionListener(e -> {
3047                ImageIndexEditor ii = InstanceManager.getDefault(ImageIndexEditor.class);
3048                ii.pack();
3049                ii.setVisible(true);
3050            });
3051            findIcon.add(editItem);
3052            findIcon.addSeparator();
3053
3054            JMenuItem searchItem = new JMenuItem(Bundle.getMessage("searchFSMenu"));
3055            searchItem.addActionListener(new ActionListener() {
3056                private IconAdder ea;
3057
3058                @Override
3059                public void actionPerformed(ActionEvent e) {
3060                    InstanceManager.getDefault(DirectorySearcher.class).searchFS();
3061                    ea.addDirectoryToCatalog();
3062                }
3063
3064                ActionListener init(IconAdder ed) {
3065                    ea = ed;
3066                    return this;
3067                }
3068            }.init(editor));
3069
3070            findIcon.add(searchItem);
3071            frame.setJMenuBar(menuBar);
3072            editor.setParent(frame);
3073            // when this window closes, check for saving
3074            if (add) {
3075                frame.addWindowListener(new WindowAdapter() {
3076                    @Override
3077                    public void windowClosing(WindowEvent e) {
3078                        setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
3079                        if (log.isDebugEnabled()) {
3080                            log.debug("windowClosing: HIDE {}", toString());
3081                        }
3082                    }
3083                });
3084            }
3085        } else {
3086            log.error("No icon editor specified for {}", name); // NOI18N
3087        }
3088        if (add) {
3089            txt = MessageFormat.format(Bundle.getMessage("AddItem"), Bundle.getMessage(bundleName));
3090            _iconEditorFrame.put(name, frame);
3091        } else {
3092            txt = MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage(bundleName));
3093        }
3094        frame.setTitle(txt + " (" + getTitle() + ")");
3095        frame.pack();
3096        return frame;
3097    }
3098
3099    /*
3100     * ******************* cleanup ************************
3101     */
3102    protected void removeFromTarget(@Nonnull Positionable l) {
3103        Point p = l.getLocation();
3104        _targetPanel.remove((Component) l);
3105        _highlightcomponent = null;
3106        int w = Math.max( l.maxWidth(), l.getWidth());
3107        int h = Math.max( l.maxHeight(), l.getHeight());
3108        _targetPanel.revalidate();
3109        _targetPanel.repaint(p.x, p.y, w, h);
3110    }
3111
3112    public boolean removeFromContents(@Nonnull Positionable l) {
3113        removeFromTarget(l);
3114        //todo check that parent == _targetPanel
3115        //Container parent = this.getParent();
3116        // force redisplay
3117        if (l.getId() != null) {
3118            _idContents.remove(l.getId());
3119        }
3120        for (String className : l.getClasses()) {
3121            _classContents.get(className).remove(l);
3122        }
3123        return _contents.remove(l);
3124    }
3125
3126    /**
3127     * Ask user the user to decide what to do with LogixNGs, whether to delete them
3128     * or convert them to normal LogixNGs.  Then respond to user's choice.
3129     */
3130    private void dispositionLogixNGs() {
3131        ArrayList<LogixNG> logixNGArrayList = new ArrayList<>();
3132        for (Positionable _content : _contents) {
3133            if (_content.getLogixNG() != null) {
3134                LogixNG logixNG = _content.getLogixNG();
3135                logixNGArrayList.add(logixNG);
3136            }
3137        }
3138        if (!logixNGArrayList.isEmpty()) {
3139            LogixNGDeleteDialog logixNGDeleteDialog = new LogixNGDeleteDialog(this, getTitle(), logixNGArrayList);
3140            logixNGDeleteDialog.setVisible(true);
3141            List<LogixNG> selectedItems = logixNGDeleteDialog.getSelectedItems();
3142            for (LogixNG logixNG : selectedItems) {
3143                deleteLogixNG_Internal(logixNG);
3144                logixNGArrayList.remove(logixNG);
3145            }
3146            for (LogixNG logixNG : logixNGArrayList) {
3147                logixNG.setInline(false);
3148                logixNG.setEnabled(!logixNGDeleteDialog.isDisableLogixNG());
3149            }
3150        }
3151    }
3152
3153    /**
3154     * Ask user if panel should be deleted. The caller should dispose the panel
3155     * to delete it.
3156     *
3157     * @return true if panel should be deleted.
3158     */
3159    public boolean deletePanel() {
3160        log.debug("deletePanel");
3161        // verify deletion
3162        int selectedValue = JmriJOptionPane.showOptionDialog(_targetPanel,
3163                Bundle.getMessage("QuestionA") + "\n" + Bundle.getMessage("QuestionA2",
3164                    Bundle.getMessage("FileMenuItemStore")),
3165                Bundle.getMessage("DeleteVerifyTitle"), JmriJOptionPane.DEFAULT_OPTION,
3166                JmriJOptionPane.QUESTION_MESSAGE, null,
3167                new Object[]{Bundle.getMessage("ButtonYesDelete"), Bundle.getMessage("ButtonCancel")},
3168                Bundle.getMessage("ButtonCancel"));
3169        // return without deleting if "Cancel" or Cancel Dialog response
3170        if (selectedValue == 0) {
3171            dispositionLogixNGs();
3172        }
3173        return (selectedValue == 0 ); // array position 0 = Yes, Delete.
3174    }
3175
3176    /**
3177     * Dispose of the editor.
3178     */
3179    @Override
3180    public void dispose() {
3181        for (JFrameItem frame : _iconEditorFrame.values()) {
3182            frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
3183            frame.dispose();
3184        }
3185        // delete panel - deregister the panel for saving
3186        ConfigureManager cm = InstanceManager.getNullableDefault(ConfigureManager.class);
3187        if (cm != null) {
3188            cm.deregister(this);
3189        }
3190        InstanceManager.getDefault(EditorManager.class).remove(this);
3191        setVisible(false);
3192        _contents.clear();
3193        _idContents.clear();
3194        for (var list : _classContents.values()) {
3195            list.clear();
3196        }
3197        _classContents.clear();
3198        removeAll();
3199        super.dispose();
3200    }
3201
3202    /*
3203     * **************** Mouse Methods **********************
3204     */
3205    public void showToolTip(Positionable selection, JmriMouseEvent event) {
3206        ToolTip tip = selection.getToolTip();
3207        tip.setLocation(selection.getX() + selection.getWidth() / 2, selection.getY() + selection.getHeight());
3208        setToolTip(tip);
3209    }
3210
3211    protected int getItemX(Positionable p, int deltaX) {
3212        if ((p instanceof MemoryOrGVIcon) && (p.getPopupUtility().getFixedWidth() == 0)) {
3213            MemoryOrGVIcon pm = (MemoryOrGVIcon) p;
3214            return pm.getOriginalX() + (int) Math.round(deltaX / getPaintScale());
3215        } else {
3216            return p.getX() + (int) Math.round(deltaX / getPaintScale());
3217        }
3218    }
3219
3220    protected int getItemY(Positionable p, int deltaY) {
3221        if ((p instanceof MemoryOrGVIcon) && (p.getPopupUtility().getFixedWidth() == 0)) {
3222            MemoryOrGVIcon pm = (MemoryOrGVIcon) p;
3223            return pm.getOriginalY() + (int) Math.round(deltaY / getPaintScale());
3224        } else {
3225            return p.getY() + (int) Math.round(deltaY / getPaintScale());
3226        }
3227    }
3228
3229    /**
3230     * Provide a method for external code to add items to context menus.
3231     *
3232     * @param nb   The namedBean associated with the postionable item.
3233     * @param item The entry to add to the menu.
3234     * @param menu The menu to add the entry to.
3235     */
3236    public void addToPopUpMenu(NamedBean nb, JMenuItem item, int menu) {
3237        if (nb == null || item == null) {
3238            return;
3239        }
3240        for (Positionable pos : _contents) {
3241            if (pos.getNamedBean() == nb && pos.getPopupUtility() != null) {
3242                addToPopUpMenu( pos, item, menu);
3243                return;
3244            } else if (pos instanceof SlipTurnoutIcon) {
3245                if (pos.getPopupUtility() != null) {
3246                    SlipTurnoutIcon sti = (SlipTurnoutIcon) pos;
3247                    if ( sti.getTurnout(SlipTurnoutIcon.EAST) == nb || sti.getTurnout(SlipTurnoutIcon.WEST) == nb
3248                        || sti.getTurnout(SlipTurnoutIcon.LOWEREAST) == nb
3249                        || sti.getTurnout(SlipTurnoutIcon.LOWERWEST) == nb) {
3250                        addToPopUpMenu( pos, item, menu);
3251                        return;
3252                    }
3253                }
3254            } else if ( pos instanceof MultiSensorIcon && pos.getPopupUtility() != null) {
3255                MultiSensorIcon msi = (MultiSensorIcon) pos;
3256                for (int i = 0; i < msi.getNumEntries(); i++) {
3257                    if ( msi.getSensorName(i).equals(nb.getUserName())
3258                        || msi.getSensorName(i).equals(nb.getSystemName())) {
3259                        addToPopUpMenu( pos, item, menu);
3260                        return;
3261                    }
3262                }
3263            }
3264        }
3265    }
3266
3267    private void addToPopUpMenu( Positionable pos, JMenuItem item, int menu ) {
3268        switch (menu) {
3269            case VIEWPOPUPONLY:
3270                pos.getPopupUtility().addViewPopUpMenu(item);
3271                break;
3272            case EDITPOPUPONLY:
3273                pos.getPopupUtility().addEditPopUpMenu(item);
3274                break;
3275            default:
3276                pos.getPopupUtility().addEditPopUpMenu(item);
3277                pos.getPopupUtility().addViewPopUpMenu(item);
3278        }
3279    }
3280
3281    public static final int VIEWPOPUPONLY = 0x00;
3282    public static final int EDITPOPUPONLY = 0x01;
3283    public static final int BOTHPOPUPS = 0x02;
3284
3285    /**
3286     * Relocate item.
3287     * <p>
3288     * Note that items can not be moved past the left or top edges of the panel.
3289     *
3290     * @param p      The item to move.
3291     * @param deltaX The horizontal displacement.
3292     * @param deltaY The vertical displacement.
3293     */
3294    public void moveItem(Positionable p, int deltaX, int deltaY) {
3295        //log.debug("moveItem at ({},{}) delta ({},{})", p.getX(), p.getY(), deltaX, deltaY);
3296        if (getFlag(OPTION_POSITION, p.isPositionable())) {
3297            int xObj = getItemX(p, deltaX);
3298            int yObj = getItemY(p, deltaY);
3299            // don't allow negative placement, icon can become unreachable
3300            if (xObj < 0) {
3301                xObj = 0;
3302            }
3303            if (yObj < 0) {
3304                yObj = 0;
3305            }
3306            p.setLocation(xObj, yObj);
3307            // and show!
3308            p.repaint();
3309        }
3310    }
3311
3312    /**
3313     * Return a List of all items whose bounding rectangle contain the mouse
3314     * position. ordered from top level to bottom
3315     *
3316     * @param event contains the mouse position.
3317     * @return a list of positionable items or an empty list.
3318     */
3319    protected List<Positionable> getSelectedItems(JmriMouseEvent event) {
3320        Rectangle rect = new Rectangle();
3321        ArrayList<Positionable> selections = new ArrayList<>();
3322        for (Positionable p : _contents) {
3323            double x = event.getX();
3324            double y = event.getY();
3325            rect = p.getBounds(rect);
3326            if (p instanceof jmri.jmrit.display.controlPanelEditor.shape.PositionableShape
3327                    && p.getDegrees() != 0) {
3328                double rad = p.getDegrees() * Math.PI / 180.0;
3329                java.awt.geom.AffineTransform t = java.awt.geom.AffineTransform.getRotateInstance(-rad);
3330                double[] pt = new double[2];
3331                // bit shift to avoid SpotBugs paranoia
3332                pt[0] = x - rect.x - (rect.width >>> 1);
3333                pt[1] = y - rect.y - (rect.height >>> 1);
3334                t.transform(pt, 0, pt, 0, 1);
3335                x = pt[0] + rect.x + (rect.width >>> 1);
3336                y = pt[1] + rect.y + (rect.height >>> 1);
3337            }
3338            Rectangle2D.Double rect2D = new Rectangle2D.Double(rect.x * _paintScale,
3339                    rect.y * _paintScale,
3340                    rect.width * _paintScale,
3341                    rect.height * _paintScale);
3342            if (rect2D.contains(x, y) && (p.getDisplayLevel() > BKG || event.isControlDown())) {
3343                boolean added = false;
3344                int level = p.getDisplayLevel();
3345                for (int k = 0; k < selections.size(); k++) {
3346                    if (level >= selections.get(k).getDisplayLevel()) {
3347                        selections.add(k, p);
3348                        added = true;       // OK to lie in the case of background icon
3349                        break;
3350                    }
3351                }
3352                if (!added) {
3353                    selections.add(p);
3354                }
3355            }
3356        }
3357        //log.debug("getSelectedItems at ({},{}) {} found,", x, y, selections.size());
3358        return selections;
3359    }
3360
3361    /*
3362     * Gather all items inside _selectRect
3363     * Keep old group if Control key is down
3364     */
3365    protected void makeSelectionGroup(JmriMouseEvent event) {
3366        if (!event.isControlDown() || _selectionGroup == null) {
3367            _selectionGroup = new ArrayList<>();
3368        }
3369        Rectangle test = new Rectangle();
3370        List<Positionable> list = getContents();
3371        if (event.isShiftDown()) {
3372            for (Positionable comp : list) {
3373                if (_selectRect.intersects(comp.getBounds(test))
3374                        && (event.isControlDown() || comp.getDisplayLevel() > BKG)) {
3375                    _selectionGroup.add(comp);
3376                    //log.debug("makeSelectionGroup: selection: {}, class= {}", comp.getNameString(), comp.getClass().getName());
3377                }
3378            }
3379        } else {
3380            for (Positionable comp : list) {
3381                if (_selectRect.contains(comp.getBounds(test))
3382                        && (event.isControlDown() || comp.getDisplayLevel() > BKG)) {
3383                    _selectionGroup.add(comp);
3384                    //log.debug("makeSelectionGroup: selection: {}, class= {}", comp.getNameString(), comp.getClass().getName());
3385                }
3386            }
3387        }
3388        log.debug("makeSelectionGroup: {} selected.", _selectionGroup.size());
3389        if (_selectionGroup.size() < 1) {
3390            _selectRect = null;
3391            deselectSelectionGroup();
3392        }
3393    }
3394
3395    /*
3396     * For the param, selection, Add to or delete from _selectionGroup.
3397     * If not there, add.
3398     * If there, delete.
3399     * make new group if Cntl key is not held down
3400     */
3401    protected void modifySelectionGroup(Positionable selection, JmriMouseEvent event) {
3402        if (!event.isControlDown() || _selectionGroup == null) {
3403            _selectionGroup = new ArrayList<>();
3404        }
3405        boolean removed = false;
3406        if (event.isControlDown()) {
3407            if (selection.getDisplayLevel() > BKG) {
3408                if (_selectionGroup.contains(selection)) {
3409                    removed = _selectionGroup.remove(selection);
3410                } else {
3411                    _selectionGroup.add(selection);
3412                }
3413            } else if (event.isShiftDown()) {
3414                if (_selectionGroup.contains(selection)) {
3415                    removed = _selectionGroup.remove(selection);
3416                } else {
3417                    _selectionGroup.add(selection);
3418                }
3419            }
3420        }
3421        log.debug("modifySelectionGroup: size= {}, selection {}",
3422            _selectionGroup.size(), (removed ? "removed" : "added"));
3423    }
3424
3425    /**
3426     * Set attributes of a Positionable.
3427     *
3428     * @param newUtil helper from which to get attributes
3429     * @param p       the item to set attributes of
3430     *
3431     */
3432    public void setAttributes(PositionablePopupUtil newUtil, Positionable p) {
3433        p.setPopupUtility(newUtil.clone(p, p.getTextComponent()));
3434        int mar = newUtil.getMargin();
3435        int bor = newUtil.getBorderSize();
3436        Border outlineBorder;
3437        if (bor == 0) {
3438            outlineBorder = BorderFactory.createEmptyBorder(0, 0, 0, 0);
3439        } else {
3440            outlineBorder = new LineBorder(newUtil.getBorderColor(), bor);
3441        }
3442        Border borderMargin;
3443        if (newUtil.hasBackground()) {
3444            borderMargin = new LineBorder(p.getBackground(), mar);
3445        } else {
3446            borderMargin = BorderFactory.createEmptyBorder(mar, mar, mar, mar);
3447        }
3448        p.setBorder(new CompoundBorder(outlineBorder, borderMargin));
3449
3450        if (p instanceof PositionableLabel) {
3451            PositionableLabel pos = (PositionableLabel) p;
3452            if (pos.isText()) {
3453                int deg = pos.getDegrees();
3454                pos.rotate(0);
3455                if (deg == 0) {
3456                    p.setOpaque(newUtil.hasBackground());
3457                } else {
3458                    pos.rotate(deg);
3459                }
3460            }
3461        } else if (p instanceof PositionableJPanel) {
3462            p.setOpaque(newUtil.hasBackground());
3463            p.getTextComponent().setOpaque(newUtil.hasBackground());
3464        }
3465        p.updateSize();
3466        p.repaint();
3467        if (p instanceof PositionableIcon) {
3468            NamedBean bean = p.getNamedBean();
3469            if (bean != null) {
3470                ((PositionableIcon) p).displayState(bean.getState());
3471            }
3472        }
3473    }
3474
3475    protected void setSelectionsAttributes(PositionablePopupUtil util, Positionable pos) {
3476        if (_selectionGroup != null && _selectionGroup.contains(pos)) {
3477            for (Positionable p : _selectionGroup) {
3478                if (p instanceof PositionableLabel) {
3479                    setAttributes(util, p);
3480                }
3481            }
3482        }
3483    }
3484
3485    protected void setSelectionsHidden(boolean enabled, Positionable p) {
3486        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3487            for (Positionable comp : _selectionGroup) {
3488                comp.setHidden(enabled);
3489            }
3490        }
3491    }
3492
3493    protected boolean setSelectionsPositionable(boolean enabled, Positionable p) {
3494        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3495            for (Positionable comp : _selectionGroup) {
3496                comp.setPositionable(enabled);
3497            }
3498            return true;
3499        } else {
3500            return false;
3501        }
3502    }
3503
3504    protected void removeSelections(Positionable p) {
3505        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3506            for (Positionable comp : _selectionGroup) {
3507                comp.remove();
3508            }
3509            deselectSelectionGroup();
3510        }
3511    }
3512
3513    protected void setSelectionsScale(double s, Positionable p) {
3514        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3515            for (Positionable comp : _selectionGroup) {
3516                comp.setScale(s);
3517            }
3518        } else {
3519            p.setScale(s);
3520        }
3521    }
3522
3523    protected void setSelectionsRotation(int k, Positionable p) {
3524        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3525            for (Positionable comp : _selectionGroup) {
3526                comp.rotate(k);
3527            }
3528        } else {
3529            p.rotate(k);
3530        }
3531    }
3532
3533    protected void setSelectionsDisplayLevel(int k, Positionable p) {
3534        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3535            for (Positionable comp : _selectionGroup) {
3536                comp.setDisplayLevel(k);
3537            }
3538        } else {
3539            p.setDisplayLevel(k);
3540        }
3541    }
3542
3543    protected void setSelectionsDockingLocation(Positionable p) {
3544        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3545            for (Positionable pos : _selectionGroup) {
3546                if (pos instanceof LocoIcon) {
3547                    ((LocoIcon) pos).setDockingLocation(pos.getX(), pos.getY());
3548                }
3549            }
3550        } else if (p instanceof LocoIcon) {
3551            ((LocoIcon) p).setDockingLocation(p.getX(), p.getY());
3552        }
3553    }
3554
3555    protected void dockSelections(Positionable p) {
3556        if (_selectionGroup != null && _selectionGroup.contains(p)) {
3557            for (Positionable pos : _selectionGroup) {
3558                if (pos instanceof LocoIcon) {
3559                    ((LocoIcon) pos).dock();
3560                }
3561            }
3562        } else if (p instanceof LocoIcon) {
3563            ((LocoIcon) p).dock();
3564        }
3565    }
3566
3567    protected boolean showAlignPopup(Positionable p) {
3568        return _selectionGroup != null && _selectionGroup.contains(p);
3569    }
3570
3571    public Rectangle getSelectRect() {
3572        return _selectRect;
3573    }
3574
3575    public void drawSelectRect(int x, int y) {
3576        int aX = getAnchorX();
3577        int aY = getAnchorY();
3578        int w = x - aX;
3579        int h = y - aY;
3580        if (x < aX) {
3581            aX = x;
3582            w = -w;
3583        }
3584        if (y < aY) {
3585            aY = y;
3586            h = -h;
3587        }
3588        _selectRect = new Rectangle((int) Math.round(aX / _paintScale), (int) Math.round(aY / _paintScale),
3589                (int) Math.round(w / _paintScale), (int) Math.round(h / _paintScale));
3590    }
3591
3592    public final int getAnchorX() {
3593        return _anchorX;
3594    }
3595
3596    public final int getAnchorY() {
3597        return _anchorY;
3598    }
3599
3600    public final int getLastX() {
3601        return _lastX;
3602    }
3603
3604    public final int getLastY() {
3605        return _lastY;
3606    }
3607
3608    @Override
3609    public void keyTyped(KeyEvent e) {
3610    }
3611
3612    @Override
3613    public void keyPressed(KeyEvent e) {
3614        if (_selectionGroup == null) {
3615            return;
3616        }
3617        int x = 0;
3618        int y = 0;
3619        switch (e.getKeyCode()) {
3620            case KeyEvent.VK_UP:
3621                y = -1;
3622                break;
3623            case KeyEvent.VK_DOWN:
3624                y = 1;
3625                break;
3626            case KeyEvent.VK_LEFT:
3627                x = -1;
3628                break;
3629            case KeyEvent.VK_RIGHT:
3630                x = 1;
3631                break;
3632            default:
3633                log.debug("Unexpected e.getKeyCode() of {}", e.getKeyCode());
3634                break;
3635        }
3636        //A cheat if the shift key isn't pressed then we move 5 pixels at a time.
3637        if (!e.isShiftDown()) {
3638            y *= 5;
3639            x *= 5;
3640        }
3641        for (Positionable comp : _selectionGroup) {
3642            moveItem(comp, x, y);
3643        }
3644        _targetPanel.repaint();
3645    }
3646
3647    @Override
3648    public void keyReleased(KeyEvent e) {
3649    }
3650
3651    @Override
3652    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
3653        NamedBean nb = (NamedBean) evt.getOldValue();
3654        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
3655            StringBuilder message = new StringBuilder();
3656            message.append(Bundle.getMessage("VetoInUseEditorHeader", getName())); // NOI18N
3657            message.append("<br>");
3658            boolean found = false;
3659            int count = 0;
3660            for (Positionable p : _contents) {
3661                if (nb.equals(p.getNamedBean())) {
3662                    found = true;
3663                    count++;
3664                }
3665            }
3666            if (found) {
3667                message.append(Bundle.getMessage("VetoFoundInPanel", count));
3668                message.append("<br>");
3669                message.append(Bundle.getMessage("VetoReferencesWillBeRemoved")); // NOI18N
3670                message.append("<br>");
3671                throw new PropertyVetoException(message.toString(), evt);
3672            }
3673        } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N
3674            ArrayList<Positionable> toDelete = new ArrayList<>();
3675            for (Positionable p : _contents) {
3676                if (nb.equals(p.getNamedBean())) {
3677                    toDelete.add(p);
3678                }
3679            }
3680            for (Positionable p : toDelete) {
3681                removeFromContents(p);
3682                _targetPanel.repaint();
3683            }
3684        }
3685    }
3686
3687    /*
3688     * ********************* Abstract Methods ***********************
3689     */
3690    @Override
3691    public abstract void mousePressed(JmriMouseEvent event);
3692
3693    @Override
3694    public abstract void mouseReleased(JmriMouseEvent event);
3695
3696    @Override
3697    public abstract void mouseClicked(JmriMouseEvent event);
3698
3699    @Override
3700    public abstract void mouseDragged(JmriMouseEvent event);
3701
3702    @Override
3703    public abstract void mouseMoved(JmriMouseEvent event);
3704
3705    @Override
3706    public abstract void mouseEntered(JmriMouseEvent event);
3707
3708    @Override
3709    public abstract void mouseExited(JmriMouseEvent event);
3710
3711    /*
3712     * set up target panel, frame etc.
3713     */
3714    protected abstract void init(String name);
3715
3716    /*
3717     * Closing of Target frame window.
3718     */
3719    protected abstract void targetWindowClosingEvent(WindowEvent e);
3720
3721    /**
3722     * Called from TargetPanel's paint method for additional drawing by editor
3723     * view.
3724     *
3725     * @param g the context to paint within
3726     */
3727    protected abstract void paintTargetPanel(Graphics g);
3728
3729    /**
3730     * Set an object's location when it is created.
3731     *
3732     * @param obj the object to locate
3733     */
3734    public abstract void setNextLocation(Positionable obj);
3735
3736    /**
3737     * After construction, initialize all the widgets to their saved config
3738     * settings.
3739     */
3740    protected abstract void initView();
3741
3742    /**
3743     * Set up item(s) to be copied by paste.
3744     *
3745     * @param p the item to copy
3746     */
3747    protected abstract void copyItem(Positionable p);
3748
3749    @CheckForNull
3750    public Rectangle2D resizePanelBounds(boolean forceFlag) {
3751        // Do nothing. This method is overridden by LayoutEditor.
3752        return null;
3753    }
3754
3755    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
3756        List<NamedBeanUsageReport> report = new ArrayList<>();
3757        if (bean != null) {
3758            getContents().forEach( pos -> {
3759                String data = getUsageData(pos);
3760                if (pos instanceof MultiSensorIcon) {
3761                    MultiSensorIcon multi = (MultiSensorIcon) pos;
3762                    multi.getSensors().forEach( sensor -> {
3763                        if (bean.equals(sensor)) {
3764                            report.add(new NamedBeanUsageReport("PositionalIcon", data));
3765                        }
3766                    });
3767
3768                } else if (pos instanceof SlipTurnoutIcon) {
3769                    SlipTurnoutIcon slip3Scissor = (SlipTurnoutIcon) pos;
3770                    if (bean.equals(slip3Scissor.getTurnout(SlipTurnoutIcon.EAST))) {
3771                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3772                    }
3773                    if (bean.equals(slip3Scissor.getTurnout(SlipTurnoutIcon.WEST))) {
3774                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3775                    }
3776                    if ( slip3Scissor.getNamedTurnout(SlipTurnoutIcon.LOWEREAST) != null
3777                        && bean.equals(slip3Scissor.getTurnout(SlipTurnoutIcon.LOWEREAST))) {
3778                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3779                    }
3780                    if ( slip3Scissor.getNamedTurnout(SlipTurnoutIcon.LOWERWEST) != null
3781                        && bean.equals(slip3Scissor.getTurnout(SlipTurnoutIcon.LOWERWEST))) {
3782                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3783                    }
3784
3785                } else if (pos instanceof LightIcon) {
3786                    LightIcon icon = (LightIcon) pos;
3787                    if (bean.equals(icon.getLight())) {
3788                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3789                    }
3790
3791                } else if (pos instanceof ReporterIcon) {
3792                    ReporterIcon icon = (ReporterIcon) pos;
3793                    if (bean.equals(icon.getReporter())) {
3794                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3795                    }
3796
3797                } else if (pos instanceof AudioIcon) {
3798                    AudioIcon icon = (AudioIcon) pos;
3799                    if (bean.equals(icon.getAudio())) {
3800                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3801                    }
3802
3803                } else {
3804                    if ( bean.equals(pos.getNamedBean())) {
3805                        report.add(new NamedBeanUsageReport("PositionalIcon", data));
3806                    }
3807               }
3808            });
3809        }
3810        return report;
3811    }
3812
3813    String getUsageData(Positionable pos) {
3814        Point point = pos.getLocation();
3815        return String.format("%s :: x=%d, y=%d",
3816                pos.getClass().getSimpleName(),
3817                Math.round(point.getX()),
3818                Math.round(point.getY()));
3819    }
3820
3821    // initialize logging
3822    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Editor.class);
3823
3824}