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