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