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