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