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