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