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