001package jmri.jmrit.display.panelEditor;
002
003import java.awt.Color;
004import java.awt.Component;
005import java.awt.Dimension;
006import java.awt.FlowLayout;
007import java.awt.Font;
008import java.awt.Graphics;
009import java.awt.Rectangle;
010import java.awt.event.ActionEvent;
011import java.awt.event.ActionListener;
012import java.awt.event.ItemEvent;
013import java.awt.event.ItemListener;
014import java.awt.event.KeyAdapter;
015import java.awt.event.KeyEvent;
016import java.awt.event.WindowAdapter;
017import java.lang.reflect.InvocationTargetException;
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.List;
022
023import javax.swing.AbstractAction;
024import javax.swing.BoxLayout;
025import javax.swing.JButton;
026import javax.swing.JCheckBox;
027import javax.swing.JCheckBoxMenuItem;
028import javax.swing.JComboBox;
029import javax.swing.JComponent;
030import javax.swing.JDialog;
031import javax.swing.JFrame;
032import javax.swing.JLabel;
033import javax.swing.JMenu;
034import javax.swing.JMenuBar;
035import javax.swing.JMenuItem;
036import javax.swing.JPanel;
037import javax.swing.JPopupMenu;
038import javax.swing.JTextField;
039
040import jmri.CatalogTreeManager;
041import jmri.ConfigureManager;
042import jmri.InstanceManager;
043import jmri.configurexml.ConfigXmlManager;
044import jmri.configurexml.XmlAdapter;
045import jmri.jmrit.catalog.ImageIndexEditor;
046import jmri.jmrit.display.*;
047import jmri.util.JmriJFrame;
048import jmri.util.gui.GuiLafPreferencesManager;
049import jmri.util.swing.JmriColorChooser;
050import jmri.util.swing.JmriJOptionPane;
051import jmri.util.swing.JmriMouseEvent;
052
053import org.jdom2.Element;
054
055/**
056 * Provides a simple editor for adding jmri.jmrit.display items to a captive
057 * JFrame.
058 * <p>
059 * GUI is structured as a band of common parameters across the top, then a
060 * series of things you can add.
061 * <p>
062 * All created objects are put specific levels depending on their type (higher
063 * levels are in front):
064 * <ul>
065 *   <li>BKG background
066 *   <li>ICONS icons and other drawing symbols
067 *   <li>LABELS text labels
068 *   <li>TURNOUTS turnouts and other variable track items
069 *   <li>SENSORS sensors and other independently modified objects
070 * </ul>
071 * <p>
072 * The "contents" List keeps track of all the objects added to the target frame
073 * for later manipulation.
074 * <p>
075 * If you close the Editor window, the target is left alone and the editor
076 * window is just hidden, not disposed. If you close the target, the editor and
077 * target are removed, and dispose is run. To make this logic work, the
078 * PanelEditor is descended from a JFrame, not a JPanel. That way it can control
079 * its own visibility.
080 * <p>
081 * The title of the target and the editor panel are kept consistent via the
082 * {#setTitle} method.
083 *
084 * @author Bob Jacobsen Copyright (c) 2002, 2003, 2007
085 * @author Dennis Miller 2004
086 * @author Howard G. Penny Copyright (c) 2005
087 * @author Matthew Harris Copyright (c) 2009
088 * @author Pete Cressman Copyright (c) 2009, 2010
089 */
090public class PanelEditor extends Editor implements ItemListener {
091
092    private static final String SENSOR = "Sensor";
093    private static final String SIGNAL_HEAD = "SignalHead";
094    private static final String SIGNAL_MAST = "SignalMast";
095    private static final String MEMORY = "Memory";
096    private static final String RIGHT_TURNOUT = "RightTurnout";
097    private static final String LEFT_TURNOUT = "LeftTurnout";
098    private static final String SLIP_TO_EDITOR = "SlipTOEditor";
099    private static final String BLOCK_LABEL = "BlockLabel";
100    private static final String REPORTER = "Reporter";
101    private static final String LIGHT = "Light";
102    private static final String BACKGROUND = "Background";
103    private static final String MULTI_SENSOR = "MultiSensor";
104    private static final String RPSREPORTER = "RPSreporter";
105    private static final String FAST_CLOCK = "FastClock";
106    private static final String GLOBAL_VARIABLE = "GlobalVariable";
107    private static final String ICON = "Icon";
108    private static final String AUDIO = "Audio";
109    private static final String LOGIXNG = "LogixNG";
110    private final JTextField nextX = new JTextField("0", 4);
111    private final JTextField nextY = new JTextField("0", 4);
112
113    private final JCheckBox editableBox = new JCheckBox(Bundle.getMessage("CheckBoxEditable"));
114    private final JCheckBox positionableBox = new JCheckBox(Bundle.getMessage("CheckBoxPositionable"));
115    private final JCheckBox controllingBox = new JCheckBox(Bundle.getMessage("CheckBoxControlling"));
116    //private JCheckBox showCoordinatesBox = new JCheckBox(Bundle.getMessage("CheckBoxShowCoordinates"));
117    private final JCheckBox showTooltipBox = new JCheckBox(Bundle.getMessage("CheckBoxShowTooltips"));
118    private final JCheckBox hiddenBox = new JCheckBox(Bundle.getMessage("CheckBoxHidden"));
119    private final JCheckBox menuBox = new JCheckBox(Bundle.getMessage("CheckBoxMenuBar"));
120    private final JLabel scrollableLabel = new JLabel(Bundle.getMessage("ComboBoxScrollable"));
121    private final JComboBox<String> scrollableComboBox = new JComboBox<>();
122
123    private final JButton labelAdd = new JButton(Bundle.getMessage("ButtonAddText"));
124    private final JTextField nextLabel = new JTextField(10);
125
126    private JComboBox<ComboBoxItem> _addIconBox;
127
128    public PanelEditor() {
129    }
130
131    public PanelEditor(String name) {
132        super(name, false, true);
133        init(name);
134    }
135
136    @Override
137    protected void init(String name) {
138        java.awt.Container contentPane = this.getContentPane();
139        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
140        // common items
141        JPanel common = new JPanel();
142        common.setLayout(new FlowLayout());
143        common.add(new JLabel(" x:"));
144        common.add(nextX);
145        common.add(new JLabel(" y:"));
146        common.add(nextY);
147        contentPane.add(common);
148        setAllEditable(true);
149        setShowHidden(true);
150        super.setTargetPanel(null, makeFrame(name));
151        super.setTargetPanelSize(400, 300);
152        super.setDefaultToolTip(new ToolTip(null, 0, 0, new Font("SansSerif", Font.PLAIN, 12),
153                Color.black, new Color(215, 225, 255), Color.black, null));
154        // set scrollbar initial state
155        setScroll(SCROLL_BOTH);
156
157        // add menu - not using PanelMenu, because it now
158        // has other stuff in it?
159        JMenuBar menuBar = new JMenuBar();
160        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
161        menuBar.add(fileMenu);
162        fileMenu.add(new jmri.jmrit.display.NewPanelAction(Bundle.getMessage("MenuItemNew")));
163        fileMenu.add(new jmri.configurexml.StoreXmlUserAction(Bundle.getMessage("FileMenuItemStore")));
164        JMenuItem storeIndexItem = new JMenuItem(Bundle.getMessage("MIStoreImageIndex"));
165        fileMenu.add(storeIndexItem);
166        storeIndexItem.addActionListener(event -> InstanceManager.getDefault(CatalogTreeManager.class).storeImageIndex());
167        JMenuItem editItem = new JMenuItem(Bundle.getMessage("editIndexMenu"));
168        editItem.addActionListener(e -> {
169            ImageIndexEditor ii = InstanceManager.getDefault(ImageIndexEditor.class);
170            ii.pack();
171            ii.setVisible(true);
172        });
173        fileMenu.add(editItem);
174
175        editItem = new JMenuItem(Bundle.getMessage("CPEView"));
176        fileMenu.add(editItem);
177        editItem.addActionListener(event -> changeView("jmri.jmrit.display.controlPanelEditor.ControlPanelEditor"));
178
179        fileMenu.addSeparator();
180        JMenuItem deleteItem = new JMenuItem(Bundle.getMessage("DeletePanel"));
181        fileMenu.add(deleteItem);
182        deleteItem.addActionListener(event -> {
183            if (deletePanel()) {
184                getTargetFrame().dispose();
185                dispose();
186            }
187        });
188
189        setJMenuBar(menuBar);
190        addHelpMenu("package.jmri.jmrit.display.PanelEditor", true);
191
192        // allow renaming the panel
193        {
194            JPanel namep = new JPanel();
195            namep.setLayout(new FlowLayout());
196            JButton b = new JButton(Bundle.getMessage("renamePanelMenu", "..."));
197            b.addActionListener(new ActionListener() {
198                PanelEditor editor;
199
200                @Override
201                public void actionPerformed(ActionEvent e) {
202                    JFrame frame = getTargetFrame();
203                    String oldName = frame.getTitle();
204                    // prompt for name
205                    String newName = JmriJOptionPane.showInputDialog(null, Bundle.getMessage("PromptNewName"), oldName);
206                    if ((newName == null) || (oldName.equals(newName))) {
207                        return;  // cancelled
208                    }
209                    if (InstanceManager.getDefault(EditorManager.class).contains(newName)) {
210                        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CanNotRename"), Bundle.getMessage("PanelExist"),
211                                JmriJOptionPane.ERROR_MESSAGE);
212                        return;
213                    }
214                    frame.setTitle(newName);
215                    editor.setTitle();
216                }
217
218                ActionListener init(PanelEditor e) {
219                    editor = e;
220                    return this;
221                }
222            }.init(this));
223            namep.add(b);
224            this.getContentPane().add(namep);
225        }
226        // add a text label
227        {
228            JPanel panel = new JPanel();
229            panel.setLayout(new FlowLayout());
230            panel.add(labelAdd);
231            labelAdd.setEnabled(false);
232            labelAdd.setToolTipText(Bundle.getMessage("ToolTipWillActivate"));
233            panel.add(nextLabel);
234            labelAdd.addActionListener(new ActionListener() {
235                PanelEditor editor;
236
237                @Override
238                public void actionPerformed(ActionEvent a) {
239                    editor.addLabel(nextLabel.getText());
240                }
241
242                ActionListener init(PanelEditor e) {
243                    editor = e;
244                    return this;
245                }
246            }.init(this));
247            nextLabel.addKeyListener(new KeyAdapter() {
248                @Override
249                public void keyReleased(KeyEvent a) {
250                    if (nextLabel.getText().equals("")) {
251                        labelAdd.setEnabled(false);
252                        labelAdd.setToolTipText(Bundle.getMessage("ToolTipWillActivate"));
253                    } else {
254                        labelAdd.setEnabled(true);
255                        labelAdd.setToolTipText(null);
256                    }
257                }
258            });
259            this.getContentPane().add(panel);
260        }
261
262        // Selection of the type of entity for the icon to represent is done from a combobox
263        _addIconBox = new JComboBox<>();
264        _addIconBox.setMinimumSize(new Dimension(75, 75));
265        _addIconBox.setMaximumSize(new Dimension(200, 200));
266        _addIconBox.addItem(new ComboBoxItem(RIGHT_TURNOUT));
267        _addIconBox.addItem(new ComboBoxItem(LEFT_TURNOUT));
268        _addIconBox.addItem(new ComboBoxItem(SLIP_TO_EDITOR));
269        _addIconBox.addItem(new ComboBoxItem(SENSOR)); // NOI18N
270        _addIconBox.addItem(new ComboBoxItem(SIGNAL_HEAD));
271        _addIconBox.addItem(new ComboBoxItem(SIGNAL_MAST));
272        _addIconBox.addItem(new ComboBoxItem(MEMORY));
273        _addIconBox.addItem(new ComboBoxItem(BLOCK_LABEL));
274        _addIconBox.addItem(new ComboBoxItem(REPORTER));
275        _addIconBox.addItem(new ComboBoxItem(LIGHT));
276        _addIconBox.addItem(new ComboBoxItem(BACKGROUND));
277        _addIconBox.addItem(new ComboBoxItem(MULTI_SENSOR));
278        _addIconBox.addItem(new ComboBoxItem(RPSREPORTER));
279        _addIconBox.addItem(new ComboBoxItem(FAST_CLOCK));
280        _addIconBox.addItem(new ComboBoxItem(GLOBAL_VARIABLE));
281        _addIconBox.addItem(new ComboBoxItem(AUDIO));
282        _addIconBox.addItem(new ComboBoxItem(LOGIXNG));
283        _addIconBox.addItem(new ComboBoxItem(ICON));
284        _addIconBox.setSelectedIndex(-1);
285        _addIconBox.addItemListener(this);  // must be AFTER no selection is set
286        JPanel p1 = new JPanel();
287        p1.setLayout(new BoxLayout(p1, BoxLayout.Y_AXIS));
288        JPanel p2 = new JPanel();
289        p2.setLayout(new FlowLayout());
290        p2.add(new JLabel(Bundle.getMessage("selectTypeIcon")));
291        p1.add(p2);
292        p1.add(_addIconBox);
293        contentPane.add(p1);
294
295        // edit, position, control controls
296        {
297            // edit mode item
298            contentPane.add(editableBox);
299            editableBox.addActionListener(event -> {
300                setAllEditable(editableBox.isSelected());
301                hiddenCheckBoxListener();
302            });
303            editableBox.setSelected(isEditable());
304            // positionable item
305            contentPane.add(positionableBox);
306            positionableBox.addActionListener(event -> setAllPositionable(positionableBox.isSelected()));
307            positionableBox.setSelected(allPositionable());
308            // controlable item
309            contentPane.add(controllingBox);
310            controllingBox.addActionListener(event -> setAllControlling(controllingBox.isSelected()));
311            controllingBox.setSelected(allControlling());
312            // hidden item
313            contentPane.add(hiddenBox);
314            hiddenCheckBoxListener();
315            hiddenBox.setSelected(showHidden());
316
317            /*
318             contentPane.add(showCoordinatesBox);
319             showCoordinatesBox.addActionListener(new ActionListener() {
320             public void actionPerformed(ActionEvent e) {
321             setShowCoordinates(showCoordinatesBox.isSelected());
322             }
323             });
324             showCoordinatesBox.setSelected(showCoordinates());
325             */
326            contentPane.add(showTooltipBox);
327            showTooltipBox.addActionListener(e -> setAllShowToolTip(showTooltipBox.isSelected()));
328            showTooltipBox.setSelected(showToolTip());
329
330            contentPane.add(menuBox);
331            menuBox.addActionListener(e -> setPanelMenuVisible(menuBox.isSelected()));
332            menuBox.setSelected(true);
333
334            // Show/Hide Scroll Bars
335            JPanel scrollPanel = new JPanel();
336            scrollPanel.setLayout(new FlowLayout());
337            scrollableLabel.setLabelFor(scrollableComboBox);
338            scrollPanel.add(scrollableLabel);
339            scrollPanel.add(scrollableComboBox);
340            contentPane.add(scrollPanel);
341            scrollableComboBox.addItem(Bundle.getMessage("ScrollNone"));
342            scrollableComboBox.addItem(Bundle.getMessage("ScrollBoth"));
343            scrollableComboBox.addItem(Bundle.getMessage("ScrollHorizontal"));
344            scrollableComboBox.addItem(Bundle.getMessage("ScrollVertical"));
345            scrollableComboBox.setSelectedIndex(SCROLL_BOTH);
346            scrollableComboBox.addActionListener(e -> setScroll(scrollableComboBox.getSelectedIndex()));
347        }
348
349        // register the resulting panel for later configuration
350        ConfigureManager cm = InstanceManager.getNullableDefault(jmri.ConfigureManager.class);
351        if (cm != null) {
352            cm.registerUser(this);
353        }
354
355        // when this window closes, set contents of target uneditable
356        addWindowListener(new java.awt.event.WindowAdapter() {
357
358            HashMap<String, JFrameItem> iconAdderFrames;
359
360            @Override
361            public void windowClosing(java.awt.event.WindowEvent e) {
362                for (JFrameItem frame : iconAdderFrames.values()) {
363                    frame.dispose();
364                }
365            }
366
367            WindowAdapter init(HashMap<String, JFrameItem> f) {
368                iconAdderFrames = f;
369                return this;
370            }
371        }.init(_iconEditorFrame));
372
373        // and don't destroy the window
374        setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
375        // move this editor panel off the panel's position
376        getTargetFrame().setLocationRelativeTo(this);
377        getTargetFrame().pack();
378        getTargetFrame().setVisible(true);
379        log.debug("PanelEditor ctor done.");
380    }  // end ctor
381
382    /**
383     * Initializes the hiddencheckbox and its listener. This has been taken out
384     * of the init, as checkbox is enable/disabled by the editableBox.
385     */
386    private void hiddenCheckBoxListener() {
387        setShowHidden(hiddenBox.isSelected());
388        if (editableBox.isSelected()) {
389            hiddenBox.setEnabled(false);
390//            hiddenBox.setSelected(true);
391        } else {
392            hiddenBox.setEnabled(true);
393            hiddenBox.addActionListener(event -> setShowHidden(hiddenBox.isSelected()));
394        }
395
396    }
397
398    /**
399     * After construction, initialize all the widgets to their saved config
400     * settings.
401     */
402    @Override
403    public void initView() {
404        editableBox.setSelected(isEditable());
405        positionableBox.setSelected(allPositionable());
406        controllingBox.setSelected(allControlling());
407        //showCoordinatesBox.setSelected(showCoordinates());
408        showTooltipBox.setSelected(showToolTip());
409        hiddenBox.setSelected(showHidden());
410        menuBox.setSelected(getTargetFrame().getJMenuBar().isVisible());
411    }
412
413    static class ComboBoxItem {
414
415        private final String name;
416
417        protected ComboBoxItem(String n) {
418            name = n;
419        }
420
421        protected String getName() {
422            return name;
423        }
424
425        @Override
426        public String toString() {
427            // I18N split Bundle name
428            // use NamedBeanBundle property for basic beans like "Turnout" I18N
429            String bundleName;
430            if (SENSOR.equals(name)) {
431                bundleName = "BeanNameSensor";
432            } else if (SIGNAL_HEAD.equals(name)) {
433                bundleName = "BeanNameSignalHead";
434            } else if (SIGNAL_MAST.equals(name)) {
435                bundleName = "BeanNameSignalMast";
436            } else if (MEMORY.equals(name)) {
437                bundleName = "BeanNameMemory";
438            } else if (REPORTER.equals(name)) {
439                bundleName = "BeanNameReporter";
440            } else if (LIGHT.equals(name)) {
441                bundleName = "BeanNameLight";
442            } else if (GLOBAL_VARIABLE.equals(name)) {
443                bundleName = "BeanNameGlobalVariable";
444            } else if (AUDIO.equals(name)) {
445                bundleName = "BeanNameAudio";
446            } else {
447                bundleName = name;
448            }
449            return Bundle.getMessage(bundleName); // use NamedBeanBundle property for basic beans like "Turnout" I18N
450        }
451    }
452
453    /*
454     * itemListener for JComboBox.
455     */
456    @Override
457    public void itemStateChanged(ItemEvent e) {
458        if (e.getStateChange() == ItemEvent.SELECTED) {
459            ComboBoxItem item = (ComboBoxItem) e.getItem();
460            String name = item.getName();
461            JFrameItem frame = super.getIconFrame(name);
462            if (frame != null) {
463                frame.getEditor().reset();
464                frame.setVisible(true);
465            } else {
466                if (name.equals(FAST_CLOCK)) {
467                    addClock();
468                } else if (name.equals(RPSREPORTER)) {
469                    addRpsReporter();
470                } else {
471                    log.error("Unable to open Icon Editor \"{}\"", item.getName());
472                }
473            }
474            _addIconBox.setSelectedIndex(-1);
475        }
476    }
477
478    /**
479     * Handle close of editor window.
480     * <p>
481     * Overload/override method in JmriJFrame parent, which by default is
482     * permanently closing the window. Here, we just want to make it invisible,
483     * so we don't dispose it (yet).
484     */
485    @Override
486    public void windowClosing(java.awt.event.WindowEvent e) {
487        setVisible(false);
488    }
489
490    /**
491     * Create sequence of panels, etc, for layout: JFrame contains its
492     * ContentPane which contains a JPanel with BoxLayout (p1) which contains a
493     * JScollPane (js) which contains the targetPane.
494     * @param name the frame name.
495     * @return the frame.
496     */
497    public JmriJFrame makeFrame(String name) {
498        JmriJFrame targetFrame = new JmriJFrameWithPermissions(name);
499        targetFrame.setVisible(false);
500
501        JMenuBar menuBar = new JMenuBar();
502        JMenu editMenu = new JMenu(Bundle.getMessage("MenuEdit"));
503        menuBar.add(editMenu);
504        editMenu.add(new AbstractAction(Bundle.getMessage("OpenEditor")) {
505            @Override
506            public void actionPerformed(ActionEvent e) {
507                setVisible(true);
508            }
509        });
510        editMenu.addSeparator();
511        editMenu.add(new AbstractAction(Bundle.getMessage("DeletePanel")) {
512            @Override
513            public void actionPerformed(ActionEvent e) {
514                if (deletePanel()) {
515                    dispose();
516                }
517            }
518        });
519        targetFrame.setJMenuBar(menuBar);
520        // add maker menu
521        JMenu markerMenu = new JMenu(Bundle.getMessage("MenuMarker"));
522        menuBar.add(markerMenu);
523        markerMenu.add(new AbstractAction(Bundle.getMessage("AddLoco")) {
524            @Override
525            public void actionPerformed(ActionEvent e) {
526                locoMarkerFromInput();
527            }
528        });
529        markerMenu.add(new AbstractAction(Bundle.getMessage("AddLocoRoster")) {
530            @Override
531            public void actionPerformed(ActionEvent e) {
532                locoMarkerFromRoster();
533            }
534        });
535        markerMenu.add(new AbstractAction(Bundle.getMessage("RemoveMarkers")) {
536            @Override
537            public void actionPerformed(ActionEvent e) {
538                removeMarkers();
539            }
540        });
541
542        JMenu warrantMenu = jmri.jmrit.logix.WarrantTableAction.getDefault().makeWarrantMenu(isEditable());
543        if (warrantMenu != null) {
544            menuBar.add(warrantMenu);
545        }
546
547        targetFrame.addHelpMenu("package.jmri.jmrit.display.PanelTarget", true);
548        return targetFrame;
549    }
550
551    /*
552     ************* implementation of Abstract Editor methods **********
553     */
554
555    /**
556     * The target window has been requested to close, don't delete it at this
557     * time. Deletion must be accomplished via the Delete this panel menu item.
558     */
559    @Override
560    protected void targetWindowClosingEvent(java.awt.event.WindowEvent e) {
561        targetWindowClosing();
562    }
563
564    /**
565     * Called from TargetPanel's paint method for additional drawing by editor
566     * view
567     */
568    @Override
569    protected void paintTargetPanel(Graphics g) {
570        /*Graphics2D g2 = (Graphics2D)g;
571         drawPositionableLabelBorder(g2);*/
572    }
573
574    /**
575     * Set an object's location when it is created.
576     */
577    @Override
578    protected void setNextLocation(Positionable obj) {
579        int x = Integer.parseInt(nextX.getText());
580        int y = Integer.parseInt(nextY.getText());
581        obj.setLocation(x, y);
582    }
583
584    /**
585     * Create popup for a Positionable object. Popup items common to all
586     * positionable objects are done before and after the items that pertain
587     * only to specific Positionable types.
588     *
589     * @param p           the item containing or requiring the context menu
590     * @param event       the event triggering the menu
591     * @param selections  the list of all Positionables at this position
592     */
593    protected void showPopUp(Positionable p, JmriMouseEvent event, List<Positionable> selections) {
594        if (!((JComponent) p).isVisible()) {
595            return;     // component must be showing on the screen to determine its location
596        }
597        JPopupMenu popup = new JPopupMenu();
598        PositionablePopupUtil util = p.getPopupUtility();
599        if (p.isEditable()) {
600            // items for all Positionables
601            if (p.doViemMenu()) {
602                popup.add(p.getNameString());
603                setPositionableMenu(p, popup);
604                if (p.isPositionable()) {
605                    setShowCoordinatesMenu(p, popup);
606                    setShowAlignmentMenu(p, popup);
607                }
608                setDisplayLevelMenu(p, popup);
609                setHiddenMenu(p, popup);
610                setEmptyHiddenMenu(p, popup);
611                setValueEditDisabledMenu(p, popup);
612                setEditIdMenu(p, popup);
613                setEditClassesMenu(p, popup);
614                popup.addSeparator();
615                setLogixNGPositionableMenu(p, popup);
616                popup.addSeparator();
617            }
618
619            // Positionable items with defaults or using overrides
620            boolean popupSet = false;
621            popupSet = p.setRotateOrthogonalMenu(popup);
622            popupSet |= p.setRotateMenu(popup);
623            popupSet |= p.setScaleMenu(popup);
624            if (popupSet) {
625                popup.addSeparator();
626            }
627            popupSet = p.setEditIconMenu(popup);
628            if (popupSet) {
629                popup.addSeparator();
630            }
631            popupSet = p.setTextEditMenu(popup);
632            if (util != null) {
633                util.setFixedTextMenu(popup);
634                util.setTextMarginMenu(popup);
635                util.setTextBorderMenu(popup);
636                util.setTextFontMenu(popup);
637                util.setBackgroundMenu(popup);
638                util.setTextJustificationMenu(popup);
639                util.setTextOrientationMenu(popup);
640                util.copyItem(popup);
641                popup.addSeparator();
642                util.propertyUtil(popup);
643                util.setAdditionalEditPopUpMenu(popup);
644                popupSet = true;
645            }
646            if (popupSet) {
647                popup.addSeparator();
648            }
649            p.setDisableControlMenu(popup);
650
651            // for Positionables with unique item settings
652            p.showPopUp(popup);
653
654            setShowToolTipMenu(p, popup);
655            setRemoveMenu(p, popup);
656        } else {
657            p.showPopUp(popup);
658            if (util != null) {
659                util.setAdditionalViewPopUpMenu(popup);
660            }
661        }
662
663        if (selections.size() > 1) {
664            boolean found = false;
665            JMenu iconsBelowMenu = new JMenu(Bundle.getMessage("MenuItemIconsBelow"));
666            for (int i=0; i < selections.size(); i++) {
667                Positionable pos = selections.get(i);
668                if (found) {
669                    iconsBelowMenu.add(new AbstractAction(Bundle.getMessage(
670                            "PositionableTypeAndName", pos.getTypeString(), pos.getNameString())) {
671                        @Override
672                        public void actionPerformed(ActionEvent e) {
673                            showPopUp(pos, event, new ArrayList<>());
674                        }
675                    });
676                } else {
677                    if (p == pos) found = true;
678                }
679            }
680            popup.addSeparator();
681            popup.add(iconsBelowMenu);
682        }
683
684        popup.show((Component) p, p.getWidth() / 2, p.getHeight() / 2);
685    }
686
687    /**
688     * ***************************************************
689     */
690    private boolean delayedPopupTrigger;
691
692    @Override
693    public void mousePressed(JmriMouseEvent event) {
694        setToolTip(null); // ends tooltip if displayed
695        if (log.isDebugEnabled()) {
696            log.debug("mousePressed at ({},{}) _dragging= {}", event.getX(), event.getY(), _dragging);
697        }
698        _anchorX = event.getX();
699        _anchorY = event.getY();
700        _lastX = _anchorX;
701        _lastY = _anchorY;
702        List<Positionable> selections = getSelectedItems(event);
703        if (_dragging) {
704            return;
705        }
706        if (selections.size() > 0) {
707            if (event.isShiftDown() && selections.size() > 1) {
708                _currentSelection = selections.get(1);
709            } else {
710                _currentSelection = selections.get(0);
711            }
712            if (event.isPopupTrigger()) {
713                log.debug("mousePressed calls showPopUp");
714                if (event.isMetaDown() || event.isAltDown()) {
715                    // if requesting a popup and it might conflict with moving, delay the request to mouseReleased
716                    delayedPopupTrigger = true;
717                } else {
718                    // no possible conflict with moving, display the popup now
719                    if (_selectionGroup != null) {
720                        //Will show the copy option only
721                        showMultiSelectPopUp(event, _currentSelection);
722                    } else {
723                        showPopUp(_currentSelection, event, selections);
724                    }
725                }
726            } else if (!event.isControlDown()) {
727                _currentSelection.doMousePressed(event);
728                if (_multiItemCopyGroup != null && !_multiItemCopyGroup.contains(_currentSelection)) {
729                    _multiItemCopyGroup = null;
730                }
731                // _selectionGroup = null;
732            }
733        } else {
734            if (event.isPopupTrigger()) {
735                if (event.isMetaDown() || event.isAltDown()) {
736                    // if requesting a popup and it might conflict with moving, delay the request to mouseReleased
737                    delayedPopupTrigger = true;
738                } else {
739                    if (_multiItemCopyGroup != null) {
740                        pasteItemPopUp(event);
741                    } else if (_selectionGroup != null) {
742                        showMultiSelectPopUp(event, _currentSelection);
743                    } else {
744                        backgroundPopUp(event);
745                        _currentSelection = null;
746                    }
747                }
748            } else {
749                _currentSelection = null;
750            }
751        }
752        // if ((event.isControlDown() || _selectionGroup!=null) && _currentSelection!=null){
753        if ((event.isControlDown()) || event.isMetaDown() || event.isAltDown()) {
754            //Don't want to do anything, just want to catch it, so that the next two else ifs are not
755            //executed
756        } else if ((_currentSelection == null && _multiItemCopyGroup == null)
757                || (_selectRect != null && !_selectRect.contains(_anchorX, _anchorY))) {
758            _selectRect = new Rectangle(_anchorX, _anchorY, 0, 0);
759            _selectionGroup = null;
760        } else {
761            _selectRect = null;
762            _selectionGroup = null;
763        }
764        _targetPanel.repaint(); // needed for ToolTip
765    }
766
767    @Override
768    public void mouseReleased(JmriMouseEvent event) {
769        setToolTip(null); // ends tooltip if displayed
770        if (log.isDebugEnabled()) {
771            // in if statement to avoid inline conditional unless logging
772            log.debug("mouseReleased at ({},{}) dragging= {} selectRect is {}", event.getX(), event.getY(), _dragging,
773                    _selectRect == null ? "null" : "not null");
774        }
775        List<Positionable> selections = getSelectedItems(event);
776
777        if (_dragging) {
778            mouseDragged(event);
779        }
780        if (selections.size() > 0) {
781            if (event.isShiftDown() && selections.size() > 1) {
782                _currentSelection = selections.get(1);
783            } else {
784                _currentSelection = selections.get(0);
785            }
786            if (_multiItemCopyGroup != null && !_multiItemCopyGroup.contains(_currentSelection)) {
787                _multiItemCopyGroup = null;
788            }
789        } else {
790            if ((event.isPopupTrigger() || delayedPopupTrigger) && !_dragging) {
791                if (_multiItemCopyGroup != null) {
792                    pasteItemPopUp(event);
793                } else {
794                    backgroundPopUp(event);
795                    _currentSelection = null;
796                }
797            } else {
798                _currentSelection = null;
799
800            }
801        }
802        /*if (event.isControlDown() && _currentSelection!=null && !event.isPopupTrigger()){
803         amendSelectionGroup(_currentSelection, event);*/
804        if ((event.isPopupTrigger() || delayedPopupTrigger) && _currentSelection != null && !_dragging) {
805            if (_selectionGroup != null) {
806                //Will show the copy option only
807                showMultiSelectPopUp(event, _currentSelection);
808
809            } else {
810                showPopUp(_currentSelection, event, selections);
811            }
812        } else {
813            if (_currentSelection != null && !_dragging && !event.isControlDown()) {
814                _currentSelection.doMouseReleased(event);
815            }
816            if (allPositionable() && _selectRect != null) {
817                if (_selectionGroup == null && _dragging) {
818                    makeSelectionGroup(event);
819                }
820            }
821        }
822        delayedPopupTrigger = false;
823        _dragging = false;
824        _selectRect = null;
825
826        // if not sending MouseClicked, do it here
827        if (InstanceManager.getDefault(GuiLafPreferencesManager.class).isNonStandardMouseEvent()) {
828            mouseClicked(event);
829        }
830        _targetPanel.repaint(); // needed for ToolTip
831    }
832
833    @Override
834    public void mouseDragged(JmriMouseEvent event) {
835        setToolTip(null); // ends tooltip if displayed
836        if ((event.isPopupTrigger()) || (!event.isMetaDown() && !event.isAltDown())) {
837            if (_currentSelection != null) {
838                List<Positionable> selections = getSelectedItems(event);
839                if (selections.size() > 0) {
840                    if (selections.get(0) != _currentSelection) {
841                        _currentSelection.doMouseReleased(event);
842                    } else {
843                        _currentSelection.doMouseDragged(event);
844                    }
845                } else {
846                    _currentSelection.doMouseReleased(event);
847                }
848            }
849            return;
850        }
851        moveIt:
852        if (_currentSelection != null && getFlag(OPTION_POSITION, _currentSelection.isPositionable())) {
853            int deltaX = event.getX() - _lastX;
854            int deltaY = event.getY() - _lastY;
855            int minX = getItemX(_currentSelection, deltaX);
856            int minY = getItemY(_currentSelection, deltaY);
857            if (_selectionGroup != null && _selectionGroup.contains(_currentSelection)) {
858                for (Positionable comp : _selectionGroup) {
859                    minX = Math.min(getItemX(comp, deltaX), minX);
860                    minY = Math.min(getItemY(comp, deltaY), minY);
861                }
862            }
863            if (minX < 0 || minY < 0) {
864                break moveIt;
865            }
866            if (_selectionGroup != null && _selectionGroup.contains(_currentSelection)) {
867                for (Positionable comp : _selectionGroup) {
868                    moveItem(comp, deltaX, deltaY);
869                }
870                _highlightcomponent = null;
871            } else {
872                moveItem(_currentSelection, deltaX, deltaY);
873                _highlightcomponent = new Rectangle(_currentSelection.getX(), _currentSelection.getY(),
874                        _currentSelection.maxWidth(), _currentSelection.maxHeight());
875            }
876        } else {
877            if (allPositionable() && _selectionGroup == null) {
878                drawSelectRect(event.getX(), event.getY());
879            }
880        }
881        _dragging = true;
882        _lastX = event.getX();
883        _lastY = event.getY();
884        _targetPanel.repaint(); // needed for ToolTip
885    }
886
887    @Override
888    public void mouseMoved(JmriMouseEvent event) {
889        // log.debug("mouseMoved at ({},{})", event.getX(), event.getY());
890        if (_dragging || event.isPopupTrigger()) {
891            return;
892        }
893
894        List<Positionable> selections = getSelectedItems(event);
895        Positionable selection = null;
896        if (selections.size() > 0) {
897            if (event.isShiftDown() && selections.size() > 1) {
898                selection = selections.get(1);
899            } else {
900                selection = selections.get(0);
901            }
902        }
903        if (isEditable() && selection != null && selection.getDisplayLevel() > BKG) {
904            _highlightcomponent = new Rectangle(selection.getX(), selection.getY(), selection.maxWidth(), selection.maxHeight());
905            _targetPanel.repaint();
906        } else {
907            _highlightcomponent = null;
908            _targetPanel.repaint();
909        }
910        if (selection != null && selection.getDisplayLevel() > BKG && selection.showToolTip()) {
911            showToolTip(selection, event);
912            //selection.highlightlabel(true);
913            _targetPanel.repaint();
914        } else {
915            setToolTip(null);
916            _highlightcomponent = null;
917            _targetPanel.repaint();
918        }
919    }
920
921    @Override
922    public void mouseClicked(JmriMouseEvent event) {
923        setToolTip(null); // ends tooltip if displayed
924        if (log.isDebugEnabled()) {
925            log.debug("mouseClicked at ({},{}) dragging= {} selectRect is {}",
926                    event.getX(), event.getY(), _dragging, (_selectRect == null ? "null" : "not null"));
927        }
928        List<Positionable> selections = getSelectedItems(event);
929
930        if (selections.size() > 0) {
931            if (event.isShiftDown() && selections.size() > 1) {
932                _currentSelection = selections.get(1);
933            } else {
934                _currentSelection = selections.get(0);
935            }
936        } else {
937            _currentSelection = null;
938            if (event.isPopupTrigger()) {
939                if (_multiItemCopyGroup == null) {
940                    pasteItemPopUp(event);
941                } else {
942                    backgroundPopUp(event);
943                }
944            }
945        }
946        if (event.isPopupTrigger() && _currentSelection != null && !_dragging) {
947            if (_selectionGroup != null) {
948                showMultiSelectPopUp(event, _currentSelection);
949            } else {
950                showPopUp(_currentSelection, event, selections);
951            }
952            // _selectionGroup = null; // Show popup only works for a single item
953
954        } else {
955            if (_currentSelection != null && !_dragging && !event.isControlDown()) {
956                _currentSelection.doMouseClicked(event);
957            }
958        }
959        _targetPanel.repaint(); // needed for ToolTip
960        if (event.isControlDown() && _currentSelection != null && !event.isPopupTrigger()) {
961            amendSelectionGroup(_currentSelection);
962        }
963    }
964
965    @Override
966    public void mouseEntered(JmriMouseEvent event) {
967    }
968
969    @Override
970    public void mouseExited(JmriMouseEvent event) {
971        setToolTip(null);
972        _targetPanel.repaint();  // needed for ToolTip
973    }
974
975    protected ArrayList<Positionable> _multiItemCopyGroup = null;  // items gathered inside fence
976
977    @Override
978    protected void copyItem(Positionable p) {
979        _multiItemCopyGroup = new ArrayList<>();
980        _multiItemCopyGroup.add(p);
981    }
982
983    protected void pasteItemPopUp(final JmriMouseEvent event) {
984        if (!isEditable()) {
985            return;
986        }
987        if (_multiItemCopyGroup == null) {
988            return;
989        }
990        JPopupMenu popup = new JPopupMenu();
991        JMenuItem edit = new JMenuItem(Bundle.getMessage("MenuItemPaste"));
992        edit.addActionListener(e -> pasteItem(event));
993        setBackgroundMenu(popup);
994        showAddItemPopUp(event, popup);
995        popup.add(edit);
996        popup.show(event.getComponent(), event.getX(), event.getY());
997    }
998
999    protected void backgroundPopUp(JmriMouseEvent event) {
1000        if (!isEditable()) {
1001            return;
1002        }
1003        JPopupMenu popup = new JPopupMenu();
1004        setBackgroundMenu(popup);
1005        showAddItemPopUp(event, popup);
1006        popup.show(event.getComponent(), event.getX(), event.getY());
1007    }
1008
1009    protected void showMultiSelectPopUp(final JmriMouseEvent event, Positionable p) {
1010        JPopupMenu popup = new JPopupMenu();
1011        JMenuItem copy = new JMenuItem(Bundle.getMessage("MenuItemCopy")); // changed "edit" to "copy"
1012        if (p.isPositionable()) {
1013            setShowAlignmentMenu(p, popup);
1014        }
1015        copy.addActionListener(e -> {
1016            _multiItemCopyGroup = new ArrayList<>();
1017            // must make a copy or pasteItem() will hang
1018            if (_selectionGroup != null) {
1019                _multiItemCopyGroup.addAll(_selectionGroup);
1020            }
1021        });
1022
1023        setMultiItemsPositionableMenu(popup); // adding Lock Position for all
1024        // selected items
1025
1026        setRemoveMenu(p, popup);
1027        //showAddItemPopUp(event, popup); // no need to Add when group selected
1028        popup.add(copy);
1029        popup.show(event.getComponent(), event.getX(), event.getY());
1030    }
1031
1032    protected void showAddItemPopUp(final JmriMouseEvent event, JPopupMenu popup) {
1033        if (!isEditable()) {
1034            return;
1035        }
1036        JMenu _add = new JMenu(Bundle.getMessage("MenuItemAddItem"));
1037        // for items in the following list, I18N is picked up later on
1038        addItemPopUp(new ComboBoxItem(RIGHT_TURNOUT), _add);
1039        addItemPopUp(new ComboBoxItem(LEFT_TURNOUT), _add);
1040        addItemPopUp(new ComboBoxItem(SLIP_TO_EDITOR), _add);
1041        addItemPopUp(new ComboBoxItem(SENSOR), _add);
1042        addItemPopUp(new ComboBoxItem(SIGNAL_HEAD), _add);
1043        addItemPopUp(new ComboBoxItem(SIGNAL_MAST), _add);
1044        addItemPopUp(new ComboBoxItem(MEMORY), _add);
1045        addItemPopUp(new ComboBoxItem(BLOCK_LABEL), _add);
1046        addItemPopUp(new ComboBoxItem(REPORTER), _add);
1047        addItemPopUp(new ComboBoxItem(LIGHT), _add);
1048        addItemPopUp(new ComboBoxItem(BACKGROUND), _add);
1049        addItemPopUp(new ComboBoxItem(MULTI_SENSOR), _add);
1050        addItemPopUp(new ComboBoxItem(RPSREPORTER), _add);
1051        addItemPopUp(new ComboBoxItem(FAST_CLOCK), _add);
1052        addItemPopUp(new ComboBoxItem(GLOBAL_VARIABLE), _add);
1053        addItemPopUp(new ComboBoxItem(AUDIO), _add);
1054        addItemPopUp(new ComboBoxItem(LOGIXNG), _add);
1055        addItemPopUp(new ComboBoxItem(ICON), _add);
1056        addItemPopUp(new ComboBoxItem("Text"), _add);
1057        popup.add(_add);
1058    }
1059
1060    protected void addItemPopUp(final ComboBoxItem item, JMenu menu) {
1061
1062        ActionListener a = new ActionListener() {
1063            //final String desiredName = name;
1064            @Override
1065            public void actionPerformed(ActionEvent e) {
1066                addItemViaMouseClick = true;
1067                getIconFrame(item.getName());
1068            }
1069
1070            ActionListener init(ComboBoxItem i) {
1071                return this;
1072            }
1073        }.init(item);
1074        JMenuItem addto = new JMenuItem(item.toString());
1075        addto.addActionListener(a);
1076        menu.add(addto);
1077    }
1078
1079    protected boolean addItemViaMouseClick = false;
1080
1081    @Override
1082    public void putItem(Positionable l) throws Positionable.DuplicateIdException {
1083        super.putItem(l);
1084        /*This allows us to catch any new items that are being pasted into the panel
1085         and add them to the selection group, so that the user can instantly move them around*/
1086        //!!!
1087        if (pasteItemFlag) {
1088            amendSelectionGroup(l);
1089            return;
1090        }
1091        if (addItemViaMouseClick) {
1092            addItemViaMouseClick = false;
1093            l.setLocation(_lastX, _lastY);
1094        }
1095    }
1096
1097    private void amendSelectionGroup(Positionable p) {
1098        if (p == null) {
1099            return;
1100        }
1101        if (_selectionGroup == null) {
1102            _selectionGroup = new ArrayList<>();
1103        }
1104        boolean removed = false;
1105        for (int i = 0; i < _selectionGroup.size(); i++) {
1106            if (_selectionGroup.get(i) == p) {
1107                _selectionGroup.remove(i);
1108                removed = true;
1109                break;
1110            }
1111        }
1112        if (!removed) {
1113            _selectionGroup.add(p);
1114        } else if (_selectionGroup.isEmpty()) {
1115            _selectionGroup = null;
1116        }
1117        _targetPanel.repaint();
1118    }
1119
1120    protected boolean pasteItemFlag = false;
1121
1122    protected void pasteItem(JmriMouseEvent e) {
1123        pasteItemFlag = true;
1124        XmlAdapter adapter;
1125        String className;
1126        int x;
1127        int y;
1128        int xOrig;
1129        int yOrig;
1130        if (_multiItemCopyGroup != null) {
1131            JComponent copied;
1132            int xoffset;
1133            int yoffset;
1134            x = _multiItemCopyGroup.get(0).getX();
1135            y = _multiItemCopyGroup.get(0).getY();
1136            xoffset = e.getX() - x;
1137            yoffset = e.getY() - y;
1138            /*We make a copy of the selected items and work off of that copy
1139             as amendments are made to the multiItemCopyGroup during this process
1140             which can result in a loop*/
1141            ArrayList<Positionable> _copyOfMultiItemCopyGroup = new ArrayList<>(_multiItemCopyGroup);
1142            Collections.copy(_copyOfMultiItemCopyGroup, _multiItemCopyGroup);
1143            for (Positionable comp : _copyOfMultiItemCopyGroup) {
1144                copied = (JComponent) comp;
1145                xOrig = copied.getX();
1146                yOrig = copied.getY();
1147                x = xOrig + xoffset;
1148                y = yOrig + yoffset;
1149                if (x < 0) {
1150                    x = 1;
1151                }
1152                if (y < 0) {
1153                    y = 1;
1154                }
1155                className = ConfigXmlManager.adapterName(copied);
1156                copied.setLocation(x, y);
1157                try {
1158                    adapter = (XmlAdapter) Class.forName(className).getDeclaredConstructor().newInstance();
1159                    Element el = adapter.store(copied);
1160                    adapter.load(el, this);
1161                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException
1162                    | jmri.configurexml.JmriConfigureXmlException
1163                    | RuntimeException ex) {
1164                        log.debug("Could not paste.", ex);
1165                }
1166                /*We remove the original item from the list, so we end up with
1167                 just the new items selected and allow the items to be moved around */
1168                amendSelectionGroup(comp);
1169                copied.setLocation(xOrig, yOrig);
1170            }
1171            _selectionGroup = null;
1172        }
1173        pasteItemFlag = false;
1174        _targetPanel.repaint();
1175    }
1176
1177    /**
1178     * Add an action to remove the Positionable item.
1179     */
1180    @Override
1181    public void setRemoveMenu(Positionable p, JPopupMenu popup) {
1182        popup.add(new AbstractAction(Bundle.getMessage("Remove")) {
1183            Positionable comp;
1184
1185            @Override
1186            public void actionPerformed(ActionEvent e) {
1187                if (_selectionGroup == null) {
1188                    comp.remove();
1189                } else {
1190                    removeMultiItems();
1191                }
1192            }
1193
1194            AbstractAction init(Positionable pos) {
1195                comp = pos;
1196                return this;
1197            }
1198        }.init(p));
1199    }
1200
1201    private void removeMultiItems() {
1202        boolean itemsInCopy = false;
1203        if (_selectionGroup == _multiItemCopyGroup) {
1204            itemsInCopy = true;
1205        }
1206        for (Positionable comp : _selectionGroup) {
1207            comp.remove();
1208        }
1209        //As we have removed all the items from the panel we can remove the group.
1210        _selectionGroup = null;
1211        //If the items in the selection group and copy group are the same we need to
1212        //clear the copy group as the originals no longer exist.
1213        if (itemsInCopy) {
1214            _multiItemCopyGroup = null;
1215        }
1216    }
1217
1218    // This adds a single CheckBox in the PopupMenu to set or clear all the selected
1219    // items "Lock Position" or Positionable setting, when clicked, all the items in
1220    // the selection will be changed accordingly.
1221    private void setMultiItemsPositionableMenu(JPopupMenu popup) {
1222        // This would do great with a "greyed" CheckBox if the multiple items have different states.
1223        // Then selecting the true or false state would force all to change to true or false
1224
1225        JCheckBoxMenuItem lockItem = new JCheckBoxMenuItem(Bundle.getMessage("LockPosition"));
1226        boolean allSetToMove = false;  // used to decide the state of the checkbox shown
1227        int trues = 0;                 // used to see if all items have the same setting
1228
1229        int size = _selectionGroup.size();
1230
1231        for (Positionable comp : _selectionGroup) {
1232            if (!comp.isPositionable()) {
1233                allSetToMove = true;
1234                trues++;
1235            }
1236
1237            lockItem.setSelected(allSetToMove);
1238
1239            lockItem.addActionListener(new ActionListener() {
1240                Positionable comp;
1241                JCheckBoxMenuItem checkBox;
1242
1243                @Override
1244                public void actionPerformed(ActionEvent e) {
1245                    comp.setPositionable(!checkBox.isSelected());
1246                    setSelectionsPositionable(!checkBox.isSelected(), comp);
1247                }
1248
1249                ActionListener init(Positionable pos, JCheckBoxMenuItem cb) {
1250                    comp = pos;
1251                    checkBox = cb;
1252                    return this;
1253                }
1254            }.init(comp, lockItem));
1255        }
1256
1257        // Add "~" to the Text when all items do not have the same setting,
1258        // until we get a "greyed" CheckBox ;) - GJM
1259        if ((trues != size) && (trues != 0)) {
1260            lockItem.setText("~ " + lockItem.getText());
1261            // uncheck box if all not the same
1262            lockItem.setSelected(false);
1263        }
1264        popup.add(lockItem);
1265    }
1266
1267    public void setBackgroundMenu(JPopupMenu popup) {
1268        // Panel background, not text background
1269        JMenuItem edit = new JMenuItem(Bundle.getMessage("FontBackgroundColor"));
1270        edit.addActionListener((ActionEvent event) -> {
1271            Color desiredColor = JmriColorChooser.showDialog(this,
1272                                 Bundle.getMessage("FontBackgroundColor"),
1273                                 getBackgroundColor());
1274            if (desiredColor!=null ) {
1275               setBackgroundColor(desiredColor);
1276           }
1277        });
1278        popup.add(edit);
1279    }
1280
1281    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PanelEditor.class);
1282
1283}