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