001package jmri.jmrit.display.controlPanelEditor;
002
003import java.awt.Color;
004import java.awt.Component;
005import java.awt.Container;
006import java.awt.Dimension;
007import java.awt.Font;
008import java.awt.Graphics;
009import java.awt.Point;
010import java.awt.Rectangle;
011import java.awt.Toolkit;
012import java.awt.datatransfer.Clipboard;
013import java.awt.datatransfer.ClipboardOwner;
014import java.awt.datatransfer.DataFlavor;
015import java.awt.datatransfer.Transferable;
016import java.awt.datatransfer.UnsupportedFlavorException;
017import java.awt.dnd.DnDConstants;
018import java.awt.dnd.DropTarget;
019import java.awt.dnd.DropTargetDragEvent;
020import java.awt.dnd.DropTargetDropEvent;
021import java.awt.dnd.DropTargetEvent;
022import java.awt.dnd.DropTargetListener;
023import java.awt.event.*;
024import java.awt.geom.Rectangle2D;
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.List;
030import java.util.ResourceBundle;
031
032import javax.annotation.Nonnull;
033import javax.swing.*;
034
035import jmri.CatalogTreeManager;
036import jmri.ConfigureManager;
037import jmri.InstanceManager;
038import jmri.jmrit.catalog.ImageIndexEditor;
039import jmri.jmrit.catalog.NamedIcon;
040import jmri.jmrit.display.CoordinateEdit;
041import jmri.jmrit.display.Editor;
042import jmri.jmrit.display.IndicatorTrack;
043import jmri.jmrit.display.LinkingObject;
044import jmri.jmrit.display.LocoIcon;
045import jmri.jmrit.display.MemoryOrGVIcon;
046import jmri.jmrit.display.Positionable;
047import jmri.jmrit.display.PositionableIcon;
048import jmri.jmrit.display.PositionableJComponent;
049import jmri.jmrit.display.PositionableJPanel;
050import jmri.jmrit.display.PositionableLabel;
051import jmri.jmrit.display.PositionablePopupUtil;
052import jmri.jmrit.display.ReporterIcon;
053import jmri.jmrit.display.RpsPositionIcon;
054import jmri.jmrit.display.ToolTip;
055import jmri.jmrit.display.controlPanelEditor.shape.ShapeDrawer;
056import jmri.jmrit.display.palette.ColorDialog;
057import jmri.jmrit.display.palette.ItemPalette;
058import jmri.jmrit.logix.WarrantTableAction;
059import jmri.util.HelpUtil;
060import jmri.util.SystemType;
061import jmri.util.gui.GuiLafPreferencesManager;
062import jmri.util.swing.JmriJOptionPane;
063import jmri.util.swing.JmriMouseEvent;
064
065/**
066 * Provides a simple editor for adding jmri.jmrit.display items to a captive
067 * JFrame.
068 * <p>
069 * GUI is structured as a band of common parameters across the top, then a
070 * series of things you can add.
071 * <p>
072 * All created objects are put specific levels depending on their type (higher
073 * levels are in front):
074 * <ul>
075 * <li>BKG background
076 * <li>ICONS icons and other drawing symbols
077 * <li>LABELS text labels
078 * <li>TURNOUTS turnouts and other variable track items
079 * <li>SENSORS sensors and other independently modified objects
080 * </ul>
081 * Note that higher numbers appear behind lower numbers.
082 * <p>
083 * The "contents" List keeps track of all the objects added to the target frame
084 * for later manipulation. Extends the behavior it shares with PanelPro DnD
085 * implemented at JDK 1.2 for backward compatibility
086 *
087 * @author Pete Cressman Copyright: Copyright (c) 2009, 2010, 2011
088 */
089public class ControlPanelEditor extends Editor implements DropTargetListener, ClipboardOwner {
090
091    protected JMenuBar _menuBar;
092    private JMenu _editorMenu;
093    protected JMenu _editMenu;
094    protected JMenu _fileMenu;
095    protected JMenu _optionMenu;
096    protected JMenu _iconMenu;
097    protected JMenu _zoomMenu;
098    private JMenu _markerMenu;
099    private JMenu _warrantMenu;
100    private JMenu _circuitMenu;
101    private JMenu _drawMenu;
102    private CircuitBuilder _circuitBuilder;
103    private final ArrayList<Rectangle> _highlightGroup = new ArrayList<>();
104    private ShapeDrawer _shapeDrawer;
105    private ItemPalette _itemPalette;
106    private boolean _disableShapeSelection;
107    private boolean _disablePortalSelection = true;  // only select PortalIcon in CircuitBuilder
108    private String _portalIconFamily = "Standard"; // initial default, must match xml, updated in setIconFamilysetPortalIconFamily
109    private HashMap<String, NamedIcon> _portalIconMap;
110
111    private final JCheckBoxMenuItem useGlobalFlagBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxGlobalFlags"));
112    private final JCheckBoxMenuItem positionableBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxPositionable"));
113    private final JCheckBoxMenuItem controllingBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxControlling"));
114    private final JCheckBoxMenuItem showTooltipBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxShowTooltips"));
115    private final JCheckBoxMenuItem hiddenBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxHidden"));
116    private final JCheckBoxMenuItem disableShapeSelect = new JCheckBoxMenuItem(Bundle.getMessage("disableShapeSelect"));
117    private final JRadioButtonMenuItem scrollBoth = new JRadioButtonMenuItem(Bundle.getMessage("ScrollBoth"));
118    private final JRadioButtonMenuItem scrollNone = new JRadioButtonMenuItem(Bundle.getMessage("ScrollNone"));
119    private final JRadioButtonMenuItem scrollHorizontal = new JRadioButtonMenuItem(Bundle.getMessage("ScrollHorizontal"));
120    private final JRadioButtonMenuItem scrollVertical = new JRadioButtonMenuItem(Bundle.getMessage("ScrollVertical"));
121
122    public ControlPanelEditor() {
123    }
124
125    public ControlPanelEditor(String name) {
126        super(name);
127        init(name);
128    }
129
130    @Override
131    protected void init(String name) {
132        setVisible(false);
133        java.awt.Container contentPane = this.getContentPane();
134        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
135
136        // make menus
137        setGlobalSetsLocalFlag(false);
138        setUseGlobalFlag(false);
139        _menuBar = new JMenuBar();
140        _circuitBuilder = new CircuitBuilder(this);
141        _shapeDrawer = new ShapeDrawer(this);
142        makeDrawMenu();
143        makeWarrantMenu(true, true);
144        makeIconMenu();
145        makeZoomMenu();
146        makeMarkerMenu();
147        makeOptionMenu();
148        makeEditMenu();
149        makeFileMenu();
150
151        setJMenuBar(_menuBar);
152        addHelpMenu("package.jmri.jmrit.display.ControlPanelEditor", true);
153
154        super.setTargetPanel(null, null);
155        super.setTargetPanelSize(300, 300);
156        makeDataFlavors();
157
158        // set scrollbar initial state
159        setScroll(SCROLL_BOTH);
160        scrollBoth.setSelected(true);
161        super.setDefaultToolTip(new ToolTip(null, 0, 0, new Font("Serif", Font.PLAIN, 12),
162                Color.black, new Color(255, 250, 210), Color.black, null));
163        // register the resulting panel for later configuration
164        ConfigureManager cm = InstanceManager.getNullableDefault(jmri.ConfigureManager.class);
165        if (cm != null) {
166            cm.registerUser(this);
167        }
168        pack();
169        setVisible(true);
170    }
171
172    protected void makeIconMenu() {
173        _iconMenu = new JMenu(Bundle.getMessage("MenuIcon"));
174        _menuBar.add(_iconMenu, 0);
175
176        JMenuItem mi = new JMenuItem(Bundle.getMessage("CircuitBuilder"));
177        mi.addActionListener((ActionEvent event) -> _circuitBuilder.openCBWindow());
178        setMenuAcceleratorKey(mi, KeyEvent.VK_B);
179        _iconMenu.add(mi);
180
181        _iconMenu.add(new JSeparator()); // below are different types of tables
182
183        mi = new JMenuItem(Bundle.getMessage("MenuItemItemPalette"));
184        mi.addActionListener(new ActionListener() {
185            Editor editor;
186
187            ActionListener init(Editor ed) {
188                editor = ed;
189                return this;
190            }
191            @Override
192            public void actionPerformed(ActionEvent e) {
193                _itemPalette = ItemPalette.getDefault(Bundle.getMessage("MenuItemItemPalette"), editor);
194                assert _itemPalette != null;
195                _itemPalette.setVisible(true);
196            }
197        }.init(this));
198        setMenuAcceleratorKey(mi, KeyEvent.VK_P);
199        _iconMenu.add(mi);
200
201        _iconMenu.add(new jmri.jmrit.beantable.OBlockTableAction(Bundle.getMessage("MenuItemOBlockTable")));
202        mi = (JMenuItem) _iconMenu.getMenuComponent(3);
203        setMenuAcceleratorKey(mi, KeyEvent.VK_O);
204
205        _iconMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemTableList")));
206        mi = (JMenuItem) _iconMenu.getMenuComponent(4);
207        setMenuAcceleratorKey(mi, KeyEvent.VK_T);
208    }
209
210    @SuppressWarnings("deprecation")  // META_MASK CTRL_MASK
211    private void setMenuAcceleratorKey (JMenuItem mi,  int key) {
212        if (SystemType.isMacOSX()) {
213            mi.setAccelerator(KeyStroke.getKeyStroke(key, InputEvent.META_DOWN_MASK));
214        } else {
215            mi.setAccelerator(KeyStroke.getKeyStroke(key, InputEvent.CTRL_DOWN_MASK));
216        }
217    }
218
219    protected void makeCircuitMenu(boolean edit) {
220        if (edit) {
221            if (_circuitMenu == null) {
222                ItemPalette.loadIcons();
223                _circuitMenu = _circuitBuilder.makeMenu();
224                int idx = _menuBar.getComponentIndex(_warrantMenu);
225                _menuBar.add(_circuitMenu, ++idx);
226                _menuBar.revalidate();
227            }
228        } else if (_circuitMenu != null) {
229            _circuitBuilder.closeCBWindow();
230            _circuitMenu = null;
231        }
232    }
233
234    protected void makeDrawMenu() {
235        if (_drawMenu == null) {
236            _drawMenu = _shapeDrawer.makeMenu();
237            _drawMenu.add(disableShapeSelect);
238            disableShapeSelect.addActionListener((ActionEvent event) -> _disableShapeSelection = disableShapeSelect.isSelected());
239        }
240        _menuBar.add(_drawMenu, 0);
241    }
242
243    public boolean getShapeSelect() {
244        return !_disableShapeSelection;
245    }
246
247    public void setShapeSelect(boolean set) {
248        _disableShapeSelection = !set;
249        disableShapeSelect.setSelected(_disableShapeSelection);
250    }
251
252    public String getPortalIconFamily() {
253        return _portalIconFamily;
254    }
255
256    public void setPortalIconFamily(String family) {
257        if (family != null && !family.equals(_portalIconFamily)) {
258            _portalIconMap = null;
259        }
260        _portalIconFamily = family;
261    }
262
263    @Nonnull
264    public HashMap<String, NamedIcon> getPortalIconMap() {
265        if (_portalIconMap == null) {
266            ItemPalette.loadIcons();
267            _portalIconMap = ItemPalette.getIconMap("Portal", _portalIconFamily);
268            if (_portalIconMap == null) {
269                HashMap<String, HashMap<String, NamedIcon>> familyMap = ItemPalette.getFamilyMaps("Portal");
270                _portalIconMap = familyMap.get("Standard");
271                // fill in the default PortalIconMap for CPE if it was null up to now.
272                // TODO check if this is ever called since we fixed getting it from ItemPalette above, remove for better maintainability
273                if (_portalIconMap == null) {
274                    log.warn("empty PortalIconMap returned");
275                    _portalIconMap = new HashMap<>();
276                    _portalIconMap.put(PortalIcon.HIDDEN,
277                            new NamedIcon("resources/icons/Invisible.gif", "resources/icons/Invisible.gif"));
278                    _portalIconMap.put(PortalIcon.PATH,
279                            new NamedIcon("resources/icons/greenSquare.gif", "resources/icons/greenSquare.gif"));
280                    _portalIconMap.put(PortalIcon.VISIBLE,
281                            new NamedIcon("resources/icons/throttles/RoundRedCircle20.png", "resources/icons/throttles/RoundRedCircle20.png"));
282                    _portalIconMap.put(PortalIcon.TO_ARROW,
283                            new NamedIcon("resources/icons/track/toArrow.gif", "resources/icons/track/toArrow.gif"));
284                    _portalIconMap.put(PortalIcon.FROM_ARROW,
285                            new NamedIcon("resources/icons/track/fromArrow.gif", "resources/icons/track/fromArrow.gif"));
286                }
287            }
288        }
289        // return a copy, not the ItemPalette's map!
290        return PositionableIcon.cloneMap(_portalIconMap, null);
291    }
292
293    public ShapeDrawer getShapeDrawer() {
294        return _shapeDrawer;
295    }
296
297    protected void makeZoomMenu() {
298        _zoomMenu = new JMenu(Bundle.getMessage("MenuZoom"));
299        _menuBar.add(_zoomMenu, 0);
300        JMenuItem addItem = new JMenuItem(Bundle.getMessage("NoZoom"));
301        _zoomMenu.add(addItem);
302        addItem.addActionListener((ActionEvent event) -> zoomRestore());
303
304        addItem = new JMenuItem(Bundle.getMessage("Zoom", "..."));
305        _zoomMenu.add(addItem);
306        PositionableJComponent z = new PositionableJComponent(this);
307        z.setScale(getPaintScale());
308        addItem.addActionListener(CoordinateEdit.getZoomEditAction(z));
309
310        addItem = new JMenuItem(Bundle.getMessage("ZoomFit"));
311        _zoomMenu.add(addItem);
312        addItem.addActionListener((ActionEvent event) -> zoomToFit());
313    }
314
315    protected void makeWarrantMenu(boolean edit, boolean addMenu) {
316        JMenu oldMenu = _warrantMenu;
317        _warrantMenu = jmri.jmrit.logix.WarrantTableAction.getDefault().makeWarrantMenu(edit);
318        if (_warrantMenu == null) {
319            _warrantMenu = new JMenu(ResourceBundle.getBundle("jmri.jmrit.logix.WarrantBundle").getString("MenuWarrant"));
320            JMenuItem aboutItem = new JMenuItem(Bundle.getMessage("AboutWarrant"));
321            HelpUtil.enableHelpOnButton(aboutItem, "package.jmri.jmrit.logix.Warrant");
322            _warrantMenu.add(aboutItem);
323            aboutItem = new JMenuItem(Bundle.getMessage("AboutOBlock"));
324            HelpUtil.enableHelpOnButton(aboutItem, "package.jmri.jmrit.logix.OBlockTable");
325            _warrantMenu.add(aboutItem);
326            aboutItem = new JMenuItem(Bundle.getMessage("AboutCircuitMenu"));
327            HelpUtil.enableHelpOnButton(aboutItem, "package.jmri.jmrit.display.CircuitBuilder");
328            _warrantMenu.add(aboutItem);
329            aboutItem.addActionListener((ActionEvent event) -> {
330                makeCircuitMenu(true);
331//                openCircuitWindow();
332            });
333        }
334        if (edit) {
335            makeCircuitMenu(edit);
336            JMenuItem item = new JMenuItem(Bundle.getMessage("OpenCircuitMenu"));
337            _warrantMenu.add(item);
338            item.addActionListener((ActionEvent event) -> _circuitBuilder.openCBWindow());
339        }
340        if (addMenu) {
341            _menuBar.add(_warrantMenu, 0);
342        } else if (oldMenu != null) {
343            int idx = _menuBar.getComponentIndex(oldMenu);
344            _menuBar.remove(oldMenu);
345            _menuBar.add(_warrantMenu, idx);
346
347        }
348        _menuBar.revalidate();
349    }
350
351    protected void makeMarkerMenu() {
352        _markerMenu = new JMenu(Bundle.getMessage("MenuMarker"));
353        _menuBar.add(_markerMenu);
354        _markerMenu.add(new AbstractAction(Bundle.getMessage("AddLoco")) {
355            @Override
356            public void actionPerformed(ActionEvent e) {
357                locoMarkerFromInput();
358            }
359        });
360        _markerMenu.add(new AbstractAction(Bundle.getMessage("AddLocoRoster")) {
361            @Override
362            public void actionPerformed(ActionEvent e) {
363                locoMarkerFromRoster();
364            }
365        });
366        _markerMenu.add(new AbstractAction(Bundle.getMessage("RemoveMarkers")) {
367            @Override
368            public void actionPerformed(ActionEvent e) {
369                removeMarkers();
370            }
371        });
372    }
373
374    protected void makeOptionMenu() {
375        _optionMenu = new JMenu(Bundle.getMessage("MenuOptions"));
376        _menuBar.add(_optionMenu, 0);
377        // use globals item
378        _optionMenu.add(useGlobalFlagBox);
379        useGlobalFlagBox.addActionListener((ActionEvent event) -> setUseGlobalFlag(useGlobalFlagBox.isSelected()));
380        useGlobalFlagBox.setSelected(useGlobalFlag());
381        // positionable item
382        _optionMenu.add(positionableBox);
383        positionableBox.addActionListener((ActionEvent event) -> setAllPositionable(positionableBox.isSelected()));
384        positionableBox.setSelected(allPositionable());
385        // controlable item
386        _optionMenu.add(controllingBox);
387        controllingBox.addActionListener((ActionEvent event) -> setAllControlling(controllingBox.isSelected()));
388        controllingBox.setSelected(allControlling());
389        // hidden item
390        _optionMenu.add(hiddenBox);
391        hiddenBox.addActionListener((ActionEvent event) -> setShowHidden(hiddenBox.isSelected()));
392        hiddenBox.setSelected(showHidden());
393
394        _optionMenu.add(showTooltipBox);
395        showTooltipBox.addActionListener((ActionEvent e) -> setAllShowToolTip(showTooltipBox.isSelected()));
396        showTooltipBox.setSelected(showToolTip());
397
398        // Show/Hide Scroll Bars
399        JMenu scrollMenu = new JMenu(Bundle.getMessage("ComboBoxScrollable"));
400        _optionMenu.add(scrollMenu);
401        ButtonGroup scrollGroup = new ButtonGroup();
402        scrollGroup.add(scrollBoth);
403        scrollMenu.add(scrollBoth);
404        scrollBoth.addActionListener((ActionEvent event) -> setScroll(SCROLL_BOTH));
405        scrollGroup.add(scrollNone);
406        scrollMenu.add(scrollNone);
407        scrollNone.addActionListener((ActionEvent event) -> setScroll(SCROLL_NONE));
408        scrollGroup.add(scrollHorizontal);
409        scrollMenu.add(scrollHorizontal);
410        scrollHorizontal.addActionListener((ActionEvent event) -> setScroll(SCROLL_HORIZONTAL));
411        scrollGroup.add(scrollVertical);
412        scrollMenu.add(scrollVertical);
413        scrollVertical.addActionListener((ActionEvent event) -> setScroll(SCROLL_VERTICAL));
414    }
415
416    private void makeFileMenu() {
417        _fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
418        _menuBar.add(_fileMenu, 0);
419        _fileMenu.add(new jmri.jmrit.display.NewPanelAction(Bundle.getMessage("MenuItemNew")));
420
421        _fileMenu.add(new jmri.configurexml.StoreXmlUserAction(Bundle.getMessage("FileMenuItemStore")));
422        JMenuItem storeIndexItem = new JMenuItem(Bundle.getMessage("MIStoreImageIndex"));
423        _fileMenu.add(storeIndexItem);
424        storeIndexItem.addActionListener((ActionEvent event) -> InstanceManager.getDefault(CatalogTreeManager.class).storeImageIndex());
425
426        JMenuItem editItem = new JMenuItem(Bundle.getMessage("renamePanelMenu", "..."));
427        PositionableJComponent z = new PositionableJComponent(this);
428        z.setScale(getPaintScale());
429        editItem.addActionListener(CoordinateEdit.getNameEditAction(z));
430        _fileMenu.add(editItem);
431
432        editItem = new JMenuItem(Bundle.getMessage("editIndexMenu"));
433        _fileMenu.add(editItem);
434        editItem.addActionListener((ActionEvent event) -> {
435                ImageIndexEditor ii = InstanceManager.getDefault(ImageIndexEditor.class);
436                ii.pack();
437                ii.setVisible(true);
438        });
439
440        editItem = new JMenuItem(Bundle.getMessage("PEView"));
441        _fileMenu.add(editItem);
442        editItem.addActionListener((ActionEvent event) -> {
443            changeView("jmri.jmrit.display.panelEditor.PanelEditor");
444            if (_itemPalette != null) {
445                _itemPalette.dispose();
446            }
447        });
448
449        _fileMenu.addSeparator();
450        JMenuItem deleteItem = new JMenuItem(Bundle.getMessage("DeletePanel"));
451        _fileMenu.add(deleteItem);
452        deleteItem.addActionListener((ActionEvent event) -> {
453            if (deletePanel()) {
454                dispose();
455            }
456        });
457        _fileMenu.addSeparator();
458        editItem = new JMenuItem(Bundle.getMessage("CloseEditor"));
459        _fileMenu.add(editItem);
460        editItem.addActionListener((ActionEvent event) -> setAllEditable(false));
461    }
462
463    /**
464     * Create an Edit menu to support cut/copy/paste. An incredible hack to get
465     * some semblance of CCP between panels. The hack works for one of two
466     * problems. 1. Invoking a copy to the system clipboard causes a delayed
467     * repaint placed on the EventQueue whenever ScrollBars are invoked. This
468     * repaint ends with a null pointer exception at
469     * javax.swing.plaf.basic.BasicScrollPaneUI.paint(BasicScrollPaneUI.java:90)
470     * This error occurs regardless of the method used to put the copy in the
471     * clipboard - JDK 1.2 style or 1.4 TransferHandler Fixed! Get the plaf glue
472     * (BasicScrollPaneUI) and call installUI(_panelScrollPane) See
473     * copyToClipboard() below, line 527 (something the Java code should have
474     * done) No scrollbars - no problem. Hack does not fix this problem. 2. The
475     * clipboard provides a shallow copy of what was placed there. For things
476     * that have an icon Map (ArrayLists) the Tranferable data is shallow. The
477     * Hack to work around this is: Place a reference to the panel copying to
478     * the clipboard in the clipboard and let the pasting panel callback to the
479     * copying panel to get the data. See public ArrayList&lt;Positionable&gt;
480     * getClipGroup() {} below.
481     */
482    protected void makeEditMenu() {
483        _editMenu = new JMenu(Bundle.getMessage("ButtonEdit"));
484        _menuBar.add(_editMenu, 0);
485        _editMenu.setMnemonic(KeyEvent.VK_E);
486        JMenuItem menuItem = new JMenuItem(Bundle.getMessage("MenuItemCut"));
487        menuItem.addActionListener((ActionEvent event) -> {
488            copyToClipboard();
489            removeSelections(null);
490        });
491        setMenuAcceleratorKey(menuItem, KeyEvent.VK_X);
492        _editMenu.add(menuItem);
493
494        menuItem = new JMenuItem(Bundle.getMessage("MenuItemCopy"));
495        menuItem.addActionListener((ActionEvent event) -> copyToClipboard());
496        setMenuAcceleratorKey(menuItem, KeyEvent.VK_C);
497        _editMenu.add(menuItem);
498
499        menuItem = new JMenuItem(Bundle.getMessage("MenuItemPaste"));
500        menuItem.addActionListener((ActionEvent event) -> pasteFromClipboard());
501        setMenuAcceleratorKey(menuItem, KeyEvent.VK_V);
502        _editMenu.add(menuItem);
503
504        _editMenu.add(makeSelectTypeMenu());
505        _editMenu.add(makeSelectLevelMenu());
506
507        menuItem = new JMenuItem(Bundle.getMessage("SelectAll"));
508        menuItem.addActionListener((ActionEvent event) -> {
509            _selectionGroup = new ArrayList<>(getContents());
510            _targetPanel.repaint();
511        });
512        setMenuAcceleratorKey(menuItem, KeyEvent.VK_A);
513        _editMenu.add(menuItem);
514    }
515
516    private JMenu makeSelectTypeMenu() {
517        JMenu menu = new JMenu(Bundle.getMessage("SelectType"));
518        ButtonGroup typeGroup = new ButtonGroup();
519        // I18N use existing jmri.NamedBeanBundle keys
520        JRadioButtonMenuItem button = makeSelectTypeButton("IndicatorTrack", "jmri.jmrit.display.IndicatorTrackIcon");
521        typeGroup.add(button);
522        menu.add(button);
523        button = makeSelectTypeButton("IndicatorTO", "jmri.jmrit.display.IndicatorTurnoutIcon");
524        typeGroup.add(button);
525        menu.add(button);
526        button = makeSelectTypeButton("BeanNameTurnout", "jmri.jmrit.display.TurnoutIcon");
527        typeGroup.add(button);
528        menu.add(button);
529        button = makeSelectTypeButton("BeanNameSensor", "jmri.jmrit.display.SensorIcon");
530        typeGroup.add(button);
531        menu.add(button);
532        button = makeSelectTypeButton("Shape", "jmri.jmrit.display.controlPanelEditor.shape.PositionableShape");
533        typeGroup.add(button);
534        menu.add(button);
535        button = makeSelectTypeButton("BeanNameSignalMast", "jmri.jmrit.display.SignalMastIcon");
536        typeGroup.add(button);
537        menu.add(button);
538        button = makeSelectTypeButton("BeanNameSignalHead", "jmri.jmrit.display.SignalHeadIcon");
539        typeGroup.add(button);
540        menu.add(button);
541        button = makeSelectTypeButton("BeanNameMemory", "jmri.jmrit.display.MemoryIcon");
542        typeGroup.add(button);
543        menu.add(button);
544        button = makeSelectTypeButton("MemoryInput", "jmri.jmrit.display.PositionableJPanel");
545        typeGroup.add(button);
546        menu.add(button);
547        button = makeSelectTypeButton("MultiSensor", "jmri.jmrit.display.MultiSensorIcon");
548        typeGroup.add(button);
549        menu.add(button);
550        button = makeSelectTypeButton("LocoID", "jmri.jmrit.display.LocoIcon");
551        typeGroup.add(button);
552        menu.add(button);
553        button = makeSelectTypeButton("BeanNameLight", "jmri.jmrit.display.LightIcon");
554        typeGroup.add(button);
555        menu.add(button);
556        return menu;
557    }
558
559    private JRadioButtonMenuItem makeSelectTypeButton(String label, String className) {
560        JRadioButtonMenuItem button = new JRadioButtonMenuItem(Bundle.getMessage(label));
561        button.addActionListener(new ActionListener() {
562            String cName;
563
564            ActionListener init(String name) {
565                cName = name;
566                return this;
567            }
568
569            @Override
570            public void actionPerformed(ActionEvent event) {
571                selectType(cName);
572            }
573        }.init(className));
574        return button;
575    }
576
577    private void selectType(String name) {
578        try {
579            Class<?> cl = Class.forName(name);
580            _selectionGroup = new ArrayList<>();
581            for (Positionable pos : getContents()) {
582                if (cl.isInstance(pos)) {
583                    _selectionGroup.add(pos);
584                }
585            }
586        } catch (ClassNotFoundException cnfe) {
587            log.error("selectType Menu {}", cnfe.toString());
588        }
589        _targetPanel.repaint();
590    }
591
592    private JMenu makeSelectLevelMenu() {
593        JMenu menu = new JMenu(Bundle.getMessage("SelectLevel"));
594        ButtonGroup levelGroup = new ButtonGroup();
595        JRadioButtonMenuItem button;
596        for (int i = 0; i < 11; i++) {
597            button = new JRadioButtonMenuItem(Bundle.getMessage("selectLevel", "" + i));
598            levelGroup.add(button);
599            menu.add(button);
600            button.addActionListener(new ActionListener() {
601                int j;
602
603                ActionListener init(int k) {
604                    j = k;
605                    return this;
606                }
607
608                @Override
609                public void actionPerformed(ActionEvent event) {
610                    selectLevel(j);
611                }
612            }.init(i));
613        }
614        return menu;
615    }
616
617    private void selectLevel(int i) {
618        _selectionGroup = new ArrayList<>();
619        for (Positionable pos : getContents()) {
620            if (pos.getDisplayLevel() == i) {
621                _selectionGroup.add(pos);
622            }
623        }
624        _targetPanel.repaint();
625    }
626
627    ////////////////////////// end Menus //////////////////////////
628    public CircuitBuilder getCircuitBuilder() {
629        return _circuitBuilder;
630    }
631
632    private void pasteFromClipboard() {
633        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
634        DataFlavor[] flavors = clipboard.getAvailableDataFlavors();
635        for (DataFlavor flavor : flavors) {
636            if (_positionableListDataFlavor.equals(flavor)) {
637                try {
638                    @SuppressWarnings("unchecked")
639                    List<Positionable> clipGroup = (List<Positionable>) clipboard.getData(_positionableListDataFlavor);
640                    if (clipGroup != null && clipGroup.size() > 0) {
641                        Positionable pos = clipGroup.get(0);
642                        int minX = pos.getLocation().x;
643                        int minY = pos.getLocation().y;
644                        // locate group at mouse point
645                        for (int i = 1; i < clipGroup.size(); i++) {
646                            pos = clipGroup.get(i);
647                            minX = Math.min(minX, pos.getLocation().x);
648                            minY = Math.min(minY, pos.getLocation().y);
649                        }
650                        if (_pastePending) {
651                            abortPasteItems();
652                        }
653                        _selectionGroup = new ArrayList<>();
654                        for (Positionable positionable : clipGroup) {
655                            pos = positionable;
656                            // make positionable belong to this editor
657                            pos.setEditor(this);
658                            pos.setLocation(pos.getLocation().x + _anchorX - minX, pos.getLocation().y + _anchorY - minY);
659                            // now set display level in the pane.
660                            pos.setDisplayLevel(pos.getDisplayLevel());
661                            try {
662                                pos.setId(null);
663                                putItem(pos);
664                            } catch (Positionable.DuplicateIdException e) {
665                                // This should never happen
666                                log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
667                            }
668                            pos.updateSize();
669                            pos.setVisible(true);
670                            _selectionGroup.add(pos);
671                            if (pos instanceof PositionableIcon) {
672                                jmri.NamedBean bean = pos.getNamedBean();
673                                if (bean != null) {
674                                    ((PositionableIcon) pos).displayState(bean.getState());
675                                }
676                            } else if (pos instanceof MemoryOrGVIcon) {
677                                ((MemoryOrGVIcon) pos).displayState();
678                            } else if (pos instanceof PositionableJComponent) {
679                                ((PositionableJComponent) pos).displayState();
680                            }
681                            log.debug("Paste Added at ({}, {})", pos.getLocation().x, pos.getLocation().y);
682                        }
683                    }
684                    return;
685                } catch (IOException ioe) {
686                    log.warn("Editor Paste caught IOException", ioe);
687                } catch (UnsupportedFlavorException ufe) {
688                    log.warn("Editor Paste caught UnsupportedFlavorException", ufe);
689                }
690            }
691        }
692    }
693
694    /*
695     * The editor instance is dragged.  When dropped this editor will reference
696     * the list of positionables (_clipGroup) for pasting
697     */
698    private void copyToClipboard() {
699        if (_selectionGroup != null) {
700            ArrayList<Positionable> dragGroup = new ArrayList<>();
701
702            for (Positionable comp : _selectionGroup) {
703                Positionable pos = comp.deepClone();
704                dragGroup.add(pos);
705                removeFromTarget(pos);   // cloned item gets added to _targetPane during cloning
706            }
707            log.debug("copyToClipboard: cloned _selectionGroup, size= {}", _selectionGroup.size());
708            _clipGroup = dragGroup;
709
710            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
711            clipboard.setContents(new PositionableListDnD(_clipGroup), this);
712            log.debug("copyToClipboard: setContents _selectionGroup, size= {}", _selectionGroup.size());
713        } else {
714            _clipGroup = null;
715        }
716    }
717
718    ArrayList<Positionable> _clipGroup;
719
720    public ArrayList<Positionable> getClipGroup() {
721        if (log.isDebugEnabled()) { // avoid string concatination if not debug
722            log.debug("getClipGroup: _clipGroup{}", _clipGroup == null ? "=null" : ", size= " + _clipGroup.size());
723        }
724        if (_clipGroup == null) {
725            return null;
726        }
727        ArrayList<Positionable> clipGrp = new ArrayList<>();
728        for (Positionable _comp : _clipGroup) {
729            Positionable pos = _comp.deepClone();
730            clipGrp.add(pos);
731            removeFromTarget(pos);   // cloned item gets added to _targetPane during cloning
732        }
733        return clipGrp;
734    }
735
736    ///// implementation of ClipboardOwner
737    @Override
738    public void lostOwnership(Clipboard clipboard, Transferable contents) {
739        /* don't care */
740        log.debug("lostOwnership: content flavor[0] = {}", contents.getTransferDataFlavors()[0]);
741    }
742
743    @Override
744    public void setAllEditable(boolean edit) {
745        if (_warrantMenu != null) {
746            _menuBar.remove(_warrantMenu);
747        }
748        if (_circuitMenu != null) {
749            _menuBar.remove(_circuitMenu);
750            _circuitMenu = null;
751        }
752        if (edit) {
753            if (_editorMenu != null) {
754                _menuBar.remove(_editorMenu);
755            }
756            if (_markerMenu != null) {
757                _menuBar.remove(_markerMenu);
758            }
759            if (_drawMenu == null) {
760                makeDrawMenu();
761            } else {
762                _menuBar.add(_drawMenu, 0);
763            }
764            makeWarrantMenu(true, true);
765
766            if (_iconMenu == null) {
767                makeIconMenu();
768            } else {
769                _menuBar.add(_iconMenu, 0);
770            }
771            if (_zoomMenu == null) {
772                makeZoomMenu();
773            } else {
774                _menuBar.add(_zoomMenu, 0);
775            }
776            if (_optionMenu == null) {
777                makeOptionMenu();
778            } else {
779                _menuBar.add(_optionMenu, 0);
780            }
781            if (_editMenu == null) {
782                makeEditMenu();
783            } else {
784                _menuBar.add(_editMenu, 0);
785            }
786            if (_fileMenu == null) {
787                makeFileMenu();
788            } else {
789                _menuBar.add(_fileMenu, 0);
790            }
791        } else {
792            if (_fileMenu != null) {
793                _menuBar.remove(_fileMenu);
794            }
795            if (_editMenu != null) {
796                _menuBar.remove(_editMenu);
797            }
798            if (_optionMenu != null) {
799                _menuBar.remove(_optionMenu);
800            }
801            if (_zoomMenu != null) {
802                _menuBar.remove(_zoomMenu);
803            }
804            if (_iconMenu != null) {
805                _menuBar.remove(_iconMenu);
806            }
807            if (_drawMenu != null) {
808                _menuBar.remove(_drawMenu);
809            }
810            if (InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getNamedBeanSet().size() > 1) {
811                makeWarrantMenu(false, true);
812//                _circuitMenu = null;
813            }
814            if (_markerMenu == null) {
815                makeMarkerMenu();
816            } else {
817                _menuBar.add(_markerMenu, 0);
818            }
819            if (_editorMenu == null) {  // replaces _fileMenu
820                _editorMenu = new JMenu(Bundle.getMessage("MenuEdit"));
821                _editorMenu.add(new AbstractAction(Bundle.getMessage("OpenEditor")) {
822                    @Override
823                    public void actionPerformed(ActionEvent e) {
824                        setAllEditable(true);
825                    }
826                });
827            }
828            _menuBar.add(_editorMenu, 0);
829        }
830        super.setAllEditable(edit);
831        setTitle();
832        _menuBar.revalidate();
833    }
834
835    @Override
836    public void setUseGlobalFlag(boolean set) {
837        positionableBox.setEnabled(set);
838        controllingBox.setEnabled(set);
839        super.setUseGlobalFlag(set);
840    }
841
842    private void zoomRestore() {
843        List<Positionable> contents = getContents();
844        for (Positionable p : contents) {
845            p.setLocation(p.getX() + _fitX, p.getY() + _fitY);
846        }
847        setPaintScale(1.0);
848    }
849
850    int _fitX = 0;
851    int _fitY = 0;
852
853    private void zoomToFit() {
854        double minX = 1000.0;
855        double maxX = 0.0;
856        double minY = 1000.0;
857        double maxY = 0.0;
858        List<Positionable> contents = getContents();
859        for (Positionable p : contents) {
860            minX = Math.min(p.getX(), minX);
861            minY = Math.min(p.getY(), minY);
862            maxX = Math.max(p.getX() + p.getWidth(), maxX);
863            maxY = Math.max(p.getY() + p.getHeight(), maxY);
864        }
865        _fitX = (int) Math.floor(minX);
866        _fitY = (int) Math.floor(minY);
867
868        JFrame frame = getTargetFrame();
869        Container contentPane = getTargetFrame().getContentPane();
870        Dimension dim = contentPane.getSize();
871        Dimension d = getTargetPanel().getSize();
872        getTargetPanel().setSize((int) Math.ceil(maxX - minX), (int) Math.ceil(maxY - minY));
873
874        JScrollPane scrollPane = getPanelScrollPane();
875        scrollPane.getHorizontalScrollBar().setValue(0);
876        scrollPane.getVerticalScrollBar().setValue(0);
877        JViewport viewPort = scrollPane.getViewport();
878        Dimension dv = viewPort.getExtentSize();
879
880        int dX = frame.getWidth() - dv.width;
881        int dY = frame.getHeight() - dv.height;
882        log.debug("zoomToFit: layoutWidth= {}, layoutHeight= {}\n\tframeWidth= {}, frameHeight= {}, viewWidth= {}, viewHeight= {}\n\tconWidth= {}, conHeight= {}, panelWidth= {}, panelHeight= {}",
883                (maxX - minX), (maxY - minY), frame.getWidth(), frame.getHeight(), dv.width, dv.height, dim.width, dim.height, d.width, d.height);
884        double ratioX = dv.width / (maxX - minX);
885        double ratioY = dv.height / (maxY - minY);
886        double ratio = Math.min(ratioX, ratioY);
887        /*
888         if (ratioX<ratioY) {
889         if (ratioX>1.0) {
890         ratio = ratioX;
891         } else {
892         ratio = ratioY;
893         }
894         } else {
895         if (ratioY<1.0) {
896         ratio = ratioX;
897         } else {
898         ratio = ratioY;
899         }
900         } */
901        _fitX = (int) Math.floor(minX);
902        _fitY = (int) Math.floor(minY);
903        for (Positionable p : contents) {
904            p.setLocation(p.getX() - _fitX, p.getY() - _fitY);
905        }
906        setScroll(SCROLL_BOTH);
907        setPaintScale(ratio);
908        setScroll(SCROLL_NONE);
909        scrollNone.setSelected(true);
910        //getTargetPanel().setSize((int)Math.ceil(maxX), (int)Math.ceil(maxY));
911        frame.setSize((int) Math.ceil((maxX - minX) * ratio) + dX, (int) Math.ceil((maxY - minY) * ratio) + dY);
912        scrollPane.getHorizontalScrollBar().setValue(0);
913        scrollPane.getVerticalScrollBar().setValue(0);
914        log.debug("zoomToFit: ratio= {}, w= {}, h= {}, frameWidth= {}, frameHeight= {}",
915                ratio, (maxX - minX), (maxY - minY), frame.getWidth(), frame.getHeight());
916    }
917
918    @Override
919    public void setTitle() {
920        String name = getName();
921        if (name == null || name.length() == 0) {
922            name = Bundle.getMessage("untitled");
923        }
924        String ending = " " + Bundle.getMessage("LabelEditor");
925        String defaultName = Bundle.getMessage("ControlPanelEditor");
926        defaultName = defaultName.substring(0, defaultName.length() - ending.length());
927        if (name.endsWith(ending)) {
928            name = name.substring(0, name.length() - ending.length());
929        }
930        if (name.equals(defaultName)) {
931            name = Bundle.getMessage("untitled") + "(" + name + ")";
932        }
933       if (isEditable()) {
934            super.setTitle(name + " " + Bundle.getMessage("LabelEditor"));
935        } else {
936            super.setTitle(name);
937        }
938        setName(name);
939    }
940
941    // all content loaded from file.
942    public void loadComplete() {
943        log.debug("loadComplete");
944    }
945
946    /**
947     * After construction, initialize all the widgets to their saved config
948     * settings.
949     */
950    @Override
951    public void initView() {
952        positionableBox.setSelected(allPositionable());
953        controllingBox.setSelected(allControlling());
954        //showCoordinatesBox.setSelected(showCoordinates());
955        showTooltipBox.setSelected(showToolTip());
956        hiddenBox.setSelected(showHidden());
957        switch (_scrollState) {
958            case SCROLL_NONE:
959                scrollNone.setSelected(true);
960                break;
961            case SCROLL_BOTH:
962                scrollBoth.setSelected(true);
963                break;
964            case SCROLL_HORIZONTAL:
965                scrollHorizontal.setSelected(true);
966                break;
967            case SCROLL_VERTICAL:
968                scrollVertical.setSelected(true);
969                break;
970            default:
971                log.warn("Unhandled scroll state: {}", _scrollState);
972                break;
973        }
974        log.debug("InitView done");
975    }
976
977    ////////////////// Overridden methods of Editor //////////////////
978    private boolean _manualSelection = false;
979
980    @Override
981    public void deselectSelectionGroup() {
982        _circuitBuilder.hidePortalIcons(false);
983        super.deselectSelectionGroup();
984    }
985
986    protected Positionable getCurrentSelection(JmriMouseEvent event) {
987        if (_pastePending && !event.isPopupTrigger() && !event.isMetaDown() && !event.isAltDown()) {
988            return getCopySelection(event);
989        }
990        List<Positionable> selections = getSelectedItems(event);
991        if (_disableShapeSelection || _disablePortalSelection) {
992            ArrayList<Positionable> list = new ArrayList<>();
993            for (Positionable pos : selections) {
994                if (_disableShapeSelection && pos instanceof jmri.jmrit.display.controlPanelEditor.shape.PositionableShape) {
995                    continue;
996                }
997                if (_disablePortalSelection && pos instanceof PortalIcon) {
998                    continue;
999                }
1000                list.add(pos);
1001            }
1002            selections = list;
1003        }
1004        Positionable selection = null;
1005        if (selections.size() > 0) {
1006            if (event.isControlDown()) {
1007                if (event.isShiftDown() && selections.size() > 3) {
1008                    if (_manualSelection) {
1009                        // selection made - don't change it
1010                        deselectSelectionGroup();
1011                        return _currentSelection;
1012                    }
1013                    // show list
1014                    String[] selects = new String[selections.size()];
1015                    Iterator<Positionable> iter = selections.iterator();
1016                    int i = 0;
1017                    while (iter.hasNext()) {
1018                        Positionable pos = iter.next();
1019                        if (pos instanceof jmri.NamedBean) {
1020                            selects[i++] = ((jmri.NamedBean) pos).getDisplayName();
1021                        } else {
1022                            selects[i++] = pos.getNameString();
1023                        }
1024                    }
1025                    Object select = JmriJOptionPane.showInputDialog(this, Bundle.getMessage("multipleSelections"),
1026                            Bundle.getMessage("QuestionTitle"), JmriJOptionPane.QUESTION_MESSAGE,
1027                            null, selects, null);
1028                    if (select != null) {
1029                        iter = selections.iterator();
1030                        while (iter.hasNext()) {
1031                            Positionable pos = iter.next();
1032                            String name;
1033                            if (pos instanceof jmri.NamedBean) {
1034                                name = ((jmri.NamedBean) pos).getDisplayName();
1035                            } else {
1036                                name = pos.getNameString();
1037                            }
1038                            if (select.equals(name)) {
1039                                _manualSelection = true;
1040                                highlight(pos);
1041                                return pos;
1042                            }
1043                        }
1044                    } else {
1045                        selection = selections.get(selections.size() - 1);
1046                    }
1047                } else {
1048                    // select bottom-most item over the background, otherwise take the background item
1049                    selection = selections.get(selections.size() - 1);
1050                    if (selection.getDisplayLevel() <= BKG && selections.size() > 1) {
1051                        selection = selections.get(selections.size() - 2);
1052                    }
1053//              _manualSelection = false;
1054                }
1055            } else {
1056                if (event.isShiftDown() && selections.size() > 1) {
1057                    selection = selections.get(1);
1058                } else {
1059                    selection = selections.get(0);
1060                }
1061                if (selection.getDisplayLevel() <= BKG) {
1062                    selection = null;
1063                }
1064                _manualSelection = false;
1065            }
1066        } else {
1067            if (event.isControlDown() && (event.isPopupTrigger() || event.isMetaDown() || event.isAltDown())) {
1068                ActionListener ca;
1069                Editor ed = this;
1070                ca = e -> {
1071                    if (_itemPalette != null) {
1072                        _itemPalette.setEditor(ed);
1073                    }
1074                };
1075                new ColorDialog(this, getTargetPanel(), ColorDialog.ONLY, ca);
1076            }
1077        }
1078        if (!isEditable() && selection != null && selection.isHidden()) {
1079            selection = null;
1080        }
1081        return selection;
1082    }
1083
1084    private Positionable getCopySelection(JmriMouseEvent event) {
1085        if (_selectionGroup == null) {
1086            return null;
1087        }
1088        double x = event.getX();
1089        double y = event.getY();
1090
1091        for (Positionable p : _selectionGroup) {
1092            Rectangle2D.Double rect2D = new Rectangle2D.Double(p.getX() * _paintScale,
1093                    p.getY() * _paintScale,
1094                    p.maxWidth() * _paintScale,
1095                    p.maxHeight() * _paintScale);
1096            if (rect2D.contains(x, y)) {
1097                return p;
1098            }
1099        }
1100        return null;
1101    }
1102
1103    /**
1104     * Capture key events.
1105     *
1106     * @param e the event
1107     */
1108    @Override
1109    public void keyPressed(KeyEvent e) {
1110        int x = 0;
1111        int y = 0;
1112        switch (e.getKeyCode()) {
1113            case KeyEvent.VK_UP:
1114            case KeyEvent.VK_KP_UP:
1115            case KeyEvent.VK_NUMPAD8:
1116                y = -1;
1117                break;
1118            case KeyEvent.VK_DOWN:
1119            case KeyEvent.VK_KP_DOWN:
1120            case KeyEvent.VK_NUMPAD2:
1121                y = 1;
1122                break;
1123            case KeyEvent.VK_LEFT:
1124            case KeyEvent.VK_KP_LEFT:
1125            case KeyEvent.VK_NUMPAD4:
1126                x = -1;
1127                break;
1128            case KeyEvent.VK_RIGHT:
1129            case KeyEvent.VK_KP_RIGHT:
1130            case KeyEvent.VK_NUMPAD6:
1131                x = 1;
1132                break;
1133            case KeyEvent.VK_D:
1134            case KeyEvent.VK_DELETE:
1135            case KeyEvent.VK_MINUS:
1136                _shapeDrawer.delete();
1137                break;
1138            case KeyEvent.VK_A:
1139            case KeyEvent.VK_INSERT:
1140            case KeyEvent.VK_PLUS:
1141                _shapeDrawer.add(e.isShiftDown());
1142                break;
1143            default:
1144                return;
1145
1146        }
1147        if (e.isShiftDown()) {
1148            x *= 5;
1149            y *= 5;
1150        }
1151        if (_selectionGroup != null) {
1152            for (Positionable comp : _selectionGroup) {
1153                moveItem(comp, x, y);
1154            }
1155        }
1156        repaint();
1157    }
1158
1159    ///////////////// Handle mouse events ////////////////
1160    private long _mouseDownTime = 0;
1161
1162    @Override
1163    public void mousePressed(JmriMouseEvent event) {
1164        _mouseDownTime = System.currentTimeMillis();
1165        setToolTip(null); // ends tooltip if displayed
1166        log.debug("mousePressed at ({},{}) _dragging={}", event.getX(), event.getY(), _dragging);
1167        //  " _selectionGroup= "+(_selectionGroup==null?"null":_selectionGroup.size()));
1168        boolean circuitBuilder = _circuitBuilder.saveSelectionGroup(_selectionGroup);
1169        _anchorX = event.getX();
1170        _anchorY = event.getY();
1171        _lastX = _anchorX;
1172        _lastY = _anchorY;
1173
1174        _currentSelection = getCurrentSelection(event);
1175        _circuitBuilder.doMousePressed(event, _currentSelection);
1176
1177        if (!event.isPopupTrigger() && !event.isMetaDown() && !event.isAltDown() && !circuitBuilder) {
1178            _shapeDrawer.doMousePressed(event, _currentSelection);
1179            if (_currentSelection != null) {
1180                _currentSelection.doMousePressed(event);
1181                if (isEditable()) {
1182                    if (!event.isControlDown()
1183                            && (_selectionGroup != null && !_selectionGroup.contains(_currentSelection))) {
1184                        if (_pastePending) {
1185                            abortPasteItems();
1186                        }
1187                        deselectSelectionGroup();
1188                    }
1189                }
1190            } else {
1191                _highlightcomponent = null;
1192                if (_pastePending) {
1193                    abortPasteItems();
1194                }
1195                deselectSelectionGroup();
1196            }
1197        } else if (_currentSelection == null || (_selectionGroup != null && !_selectionGroup.contains(_currentSelection))) {
1198            deselectSelectionGroup();
1199        }
1200        _circuitBuilder.doMousePressed(event);
1201        _targetPanel.repaint(); // needed for ToolTip
1202    }
1203
1204    @Override
1205    public void mouseReleased(JmriMouseEvent event) {
1206        _mouseDownTime = 0;
1207        setToolTip(null); // ends tooltip if displayed
1208        if (log.isDebugEnabled()) { // avoid string concatination if not debug
1209            log.debug("mouseReleased at ({},{}) dragging={}, pastePending={}, selectRect is{} null",
1210                    event.getX(), event.getY(), _dragging, _pastePending, (_selectRect == null ? "" : " not"));
1211        }
1212        Positionable selection = getCurrentSelection(event);
1213
1214        if ((event.isPopupTrigger() || event.isMetaDown() || event.isAltDown()) /*&& !_dragging*/) {
1215            if (selection != null) {
1216                _highlightcomponent = null;
1217                showPopUp(selection, event);
1218            } else if (_selectRect != null) {
1219                makeSelectionGroup(event);
1220            }
1221        } else {
1222            if (selection != null) {
1223                selection.doMouseReleased(event);
1224            }
1225            // when dragging, don't change selection group
1226            if (_pastePending && _dragging) {
1227                pasteItems();
1228            }
1229            if (isEditable()) {
1230                _shapeDrawer.doMouseReleased(selection, event, this);
1231
1232                if (!_circuitBuilder.doMouseReleased(selection, _dragging)) {
1233                    if (selection != null) {
1234                        if (!_dragging) {
1235                            modifySelectionGroup(selection, event);
1236                        }
1237                    }
1238                    if (_selectRect != null) {
1239                        makeSelectionGroup(event);
1240                    }
1241                    if (_currentSelection != null && (_selectionGroup == null || _selectionGroup.isEmpty())) {
1242                        if (_selectionGroup == null) {
1243                            _selectionGroup = new ArrayList<>();
1244                        }
1245                        _selectionGroup.add(_currentSelection);
1246                    }
1247                }
1248                _currentSelection = selection;
1249            } else {
1250                deselectSelectionGroup();
1251                _currentSelection = null;
1252                _highlightcomponent = null;
1253            }
1254        }
1255        _selectRect = null;
1256
1257        // if not sending MouseClicked, do it here
1258        if (InstanceManager.getDefault(GuiLafPreferencesManager.class).isNonStandardMouseEvent()) {
1259            mouseClicked(event);
1260        }
1261
1262        _lastX = event.getX();
1263        _lastY = event.getY();
1264        _dragging = false;
1265        _currentSelection = null;
1266        _targetPanel.repaint(); // needed for ToolTip
1267//        if (_debug) log.debug("mouseReleased at ("+event.getX()+","+event.getY()+
1268//        " _selectionGroup= "+(_selectionGroup==null?"null":_selectionGroup.size()));
1269    }
1270
1271    private long _clickTime;
1272
1273    @Override
1274    public void mouseClicked(JmriMouseEvent event) {
1275        if (InstanceManager.getDefault(GuiLafPreferencesManager.class).isNonStandardMouseEvent()) {
1276            long time = System.currentTimeMillis();
1277            if (time - _clickTime < 20) {
1278                return;
1279            }
1280            _clickTime = time;
1281        }
1282
1283        setToolTip(null); // ends tooltip if displayed
1284        log.debug("mouseClicked at ({},{})", event.getX(), event.getY());
1285
1286        Positionable selection = getCurrentSelection(event);
1287        if (_shapeDrawer.doMouseClicked(event, this)) {
1288            return;
1289        }
1290
1291        if (event.isPopupTrigger() || event.isMetaDown() || event.isAltDown()) {
1292            if (selection != null) {
1293                _highlightcomponent = null;
1294                showPopUp(selection, event);
1295            }
1296        } else if (selection != null) {
1297            if (_circuitBuilder.doMouseClicked(getSelectedItems(event), event)) {
1298                return;
1299            } else {
1300                selection.doMouseClicked(event);
1301            }
1302            if (selection instanceof IndicatorTrack) {
1303                WarrantTableAction.getDefault().mouseClickedOnBlock(((IndicatorTrack) selection).getOccBlock());
1304            }
1305        }
1306        if (!isEditable()) {
1307            deselectSelectionGroup();
1308            _currentSelection = null;
1309            _highlightcomponent = null;
1310        }
1311        _targetPanel.repaint(); // needed for ToolTip
1312    }
1313
1314    @Override
1315    public void mouseDragged(JmriMouseEvent event) {
1316        //if (_debug) log.debug("mouseDragged at ("+event.getX()+","+event.getY()+")");
1317        setToolTip(null); // ends tooltip if displayed
1318
1319        long time = System.currentTimeMillis();
1320        if (time - _mouseDownTime < 200) {
1321            return;     // don't drag until sure mouse down was not just a select click
1322        }
1323        _dragging = true;
1324
1325        if (_circuitBuilder.doMouseDragged(_currentSelection, event)) {
1326            return;
1327        }
1328
1329        if (!event.isPopupTrigger() && !event.isMetaDown() && !event.isAltDown() && !_shapeDrawer.doMouseDragged(event)
1330                && (isEditable() || _currentSelection instanceof LocoIcon)) {
1331            moveIt:
1332            if (_currentSelection != null && getFlag(OPTION_POSITION, _currentSelection.isPositionable())) {
1333                int deltaX = event.getX() - _lastX;
1334                int deltaY = event.getY() - _lastY;
1335                int minX = getItemX(_currentSelection, deltaX);
1336                int minY = getItemY(_currentSelection, deltaY);
1337                if (_selectionGroup != null && _selectionGroup.contains(_currentSelection)) {
1338                    for (Positionable comp : _selectionGroup) {
1339                        minX = Math.min(getItemX(comp, deltaX), minX);
1340                        minY = Math.min(getItemY(comp, deltaY), minY);
1341                    }
1342                }
1343                if (minX < 0 || minY < 0) {
1344                    // Don't allow move beyond the left or top borders
1345                    break moveIt;
1346                    /*
1347                     // or use this choice:
1348                     // Expand the panel to the left or top as needed by the move
1349                     // Probably not the preferred solution - use the above break
1350                     if (_selectionGroup!=null && _selectionGroup.contains(_currentSelection)) {
1351                     List <Positionable> allItems = getContents();
1352                     for (int i=0; i<allItems.size(); i++){
1353                     moveItem(allItems.get(i), -deltaX, -deltaY);
1354                     }
1355                     } else {
1356                     moveItem(_currentSelection, -deltaX, -deltaY);
1357                     }
1358                     */
1359                }
1360                if (_selectionGroup != null && _selectionGroup.contains(_currentSelection)
1361                        && !_circuitBuilder.dragPortal()) {
1362                    for (Positionable comp : _selectionGroup) {
1363                        moveItem(comp, deltaX, deltaY);
1364                    }
1365                    _highlightcomponent = null;
1366                } else {
1367                    moveItem(_currentSelection, deltaX, deltaY);
1368                }
1369            } else if ((isEditable() && _selectionGroup == null)) {
1370                drawSelectRect(event.getX(), event.getY());
1371            }
1372        }
1373        _highlightGroup.clear();
1374        _lastX = event.getX();
1375        _lastY = event.getY();
1376        _targetPanel.repaint(); // needed for ToolTip
1377    }
1378
1379    @Override
1380    public void mouseMoved(JmriMouseEvent event) {
1381        //if (_debug) log.debug("mouseMoved at ("+event.getX()+","+event.getY()+")");
1382        if (_dragging || event.isPopupTrigger() || event.isMetaDown() || event.isAltDown()) {
1383            return;
1384        }
1385        if (!(event.isShiftDown() && event.isControlDown()) && !_shapeDrawer.doMouseMoved(event)) {
1386            Positionable selection = getCurrentSelection(event);
1387            if (selection != null && selection.getDisplayLevel() > BKG && selection.showToolTip()) {
1388                showToolTip(selection, event);
1389                //selection.highlightlabel(true);
1390            } else {
1391                setToolTip(null);
1392            }
1393        }
1394        _targetPanel.repaint();
1395    }
1396
1397    @Override
1398    public void mouseEntered(JmriMouseEvent event) {
1399        _targetPanel.repaint();
1400    }
1401
1402    @Override
1403    public void mouseExited(JmriMouseEvent event) {
1404        setToolTip(null);
1405        _targetPanel.repaint();  // needed for ToolTip
1406    }
1407
1408    ////////////////// implementation of Abstract Editor methods //////////////////
1409    /**
1410     * The target window has been requested to close, don't delete it at this
1411     * time. Deletion must be accomplished via the Delete this panel menu item.
1412     *
1413     * @param e the triggering event
1414     */
1415    @Override
1416    protected void targetWindowClosingEvent(java.awt.event.WindowEvent e) {
1417        targetWindowClosing();
1418    }
1419
1420    @Override
1421    protected void paintTargetPanel(Graphics g) {
1422        // needed to create PositionablePolygon
1423        _shapeDrawer.paint(g);  // adds to rubber band line
1424
1425        if (!_highlightGroup.isEmpty()) {
1426            g.setColor(((TargetPane) getTargetPanel()).getHighlightColor());
1427            for (Rectangle r : _highlightGroup) {
1428                g.drawRect(r.x, r.y, r.width, r.height);
1429            }
1430        }
1431    }
1432
1433    /**
1434     * Set an object's location when it is created.
1435     */
1436    @Override
1437    public void setNextLocation(Positionable obj) {
1438        obj.setLocation(0, 0);
1439    }
1440
1441    /**
1442     * Set up selections for a paste. Note a copy of _selectionGroup is made
1443     * that is NOT in the _contents. This disconnected ArrayList is added to the
1444     * _contents when (if) a paste is made. The disconnected _selectionGroup can
1445     * be dragged to a new location.
1446     */
1447    @Override
1448    protected void copyItem(Positionable p) {
1449        if (log.isDebugEnabled()) { // avoid string concatination if not debug
1450            log.debug("Enter copyItem: _selectionGroup size={}",
1451                    _selectionGroup != null ? _selectionGroup.size() : "(null)");
1452        }
1453        // If popup menu hit again, Paste selections and make another copy
1454        if (_pastePending) {
1455            pasteItems();
1456        }
1457        if (_selectionGroup != null && !_selectionGroup.contains(p)) {
1458            deselectSelectionGroup();
1459        }
1460        if (_selectionGroup == null) {
1461            _selectionGroup = new ArrayList<>();
1462            _selectionGroup.add(p);
1463        }
1464        ArrayList<Positionable> selectionGroup = new ArrayList<>();
1465        for (Positionable comp : _selectionGroup) {
1466            Positionable pos = comp.deepClone();
1467            selectionGroup.add(pos);
1468        }
1469        _selectionGroup = selectionGroup;  // group is now disconnected
1470        _pastePending = true;
1471        log.debug("Exit copyItem: _selectionGroup.size()={}", _selectionGroup.size());
1472    }
1473
1474    void pasteItems() {
1475        if (_selectionGroup != null) {
1476            for (Positionable pos : _selectionGroup) {
1477                if (pos instanceof PositionableIcon) {
1478                    jmri.NamedBean bean = pos.getNamedBean();
1479                    if (bean != null) {
1480                        ((PositionableIcon) pos).displayState(bean.getState());
1481                    }
1482                }
1483                try {
1484                    pos.setId(null);
1485                    putItem(pos);
1486                } catch (Positionable.DuplicateIdException e) {
1487                    // This should never happen
1488                    log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1489                }
1490                log.debug("Add {}", pos.getNameString());
1491            }
1492            if (_selectionGroup.get(0) instanceof LocoIcon) {
1493                LocoIcon p = (LocoIcon) _selectionGroup.get(0);
1494                CoordinateEdit f = new CoordinateEdit();
1495                f.init("Train Name", p, false);
1496                f.initText();
1497                f.setVisible(true);
1498                f.setLocationRelativeTo(p);
1499            }
1500        }
1501        _pastePending = false;
1502    }
1503
1504    /**
1505     * Showing the popup of a member of _selectionGroup causes an image to be
1506     * placed in to the _targetPanel. If the objects are not put into _contents
1507     * (putItem(p)) the image will persist. Thus set these transitory object
1508     * invisible.
1509     */
1510    void abortPasteItems() {
1511        if (log.isDebugEnabled()) { // avoid string concatination if not debug
1512            log.debug("abortPasteItems: _selectionGroup{}",
1513                    _selectionGroup == null ? "=null" : (".size=" + _selectionGroup.size()));
1514        }
1515        if (_selectionGroup != null) {
1516            for (Positionable comp : _selectionGroup) {
1517                comp.setVisible(false);
1518                comp.remove();
1519            }
1520        }
1521        deselectSelectionGroup();
1522        _pastePending = false;
1523    }
1524
1525    /**
1526     * Add an action to copy the Positionable item and the group to which is may
1527     * belong.
1528     *
1529     * @param p     the copyable item
1530     * @param popup the menu to add it to
1531     */
1532    public void setCopyMenu(Positionable p, JPopupMenu popup) {
1533        JMenuItem edit = new JMenuItem(Bundle.getMessage("MenuItemDuplicate"));
1534        edit.addActionListener(new ActionListener() {
1535            Positionable comp;
1536
1537            @Override
1538            public void actionPerformed(ActionEvent e) {
1539                copyItem(comp);
1540            }
1541
1542            ActionListener init(Positionable pos) {
1543                comp = pos;
1544                return this;
1545            }
1546        }.init(p));
1547        popup.add(edit);
1548    }
1549
1550    @Override
1551    protected void setSelectionsScale(double s, Positionable p) {
1552        if (_circuitBuilder.saveSelectionGroup(_selectionGroup)) {
1553            p.setScale(s);
1554        } else {
1555            super.setSelectionsScale(s, p);
1556        }
1557    }
1558
1559    @Override
1560    protected void setSelectionsRotation(int k, Positionable p) {
1561        if (_circuitBuilder.saveSelectionGroup(_selectionGroup)) {
1562            p.rotate(k);
1563        } else {
1564            super.setSelectionsRotation(k, p);
1565        }
1566    }
1567
1568    /**
1569     * Create popup for a Positionable object. Popup items common to all
1570     * positionable objects are done before and after the items that pertain
1571     * only to specific Positionable types.
1572     *
1573     * @param p     the item containing or requiring the context menu
1574     * @param event the event triggering the menu
1575     */
1576    protected void showPopUp(Positionable p, JmriMouseEvent event) {
1577        if (!((JComponent) p).isVisible()) {
1578            return;     // component must be showing on the screen to determine its location
1579        }
1580        JPopupMenu popup = new JPopupMenu();
1581
1582        PositionablePopupUtil util = p.getPopupUtility();
1583        if (p.isEditable()) {
1584            // items common to all
1585            if (p.doViemMenu()) {
1586                popup.add(p.getNameString());
1587                setPositionableMenu(p, popup);
1588                if (p.isPositionable()) {
1589                    setShowCoordinatesMenu(p, popup);
1590                    setShowAlignmentMenu(p, popup);
1591                }
1592                setDisplayLevelMenu(p, popup);
1593                setHiddenMenu(p, popup);
1594                setEmptyHiddenMenu(p, popup);
1595                setValueEditDisabledMenu(p, popup);
1596                setEditIdMenu(p, popup);
1597                setEditClassesMenu(p, popup);
1598                popup.addSeparator();
1599                setLogixNGPositionableMenu(p, popup);
1600                popup.addSeparator();
1601                setCopyMenu(p, popup);
1602            }
1603
1604            // items with defaults or using overrides
1605            boolean popupSet = false;
1606//            popupSet |= p.setRotateOrthogonalMenu(popup);
1607            popupSet |= p.setRotateMenu(popup);
1608            popupSet |= p.setScaleMenu(popup);
1609            if (popupSet) {
1610                popup.addSeparator();
1611                popupSet = false;
1612            }
1613            popupSet = p.setEditItemMenu(popup);
1614            if (popupSet) {
1615                popup.addSeparator();
1616                popupSet = false;
1617            }
1618            if (p instanceof PositionableLabel) {
1619                PositionableLabel pl = (PositionableLabel) p;
1620                if (pl.isText()) {
1621                    setColorMenu(popup, (JComponent) p, ColorDialog.BORDER);
1622                    setColorMenu(popup, (JComponent) p, ColorDialog.MARGIN);
1623                    setColorMenu(popup, (JComponent) p, ColorDialog.FONT);
1624                    if (!(pl instanceof ReporterIcon) && !(pl instanceof RpsPositionIcon)) {
1625                        popupSet |= pl.setEditTextItemMenu(popup);
1626                    }
1627                }
1628            } else if (p instanceof PositionableJPanel) {
1629                setColorMenu(popup, (JComponent) p, ColorDialog.BORDER);
1630                setColorMenu(popup, (JComponent) p, ColorDialog.MARGIN);
1631                setColorMenu(popup, (JComponent) p, ColorDialog.FONT);
1632                PositionableJPanel pj = (PositionableJPanel) p;
1633                popupSet |= pj.setEditTextItemMenu(popup);
1634            }
1635            if (p instanceof LinkingObject) {
1636                ((LinkingObject) p).setLinkMenu(popup);
1637            }
1638            if (popupSet) {
1639                popup.addSeparator();
1640                popupSet = false;
1641            }
1642            p.setDisableControlMenu(popup);
1643            if (util != null) {
1644                util.setAdditionalEditPopUpMenu(popup);
1645            }
1646            // for Positionables with unique settings
1647            p.showPopUp(popup);
1648
1649            if (p.doViemMenu()) {
1650                setShowToolTipMenu(p, popup);
1651                setRemoveMenu(p, popup);
1652            }
1653        } else {
1654            if (p instanceof LocoIcon) {
1655                setCopyMenu(p, popup);
1656            }
1657            p.showPopUp(popup);
1658            if (util != null) {
1659                util.setAdditionalViewPopUpMenu(popup);
1660            }
1661        }
1662        popup.show((Component) p, p.getWidth() / 2 + (int) ((getPaintScale() - 1.0) * p.getX()),
1663                p.getHeight() / 2 + (int) ((getPaintScale() - 1.0) * p.getY()));
1664
1665        _currentSelection = null;
1666    }
1667
1668    public void setColorMenu(JPopupMenu popup, JComponent pos, int type) {
1669        String title;
1670        switch (type ) {
1671            case ColorDialog.BORDER:
1672                title = "SetBorderSizeColor";
1673                break;
1674            case ColorDialog.MARGIN:
1675                title = "SetMarginSizeColor";
1676                break;
1677            case ColorDialog.FONT:
1678                title = "SetFontSizeColor";
1679                break;
1680            case ColorDialog.TEXT:
1681                title = "SetTextSizeColor";
1682                break;
1683            default:
1684                title = "untitled";
1685                return;
1686        }
1687        JMenuItem edit = new JMenuItem(Bundle.getMessage(title));
1688        edit.addActionListener((ActionEvent event) -> new ColorDialog(this, pos, type, null));
1689        popup.add(edit);
1690    }
1691
1692    /**
1693     * ******************* Circuitbuilder ***********************************
1694     */
1695    protected void disableMenus() {
1696        _drawMenu.setEnabled(false);
1697        _warrantMenu.setEnabled(false);
1698        _iconMenu.setEnabled(false);
1699        _zoomMenu.setEnabled(false);
1700        _optionMenu.setEnabled(false);
1701        _editMenu.setEnabled(false);
1702        _fileMenu.setEnabled(false);
1703        _disablePortalSelection = false;
1704    }
1705
1706    public void resetEditor() {
1707        // enable menus
1708        _drawMenu.setEnabled(true);
1709        _warrantMenu.setEnabled(true);
1710        _iconMenu.setEnabled(true);
1711        _zoomMenu.setEnabled(true);
1712        _optionMenu.setEnabled(true);
1713        _editMenu.setEnabled(true);
1714        _fileMenu.setEnabled(true);
1715        // reset colors
1716        highlight(null);
1717        TargetPane targetPane = (TargetPane) getTargetPanel();
1718        targetPane.setDefaultColors();
1719        targetPane.revalidate();
1720        setSelectionGroup(null);
1721        _disablePortalSelection = true;
1722    }
1723
1724    /**
1725     * Highlight an item.
1726     *
1727     * @param pos the item to hightlight
1728     */
1729    protected void highlight(Positionable pos) {
1730        if (pos == null) {
1731            _highlightGroup.clear();
1732            _highlightcomponent = null;
1733        } else {
1734            Rectangle rect = new Rectangle(pos.getX(), pos.getY(),
1735                    pos.maxWidth(), pos.maxHeight());
1736            _highlightcomponent = rect;
1737            if (!_dragging) {
1738                _highlightGroup.add(rect);
1739            }
1740        }
1741        repaint();
1742    }
1743
1744    protected void setSelectionGroup(ArrayList<Positionable> group) {
1745        _highlightcomponent = null;
1746        _selectionGroup = group;
1747        repaint();
1748    }
1749
1750    protected ArrayList<Positionable> getSelectionGroup() {
1751        return _selectionGroup;
1752    }
1753
1754    /**
1755     * ************************** DnD *************************************
1756     */
1757    protected void makeDataFlavors() {
1758        try {
1759            _positionableDataFlavor = new DataFlavor(POSITIONABLE_FLAVOR);
1760            _namedIconDataFlavor = new DataFlavor(ImageIndexEditor.IconDataFlavorMime);
1761            _positionableListDataFlavor = new DataFlavor(List.class, "JComponentList");
1762        } catch (ClassNotFoundException cnfe) {
1763            log.error("Unable to find class supporting {}", ImageIndexEditor.IconDataFlavorMime, cnfe);
1764        }
1765        new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this);
1766    }
1767
1768    DataFlavor _positionableDataFlavor;
1769    DataFlavor _positionableListDataFlavor;
1770    DataFlavor _namedIconDataFlavor;
1771
1772    /**
1773     * ************************* DropTargetListener ***********************
1774     */
1775    @Override
1776    public void dragExit(DropTargetEvent evt) {
1777    }
1778
1779    @Override
1780    public void dragEnter(DropTargetDragEvent evt) {
1781    }
1782
1783    @Override
1784    public void dragOver(DropTargetDragEvent evt) {
1785    }
1786
1787    @Override
1788    public void dropActionChanged(DropTargetDragEvent evt) {
1789    }
1790
1791    @SuppressWarnings("unchecked")
1792    @Override
1793    public void drop(DropTargetDropEvent evt) {
1794        try {
1795            //Point pt = evt.getLocation(); coords relative to entire window
1796            Point pt = _targetPanel.getMousePosition(true);
1797            if (pt == null) {
1798                return;
1799            }
1800            Transferable tr = evt.getTransferable();
1801            if (log.isDebugEnabled()) { // avoid string building if not debug
1802                DataFlavor[] flavors = tr.getTransferDataFlavors();
1803                StringBuilder flavor = new StringBuilder();
1804                for (DataFlavor flavor1 : flavors) {
1805                    flavor.append(flavor1.getRepresentationClass().getName()).append(", ");
1806                }
1807                log.debug("Editor Drop: flavor classes={}", flavor);
1808            }
1809            if (tr.isDataFlavorSupported(_positionableDataFlavor)) {
1810                Positionable item = (Positionable) tr.getTransferData(_positionableDataFlavor);
1811                if (item == null) {
1812                    return;
1813                }
1814                item.setLocation((int) Math.round(pt.x/getPaintScale()), (int) Math.round(pt.y/getPaintScale()));
1815                // now set display level in the pane.
1816                item.setDisplayLevel(item.getDisplayLevel());
1817                item.setEditor(this);
1818                try {
1819                    putItem(item);
1820                } catch (Positionable.DuplicateIdException e) {
1821                    // This should never happen
1822                    log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1823                }
1824                item.updateSize();
1825                _circuitBuilder.doMouseReleased(item, true);
1826                evt.dropComplete(true);
1827                return;
1828            } else if (tr.isDataFlavorSupported(_namedIconDataFlavor)) {
1829                NamedIcon newIcon = new NamedIcon((NamedIcon) tr.getTransferData(_namedIconDataFlavor));
1830                String url = newIcon.getURL();
1831                NamedIcon icon = NamedIcon.getIconByName(url);
1832                PositionableLabel ni = new PositionableLabel(icon, this);
1833                // infer a background icon from its size
1834                assert icon != null;
1835                if (icon.getIconHeight() > 500 || icon.getIconWidth() > 600) {
1836                    ni.setDisplayLevel(BKG);
1837                } else {
1838                    ni.setDisplayLevel(ICONS);
1839                }
1840                ni.setLocation((int) Math.round(pt.x/getPaintScale()), (int) Math.round(pt.y/getPaintScale()));
1841                ni.setEditor(this);
1842                try {
1843                    putItem(ni);
1844                } catch (Positionable.DuplicateIdException e) {
1845                    // This should never happen
1846                    log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1847                }
1848                ni.updateSize();
1849                evt.dropComplete(true);
1850                return;
1851            } else if (tr.isDataFlavorSupported(DataFlavor.stringFlavor)) {
1852                String text = (String) tr.getTransferData(DataFlavor.stringFlavor);
1853                PositionableLabel l = new PositionableLabel(text, this);
1854                l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
1855                l.setDisplayLevel(LABELS);
1856                l.setLocation((int) Math.round(pt.x/getPaintScale()), (int) Math.round(pt.y/getPaintScale()));
1857                l.setEditor(this);
1858                try {
1859                    putItem(l);
1860                } catch (Positionable.DuplicateIdException e) {
1861                    // This should never happen
1862                    log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1863                }
1864                evt.dropComplete(true);
1865            } else if (tr.isDataFlavorSupported(_positionableListDataFlavor)) {
1866                List<Positionable> dragGroup
1867                        = (List<Positionable>) tr.getTransferData(_positionableListDataFlavor);
1868                for (Positionable pos : dragGroup) {
1869                    pos.setEditor(this);
1870                    pos.setLocation((int) Math.round(pt.x/getPaintScale()), (int) Math.round(pt.y/getPaintScale()));
1871                    try {
1872                        putItem(pos);
1873                    } catch (Positionable.DuplicateIdException ignore) {
1874                        try {
1875                            // Duplicate id so clear the id
1876                            pos.setId(null);
1877                            putItem(pos);
1878                        } catch (Positionable.DuplicateIdException e) {
1879                            // This should never happen
1880                            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
1881                        }
1882                    }
1883                    pos.updateSize();
1884                    log.debug("DnD Add {}", pos.getNameString());
1885                }
1886            } else {
1887                log.warn("Editor DropTargetListener supported DataFlavors not available at drop from {}", tr.getClass().getName());
1888            }
1889        } catch (IOException ioe) {
1890            log.warn("Editor DropTarget caught IOException", ioe);
1891        } catch (UnsupportedFlavorException ufe) {
1892            log.warn("Editor DropTarget caught UnsupportedFlavorException", ufe);
1893        }
1894        log.debug("Editor DropTargetListener drop REJECTED!");
1895        evt.rejectDrop();
1896    }
1897
1898    static protected class PositionableListDnD implements Transferable {
1899//        ControlPanelEditor _sourceEditor;
1900
1901        List<Positionable> _sourceEditor;
1902        DataFlavor _dataFlavor;
1903
1904        PositionableListDnD(List<Positionable> source) {
1905            _sourceEditor = source;
1906            _dataFlavor = new DataFlavor(List.class, "JComponentList");
1907        }
1908
1909        @Override
1910        @Nonnull
1911        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
1912            log.debug("PositionableListDnD.getTransferData:");
1913            if (flavor.equals(_dataFlavor)) {
1914                return _sourceEditor;
1915            }
1916            throw new UnsupportedFlavorException(flavor);
1917        }
1918
1919        @Override
1920        public DataFlavor[] getTransferDataFlavors() {
1921            return new DataFlavor[]{_dataFlavor};
1922        }
1923
1924        @Override
1925        public boolean isDataFlavorSupported(DataFlavor flavor) {
1926            return flavor.equals(_dataFlavor);
1927        }
1928    }
1929
1930    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ControlPanelEditor.class);
1931
1932}