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