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