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