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