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