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