001package jmri.jmrit.display.layoutEditor;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.*;
006import java.awt.event.*;
007import java.awt.geom.Point2D;
008import java.awt.geom.Rectangle2D;
009import java.beans.PropertyChangeEvent;
010import java.beans.PropertyVetoException;
011import java.io.File;
012import java.lang.reflect.Field;
013import java.text.MessageFormat;
014import java.text.ParseException;
015import java.util.List;
016import java.util.*;
017import java.util.concurrent.ConcurrentHashMap;
018import java.util.stream.Collectors;
019import java.util.stream.Stream;
020
021import javax.annotation.CheckForNull;
022import javax.annotation.Nonnull;
023import javax.swing.*;
024import javax.swing.event.PopupMenuEvent;
025import javax.swing.event.PopupMenuListener;
026import javax.swing.filechooser.FileNameExtensionFilter;
027
028import jmri.*;
029import jmri.configurexml.StoreXmlUserAction;
030import jmri.jmrit.catalog.NamedIcon;
031import jmri.jmrit.dispatcher.DispatcherAction;
032import jmri.jmrit.dispatcher.DispatcherFrame;
033import jmri.jmrit.display.*;
034import jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.*;
035import jmri.jmrit.display.layoutEditor.LayoutEditorToolBarPanel.LocationFormat;
036import jmri.jmrit.display.panelEditor.PanelEditor;
037import jmri.jmrit.entryexit.AddEntryExitPairAction;
038import jmri.jmrit.logixng.GlobalVariable;
039import jmri.swing.NamedBeanComboBox;
040import jmri.util.*;
041import jmri.util.swing.JComboBoxUtil;
042import jmri.util.swing.JmriColorChooser;
043import jmri.util.swing.JmriJOptionPane;
044import jmri.util.swing.JmriMouseEvent;
045
046/**
047 * Provides a scrollable Layout Panel and editor toolbars (that can be hidden)
048 * <p>
049 * This module serves as a manager for the LayoutTurnout, Layout Block,
050 * PositionablePoint, Track Segment, LayoutSlip and LevelXing objects which are
051 * integral subparts of the LayoutEditor class.
052 * <p>
053 * All created objects are put on specific levels depending on their type
054 * (higher levels are in front): Note that higher numbers appear behind lower
055 * numbers.
056 * <p>
057 * The "contents" List keeps track of all text and icon label objects added to
058 * the target frame for later manipulation. Other Lists keep track of drawn
059 * items.
060 * <p>
061 * Based in part on PanelEditor.java (Bob Jacobsen (c) 2002, 2003). In
062 * particular, text and icon label items are copied from Panel editor, as well
063 * as some of the control design.
064 *
065 * @author Dave Duchamp Copyright: (c) 2004-2007
066 * @author George Warner Copyright: (c) 2017-2019
067 */
068public final class LayoutEditor extends PanelEditor implements MouseWheelListener, LayoutModels {
069
070    // Operational instance variables - not saved to disk
071    private JmriJFrame floatingEditToolBoxFrame = null;
072    private JScrollPane floatingEditContentScrollPane = null;
073    private JPanel floatEditHelpPanel = null;
074
075    private JPanel editToolBarContainerPanel = null;
076    private JScrollPane editToolBarScrollPane = null;
077
078    private JPanel helpBarPanel = null;
079    private final JPanel helpBar = new JPanel();
080
081    private final boolean editorUseOldLocSize;
082
083    private LayoutEditorToolBarPanel leToolBarPanel = null;
084
085    @Nonnull
086    public LayoutEditorToolBarPanel getLayoutEditorToolBarPanel() {
087        return leToolBarPanel;
088    }
089
090    // end of main panel controls
091    private boolean delayedPopupTrigger = false;
092    private Point2D currentPoint = new Point2D.Double(100.0, 100.0);
093    private Point2D dLoc = new Point2D.Double(0.0, 0.0);
094
095    private int toolbarHeight = 100;
096    private int toolbarWidth = 100;
097
098    private TrackSegment newTrack = null;
099    private boolean panelChanged = false;
100
101    // size of point boxes
102    public static final double SIZE = 3.0;
103    public static final double SIZE2 = SIZE * 2.; // must be twice SIZE
104
105    public Color turnoutCircleColor = Color.black; // matches earlier versions
106    public Color turnoutCircleThrownColor = Color.black;
107    private boolean turnoutFillControlCircles = false;
108    private int turnoutCircleSize = 4; // matches earlier versions
109
110    // use turnoutCircleSize when you need an int and these when you need a double
111    // note: these only change when setTurnoutCircleSize is called
112    // using these avoids having to call getTurnoutCircleSize() and
113    // the multiply (x2) and the int -> double conversion overhead
114    public double circleRadius = SIZE * getTurnoutCircleSize();
115    public double circleDiameter = 2.0 * circleRadius;
116
117    // selection variables
118    public boolean selectionActive = false;
119    private double selectionX = 0.0;
120    private double selectionY = 0.0;
121    public double selectionWidth = 0.0;
122    public double selectionHeight = 0.0;
123
124    // Option menu items
125    private JCheckBoxMenuItem editModeCheckBoxMenuItem = null;
126
127    private JRadioButtonMenuItem toolBarSideTopButton = null;
128    private JRadioButtonMenuItem toolBarSideLeftButton = null;
129    private JRadioButtonMenuItem toolBarSideBottomButton = null;
130    private JRadioButtonMenuItem toolBarSideRightButton = null;
131    private JRadioButtonMenuItem toolBarSideFloatButton = null;
132
133    private final JCheckBoxMenuItem wideToolBarCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("ToolBarWide"));
134
135    private JCheckBoxMenuItem positionableCheckBoxMenuItem = null;
136    private JCheckBoxMenuItem controlCheckBoxMenuItem = null;
137    private JCheckBoxMenuItem animationCheckBoxMenuItem = null;
138    private JCheckBoxMenuItem showHelpCheckBoxMenuItem = null;
139    private JCheckBoxMenuItem showGridCheckBoxMenuItem = null;
140    private JCheckBoxMenuItem autoAssignBlocksCheckBoxMenuItem = null;
141    private JMenu scrollMenu = null;
142    private JRadioButtonMenuItem scrollBothMenuItem = null;
143    private JRadioButtonMenuItem scrollNoneMenuItem = null;
144    private JRadioButtonMenuItem scrollHorizontalMenuItem = null;
145    private JRadioButtonMenuItem scrollVerticalMenuItem = null;
146    private JMenu tooltipMenu = null;
147    private JRadioButtonMenuItem tooltipAlwaysMenuItem = null;
148    private JRadioButtonMenuItem tooltipNoneMenuItem = null;
149    private JRadioButtonMenuItem tooltipInEditMenuItem = null;
150    private JRadioButtonMenuItem tooltipNotInEditMenuItem = null;
151
152    private JCheckBoxMenuItem pixelsCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("Pixels"));
153    private JCheckBoxMenuItem metricCMCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("MetricCM"));
154    private JCheckBoxMenuItem englishFeetInchesCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("EnglishFeetInches"));
155
156    private JCheckBoxMenuItem snapToGridOnAddCheckBoxMenuItem = null;
157    private JCheckBoxMenuItem snapToGridOnMoveCheckBoxMenuItem = null;
158    private JCheckBoxMenuItem antialiasingOnCheckBoxMenuItem = null;
159    private JCheckBoxMenuItem drawLayoutTracksLabelCheckBoxMenuItem = null;
160    private JCheckBoxMenuItem turnoutCirclesOnCheckBoxMenuItem = null;
161    private JCheckBoxMenuItem turnoutDrawUnselectedLegCheckBoxMenuItem = null;
162    private JCheckBoxMenuItem turnoutFillControlCirclesCheckBoxMenuItem = null;
163    private JCheckBoxMenuItem hideTrackSegmentConstructionLinesCheckBoxMenuItem = null;
164    private JCheckBoxMenuItem useDirectTurnoutControlCheckBoxMenuItem = null;
165    private JCheckBoxMenuItem highlightCursorCheckBoxMenuItem = null;
166    private ButtonGroup turnoutCircleSizeButtonGroup = null;
167
168    private boolean turnoutDrawUnselectedLeg = true;
169    private boolean autoAssignBlocks = false;
170
171    // Tools menu items
172    private final JMenu zoomMenu = new JMenu(Bundle.getMessage("MenuZoom"));
173    private final JRadioButtonMenuItem zoom025Item = new JRadioButtonMenuItem("x 0.25");
174    private final JRadioButtonMenuItem zoom05Item = new JRadioButtonMenuItem("x 0.5");
175    private final JRadioButtonMenuItem zoom075Item = new JRadioButtonMenuItem("x 0.75");
176    private final JRadioButtonMenuItem noZoomItem = new JRadioButtonMenuItem(Bundle.getMessage("NoZoom"));
177    private final JRadioButtonMenuItem zoom15Item = new JRadioButtonMenuItem("x 1.5");
178    private final JRadioButtonMenuItem zoom20Item = new JRadioButtonMenuItem("x 2.0");
179    private final JRadioButtonMenuItem zoom30Item = new JRadioButtonMenuItem("x 3.0");
180    private final JRadioButtonMenuItem zoom40Item = new JRadioButtonMenuItem("x 4.0");
181    private final JRadioButtonMenuItem zoom50Item = new JRadioButtonMenuItem("x 5.0");
182    private final JRadioButtonMenuItem zoom60Item = new JRadioButtonMenuItem("x 6.0");
183    private final JRadioButtonMenuItem zoom70Item = new JRadioButtonMenuItem("x 7.0");
184    private final JRadioButtonMenuItem zoom80Item = new JRadioButtonMenuItem("x 8.0");
185
186    private final JMenuItem undoTranslateSelectionMenuItem = new JMenuItem(Bundle.getMessage("UndoTranslateSelection"));
187    private final JMenuItem assignBlockToSelectionMenuItem = new JMenuItem(Bundle.getMessage("AssignBlockToSelectionTitle") + "...");
188
189    // Selected point information
190    private Point2D startDelta = new Point2D.Double(0.0, 0.0); // starting delta coordinates
191    public Object selectedObject = null;       // selected object, null if nothing selected
192    public Object prevSelectedObject = null;   // previous selected object, for undo
193    private HitPointType selectedHitPointType = HitPointType.NONE;         // hit point type within the selected object
194
195    public LayoutTrack foundTrack = null;      // found object, null if nothing found
196    public LayoutTrackView foundTrackView = null;                 // found view object, null if nothing found
197    private Point2D foundLocation = new Point2D.Double(0.0, 0.0); // location of found object
198    public HitPointType foundHitPointType = HitPointType.NONE;          // connection type within the found object
199
200    public LayoutTrack beginTrack = null;      // begin track segment connection object, null if none
201    public Point2D beginLocation = new Point2D.Double(0.0, 0.0); // location of begin object
202    private HitPointType beginHitPointType = HitPointType.NONE; // connection type within begin connection object
203
204    public Point2D currentLocation = new Point2D.Double(0.0, 0.0); // current location
205
206    // Lists of items that describe the Layout, and allow it to be drawn
207    // Each of the items must be saved to disk over sessions
208    private List<AnalogClock2Display> clocks = new ArrayList<>();           // fast clocks
209    private List<LocoIcon> markerImage = new ArrayList<>();                 // marker images
210    private List<MultiSensorIcon> multiSensors = new ArrayList<>();         // multi-sensor images
211    private List<PositionableLabel> backgroundImage = new ArrayList<>();    // background images
212    private List<PositionableLabel> labelImage = new ArrayList<>();         // positionable label images
213    private List<SensorIcon> sensorImage = new ArrayList<>();               // sensor images
214    private List<TurnoutIcon> turnoutImage = new ArrayList<>();             // turnout _images_
215    private List<SignalHeadIcon> signalHeadImage = new ArrayList<>();       // signal head images
216
217    // PositionableLabel's
218    private List<BlockContentsIcon> blockContentsLabelList = new ArrayList<>(); // BlockContents Label List
219    private List<BlockContentsInputIcon> blockContentsInputList = new ArrayList<>(); // BlockContents Input List
220    private List<MemoryIcon> memoryLabelList = new ArrayList<>();               // Memory Label List
221    private List<MemoryInputIcon> memoryInputList = new ArrayList<>();          // Memory Input List
222    private List<GlobalVariableIcon> globalVariableLabelList = new ArrayList<>(); // LogixNG Global Variable Label List
223    private List<SensorIcon> sensorList = new ArrayList<>();                    // Sensor Icons
224    private List<TurnoutIcon> turnoutList = new ArrayList<>();                  // Turnout _Icons_
225    private List<SignalHeadIcon> signalList = new ArrayList<>();                // Signal Head Icons
226    private List<SignalMastIcon> signalMastList = new ArrayList<>();            // Signal Mast Icons
227
228    // Factory generated positionables
229    private List<Positionable> factoryPositionables = new ArrayList<>();
230
231    private JCheckBoxMenuItem disableLocoMarkerPopupMenuItem;
232
233    public final LayoutEditorViewContext gContext = new LayoutEditorViewContext(); // public for now, as things work access changes
234
235    @Nonnull
236    public List<SensorIcon> getSensorList() {
237        return sensorList;
238    }
239
240    @Nonnull
241    public List<TurnoutIcon> getTurnoutList() {
242        return turnoutList;
243    }
244
245    @Nonnull
246    public List<PositionableLabel> getLabelImageList()  {
247        return labelImage;
248    }
249
250    @Nonnull
251    public List<BlockContentsIcon> getBlockContentsLabelList() {
252        return blockContentsLabelList;
253    }
254
255    @Nonnull
256    public List<MemoryIcon> getMemoryLabelList() {
257        return memoryLabelList;
258    }
259
260    @Nonnull
261    public List<MemoryInputIcon> getMemoryInputList() {
262        return memoryInputList;
263    }
264
265    @Nonnull
266    public List<BlockContentsInputIcon> getBlockContensInputList() {
267        return blockContentsInputList;
268    }
269
270    @Nonnull
271    public List<GlobalVariableIcon> getGlobalVariableLabelList() {
272        return globalVariableLabelList;
273    }
274
275    @Nonnull
276    public List<SignalHeadIcon> getSignalList() {
277        return signalList;
278    }
279
280    @Nonnull
281    public List<SignalMastIcon> getSignalMastList() {
282        return signalMastList;
283    }
284
285    private final List<LayoutShape> layoutShapes = new ArrayList<>();               // LayoutShap list
286
287    // counts used to determine unique internal names
288    private int numAnchors = 0;
289    private int numEndBumpers = 0;
290    private int numEdgeConnectors = 0;
291    private int numTrackSegments = 0;
292    private int numLevelXings = 0;
293    private int numLayoutSlips = 0;
294    private int numLayoutTurnouts = 0;
295    private int numLayoutTurntables = 0;
296    private int numLayoutTraversers = 0;
297
298    private LayoutEditorFindItems finder = new LayoutEditorFindItems(this);
299
300    @Nonnull
301    public LayoutEditorFindItems getFinder() {
302        return finder;
303    }
304
305    private Color mainlineTrackColor = Color.DARK_GRAY;
306    private Color sidelineTrackColor = Color.DARK_GRAY;
307    public Color defaultTrackColor = Color.DARK_GRAY;
308    private Color defaultOccupiedTrackColor = Color.red;
309    private Color defaultAlternativeTrackColor = Color.white;
310    private Color defaultTextColor = Color.black;
311
312    private String layoutName = "";
313    private boolean animatingLayout = true;
314    private boolean showHelpBar = true;
315    private boolean drawGrid = true;
316
317    private boolean snapToGridOnAdd = false;
318    private boolean snapToGridOnMove = false;
319    private boolean snapToGridInvert = false;
320
321    private boolean antialiasingOn = false;
322    private boolean drawLayoutTracksLabel = false;
323    private boolean highlightSelectedBlockFlag = false;
324
325    private boolean turnoutCirclesWithoutEditMode = false;
326    private boolean tooltipsWithoutEditMode = false;
327    private boolean tooltipsInEditMode = true;
328    private boolean tooltipsAlwaysOrNever = false;     // When true, don't call setAllShowToolTip().
329
330    // turnout size parameters - saved with panel
331    private double turnoutBX = LayoutTurnout.turnoutBXDefault; // RH, LH, WYE
332    private double turnoutCX = LayoutTurnout.turnoutCXDefault;
333    private double turnoutWid = LayoutTurnout.turnoutWidDefault;
334    private double xOverLong = LayoutTurnout.xOverLongDefault; // DOUBLE_XOVER, RH_XOVER, LH_XOVER
335    private double xOverHWid = LayoutTurnout.xOverHWidDefault;
336    private double xOverShort = LayoutTurnout.xOverShortDefault;
337    private boolean useDirectTurnoutControl = false; // Uses Left click for closing points, Right click for throwing.
338    private boolean highlightCursor = false; // Highlight finger/mouse press/drag area, good for touchscreens
339
340    // saved state of options when panel was loaded or created
341    private boolean savedEditMode = true;
342    private boolean savedPositionable = true;
343    private boolean savedControlLayout = true;
344    private boolean savedAnimatingLayout = true;
345    private boolean savedShowHelpBar = true;
346
347    // zoom
348    private double minZoom = 0.25;
349    private final double maxZoom = 8.0;
350
351    // A hash to store string -> KeyEvent constants, used to set keyboard shortcuts per locale
352    private HashMap<String, Integer> stringsToVTCodes = new HashMap<>();
353
354    /*==============*\
355    |* Toolbar side *|
356    \*==============*/
357    private enum ToolBarSide {
358        eTOP("top"),
359        eLEFT("left"),
360        eBOTTOM("bottom"),
361        eRIGHT("right"),
362        eFLOAT("float");
363
364        private final String name;
365        private static final Map<String, ToolBarSide> ENUM_MAP;
366
367        ToolBarSide(String name) {
368            this.name = name;
369        }
370
371        // Build an immutable map of String name to enum pairs.
372        static {
373            Map<String, ToolBarSide> map = new ConcurrentHashMap<>();
374
375            for (ToolBarSide instance : ToolBarSide.values()) {
376                map.put(instance.getName(), instance);
377            }
378            ENUM_MAP = Collections.unmodifiableMap(map);
379        }
380
381        public static ToolBarSide getName(@CheckForNull String name) {
382            return ENUM_MAP.get(name);
383        }
384
385        public String getName() {
386            return name;
387        }
388    }
389
390    private ToolBarSide toolBarSide = ToolBarSide.eTOP;
391
392    public LayoutEditor() {
393        this("My Layout");
394    }
395
396    public LayoutEditor(@Nonnull String name) {
397        super(name);
398        setSaveSize(true);
399        layoutName = name;
400
401        editorUseOldLocSize = InstanceManager.getDefault(jmri.util.gui.GuiLafPreferencesManager.class).isEditorUseOldLocSize();
402
403        // initialise keycode map
404        initStringsToVTCodes();
405
406        setupToolBar();
407        setupMenuBar();
408
409        super.setDefaultToolTip(new ToolTip(null, 0, 0, new Font("SansSerif", Font.PLAIN, 12),
410                Color.black, new Color(215, 225, 255), Color.black, null));
411
412        // setup help bar
413        helpBar.setLayout(new BoxLayout(helpBar, BoxLayout.PAGE_AXIS));
414        JTextArea helpTextArea1 = new JTextArea(Bundle.getMessage("Help1"));
415        helpBar.add(helpTextArea1);
416        JTextArea helpTextArea2 = new JTextArea(Bundle.getMessage("Help2"));
417        helpBar.add(helpTextArea2);
418
419        String helpText3 = "";
420
421        switch (SystemType.getType()) {
422            case SystemType.MACOSX: {
423                helpText3 = Bundle.getMessage("Help3Mac");
424                break;
425            }
426
427            case SystemType.WINDOWS:
428            case SystemType.LINUX: {
429                helpText3 = Bundle.getMessage("Help3Win");
430                break;
431            }
432
433            default:
434                helpText3 = Bundle.getMessage("Help3");
435        }
436
437        JTextArea helpTextArea3 = new JTextArea(helpText3);
438        helpBar.add(helpTextArea3);
439
440        // set to full screen
441        Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
442        gContext.setWindowWidth(screenDim.width - 20);
443        gContext.setWindowHeight(screenDim.height - 120);
444
445        // Let Editor make target, and use this frame
446        super.setTargetPanel(null, null);
447        super.setTargetPanelSize(gContext.getWindowWidth(), gContext.getWindowHeight());
448        setSize(screenDim.width, screenDim.height);
449
450        // register the resulting panel for later configuration
451        InstanceManager.getOptionalDefault(ConfigureManager.class)
452                .ifPresent(cm -> cm.registerUser(this));
453
454        // confirm that panel hasn't already been loaded
455        if (!this.equals(InstanceManager.getDefault(EditorManager.class).get(name))) {
456            log.warn("File contains a panel with the same name ({}) as an existing panel", name);
457        }
458        setFocusable(true);
459        addKeyListener(this);
460        resetDirty();
461
462        // establish link to LayoutEditor Tools
463        auxTools = getLEAuxTools();
464
465        SwingUtilities.invokeLater(() -> {
466            // initialize preferences
467            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
468                String windowFrameRef = getWindowFrameRef();
469
470                Object prefsProp = prefsMgr.getProperty(windowFrameRef, "toolBarSide");
471                // log.debug("{}.toolBarSide is {}", windowFrameRef, prefsProp);
472                if (prefsProp != null) {
473                    ToolBarSide newToolBarSide = ToolBarSide.getName((String) prefsProp);
474                    setToolBarSide(newToolBarSide);
475                }
476
477                // Note: since prefs default to false and we want wide to be the default
478                // we invert it and save it as thin
479                boolean prefsToolBarIsWide = prefsMgr.getSimplePreferenceState(windowFrameRef + ".toolBarThin");
480
481                log.debug("{}.toolBarThin is {}", windowFrameRef, prefsProp);
482                setToolBarWide(prefsToolBarIsWide);
483
484                boolean prefsShowHelpBar = prefsMgr.getSimplePreferenceState(windowFrameRef + ".showHelpBar");
485                // log.debug("{}.showHelpBar is {}", windowFrameRef, prefsShowHelpBar);
486
487                setShowHelpBar(prefsShowHelpBar);
488
489                boolean prefsAntialiasingOn = prefsMgr.getSimplePreferenceState(windowFrameRef + ".antialiasingOn");
490                // log.debug("{}.antialiasingOn is {}", windowFrameRef, prefsAntialiasingOn);
491
492                setAntialiasingOn(prefsAntialiasingOn);
493
494                boolean prefsDrawLayoutTracksLabel = prefsMgr.getSimplePreferenceState(windowFrameRef + ".drawLayoutTracksLabel");
495                // log.debug("{}.drawLayoutTracksLabel is {}", windowFrameRef, prefsDrawLayoutTracksLabel);
496                setDrawLayoutTracksLabel(prefsDrawLayoutTracksLabel);
497
498                boolean prefsHighlightSelectedBlockFlag
499                        = prefsMgr.getSimplePreferenceState(windowFrameRef + ".highlightSelectedBlock");
500                // log.debug("{}.highlightSelectedBlock is {}", windowFrameRef, prefsHighlightSelectedBlockFlag);
501
502                setHighlightSelectedBlock(prefsHighlightSelectedBlockFlag);
503            }); // InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr)
504
505            // make sure that the layoutEditorComponent is in the _targetPanel components
506            List<Component> componentList = Arrays.asList(_targetPanel.getComponents());
507            if (!componentList.contains(layoutEditorComponent)) {
508                try {
509                    _targetPanel.remove(layoutEditorComponent);
510                    // Note that Integer.valueOf(3) must not be replaced with 3 in the line below.
511                    // add(c, Integer.valueOf(3)) means adding at depth 3 in the JLayeredPane, while add(c, 3) means adding at index 3 in the container.
512                    _targetPanel.add(layoutEditorComponent, Integer.valueOf(3));
513                    _targetPanel.moveToFront(layoutEditorComponent);
514                } catch (Exception e) {
515                    log.warn("paintTargetPanelBefore: ", e);
516                }
517            }
518        });
519    }
520
521    @SuppressWarnings("deprecation")  // getMenuShortcutKeyMask()
522    private void setupMenuBar() {
523        // initialize menu bar
524        JMenuBar menuBar = new JMenuBar();
525
526        // set up File menu
527        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
528        fileMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuFileMnemonic")));
529        menuBar.add(fileMenu);
530        StoreXmlUserAction store = new StoreXmlUserAction(Bundle.getMessage("FileMenuItemStore"));
531        int primary_modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
532        store.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
533                stringsToVTCodes.get(Bundle.getMessage("MenuItemStoreAccelerator")), primary_modifier));
534        fileMenu.add(store);
535        fileMenu.addSeparator();
536
537        JMenuItem deleteItem = new JMenuItem(Bundle.getMessage("DeletePanel"));
538        fileMenu.add(deleteItem);
539        deleteItem.addActionListener((ActionEvent event) -> {
540            if (deletePanel()) {
541                dispose();
542            }
543        });
544        setJMenuBar(menuBar);
545
546        // setup Options menu
547        setupOptionMenu(menuBar);
548
549        // setup Tools menu
550        setupToolsMenu(menuBar);
551
552        // setup Zoom menu
553        setupZoomMenu(menuBar);
554
555        // setup marker menu
556        setupMarkerMenu(menuBar);
557
558        // Setup Dispatcher window
559        setupDispatcherMenu(menuBar);
560
561        // setup Help menu
562        addHelpMenu("package.jmri.jmrit.display.LayoutEditor", true);
563    }
564
565    @Override
566    public void newPanelDefaults() {
567        getLayoutTrackDrawingOptions().setMainRailWidth(2);
568        getLayoutTrackDrawingOptions().setSideRailWidth(1);
569        setBackgroundColor(defaultBackgroundColor);
570        JmriColorChooser.addRecentColor(defaultTrackColor);
571        JmriColorChooser.addRecentColor(defaultOccupiedTrackColor);
572        JmriColorChooser.addRecentColor(defaultAlternativeTrackColor);
573        JmriColorChooser.addRecentColor(defaultBackgroundColor);
574        JmriColorChooser.addRecentColor(defaultTextColor);
575    }
576
577    private final LayoutEditorComponent layoutEditorComponent = new LayoutEditorComponent(this);
578
579    private void setupToolBar() {
580        // Initial setup for both horizontal and vertical
581        Container contentPane = getContentPane();
582
583        // remove these (if present) so we can add them back (without duplicates)
584        if (editToolBarContainerPanel != null) {
585            editToolBarContainerPanel.setVisible(false);
586            contentPane.remove(editToolBarContainerPanel);
587        }
588
589        if (helpBarPanel != null) {
590            contentPane.remove(helpBarPanel);
591        }
592
593        deletefloatingEditToolBoxFrame();
594        if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
595            createfloatingEditToolBoxFrame();
596            createFloatingHelpPanel();
597            return;
598        }
599
600        Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
601        boolean toolBarIsVertical = (toolBarSide.equals(ToolBarSide.eRIGHT) || toolBarSide.equals(ToolBarSide.eLEFT));
602        if ( leToolBarPanel != null ) {
603            leToolBarPanel.dispose();
604        }
605        if (toolBarIsVertical) {
606            leToolBarPanel = new LayoutEditorVerticalToolBarPanel(this);
607            leToolBarPanel.setPreferredSize(leToolBarPanel.getMinimumSize());
608            editToolBarScrollPane = new JScrollPane(leToolBarPanel);
609            toolbarWidth = editToolBarScrollPane.getPreferredSize().width;
610            toolbarHeight = screenDim.height;
611        } else {
612            leToolBarPanel = new LayoutEditorHorizontalToolBarPanel(this);
613            leToolBarPanel.revalidate();
614            leToolBarPanel.setPreferredSize(leToolBarPanel.getMinimumSize());
615            editToolBarScrollPane = new JScrollPane(leToolBarPanel);
616            editToolBarScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
617            editToolBarScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
618            editToolBarScrollPane.revalidate();
619            toolbarWidth = screenDim.width;
620            toolbarHeight = editToolBarScrollPane.getPreferredSize().height;
621        }
622
623        editToolBarContainerPanel = new JPanel();
624        editToolBarContainerPanel.setLayout(new BoxLayout(editToolBarContainerPanel, BoxLayout.PAGE_AXIS));
625        editToolBarContainerPanel.add(editToolBarScrollPane);
626
627        // setup notification for when horizontal scrollbar changes visibility
628        // editToolBarScroll.getViewport().addChangeListener(e -> {
629        // log.warn("scrollbars visible: " + editToolBarScroll.getHorizontalScrollBar().isVisible());
630        //});
631
632        editToolBarContainerPanel.setMinimumSize(new Dimension(toolbarWidth, toolbarHeight));
633        editToolBarContainerPanel.setPreferredSize(new Dimension(toolbarWidth, toolbarHeight));
634
635        helpBarPanel = new JPanel();
636        helpBarPanel.add(helpBar);
637
638        for (Component c : helpBar.getComponents()) {
639            if (c instanceof JTextArea) {
640                JTextArea j = (JTextArea) c;
641                j.setSize(new Dimension(toolbarWidth, j.getSize().height));
642                j.setLineWrap(toolBarIsVertical);
643                j.setWrapStyleWord(toolBarIsVertical);
644            }
645        }
646        contentPane.setLayout(new BoxLayout(contentPane, toolBarIsVertical ? BoxLayout.LINE_AXIS : BoxLayout.PAGE_AXIS));
647
648        switch (toolBarSide) {
649            case eTOP:
650            case eLEFT:
651                contentPane.add(editToolBarContainerPanel, 0);
652                break;
653
654            case eBOTTOM:
655            case eRIGHT:
656                contentPane.add(editToolBarContainerPanel);
657                break;
658
659            default:
660                // fall through
661                break;
662        }
663
664        if (toolBarIsVertical) {
665            editToolBarContainerPanel.add(helpBarPanel);
666        } else {
667            contentPane.add(helpBarPanel);
668        }
669        helpBarPanel.setVisible(isEditable() && getShowHelpBar());
670        editToolBarContainerPanel.setVisible(isEditable());
671    }
672
673    private void createfloatingEditToolBoxFrame() {
674        if (isEditable() && floatingEditToolBoxFrame == null) {
675            // Create a scroll pane to hold the window content.
676            leToolBarPanel = new LayoutEditorFloatingToolBarPanel(this);
677            floatingEditContentScrollPane = new JScrollPane(leToolBarPanel);
678            floatingEditContentScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
679            floatingEditContentScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
680            // Create the window and add the toolbox content
681            floatingEditToolBoxFrame = new JmriJFrame(Bundle.getMessage("ToolBox", getLayoutName()));
682            floatingEditToolBoxFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
683            floatingEditToolBoxFrame.setContentPane(floatingEditContentScrollPane);
684            floatingEditToolBoxFrame.pack();
685            floatingEditToolBoxFrame.setAlwaysOnTop(true);
686            floatingEditToolBoxFrame.setVisible(true);
687        }
688    }
689
690    private void deletefloatingEditToolBoxFrame() {
691        if (floatingEditContentScrollPane != null) {
692            floatingEditContentScrollPane.removeAll();
693            floatingEditContentScrollPane = null;
694        }
695        if (floatingEditToolBoxFrame != null) {
696            floatingEditToolBoxFrame.dispose();
697            floatingEditToolBoxFrame = null;
698        }
699    }
700
701    private void createFloatingHelpPanel() {
702
703        if (leToolBarPanel instanceof LayoutEditorFloatingToolBarPanel) {
704            LayoutEditorFloatingToolBarPanel leftbp = (LayoutEditorFloatingToolBarPanel) leToolBarPanel;
705            floatEditHelpPanel = new JPanel();
706            leToolBarPanel.add(floatEditHelpPanel);
707
708            // Notice: End tree structure indenting
709            // Force the help panel width to the same as the tabs section
710            int tabSectionWidth = (int) leftbp.getPreferredSize().getWidth();
711
712            // Change the textarea settings
713            for (Component c : helpBar.getComponents()) {
714                if (c instanceof JTextArea) {
715                    JTextArea j = (JTextArea) c;
716                    j.setSize(new Dimension(tabSectionWidth, j.getSize().height));
717                    j.setLineWrap(true);
718                    j.setWrapStyleWord(true);
719                }
720            }
721
722            // Change the width of the help panel section
723            floatEditHelpPanel.setMaximumSize(new Dimension(tabSectionWidth, Integer.MAX_VALUE));
724            floatEditHelpPanel.add(helpBar);
725            floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
726        }
727    }
728
729    @Override
730    public void init(String name) {
731    }
732
733    @Override
734    public void initView() {
735        editModeCheckBoxMenuItem.setSelected(isEditable());
736
737        positionableCheckBoxMenuItem.setSelected(allPositionable());
738        controlCheckBoxMenuItem.setSelected(allControlling());
739
740        if (isEditable()) {
741            if (!tooltipsAlwaysOrNever) {
742                setAllShowToolTip(tooltipsInEditMode);
743                setAllShowLayoutTurnoutToolTip(tooltipsInEditMode);
744            }
745        } else {
746            if (!tooltipsAlwaysOrNever) {
747                setAllShowToolTip(tooltipsWithoutEditMode);
748                setAllShowLayoutTurnoutToolTip(tooltipsWithoutEditMode);
749            }
750        }
751
752        scrollNoneMenuItem.setSelected(_scrollState == Editor.SCROLL_NONE);
753        scrollBothMenuItem.setSelected(_scrollState == Editor.SCROLL_BOTH);
754        scrollHorizontalMenuItem.setSelected(_scrollState == Editor.SCROLL_HORIZONTAL);
755        scrollVerticalMenuItem.setSelected(_scrollState == Editor.SCROLL_VERTICAL);
756    }
757
758    @Override
759    public void setSize(int w, int h) {
760        super.setSize(w, h);
761    }
762
763    @Override
764    public void targetWindowClosingEvent(WindowEvent e) {
765        boolean save = (isDirty() || (savedEditMode != isEditable())
766                || (savedPositionable != allPositionable())
767                || (savedControlLayout != allControlling())
768                || (savedAnimatingLayout != isAnimating())
769                || (savedShowHelpBar != getShowHelpBar()));
770
771        log.trace("Temp fix to disable CI errors: save = {}", save);
772        targetWindowClosing();
773    }
774
775    /**
776     * Set up NamedBeanComboBox
777     *
778     * @param nbComboBox     the NamedBeanComboBox to set up
779     * @param inValidateMode true to validate typed inputs; false otherwise
780     * @param inEnable       boolean to enable / disable the NamedBeanComboBox
781     * @param inEditable     boolean to make the NamedBeanComboBox editable
782     */
783    public static void setupComboBox(@Nonnull NamedBeanComboBox<?> nbComboBox,
784        boolean inValidateMode, boolean inEnable, boolean inEditable) {
785        log.debug("LE setupComboBox called");
786        NamedBeanComboBox<?> inComboBox = Objects.requireNonNull(nbComboBox);
787
788        inComboBox.setEnabled(inEnable);
789        inComboBox.setEditable(inEditable);
790        inComboBox.setValidatingInput(inValidateMode);
791        inComboBox.setSelectedIndex(-1);
792
793        // This has to be set before calling setupComboBoxMaxRows
794        // (otherwise if inFirstBlank then the  number of rows will be wrong)
795        inComboBox.setAllowNull(!inValidateMode);
796
797        // set the max number of rows that will fit onscreen
798        JComboBoxUtil.setupComboBoxMaxRows(inComboBox);
799
800        inComboBox.setSelectedIndex(-1);
801    }
802
803    /**
804     * Grabs a subset of the possible KeyEvent constants and puts them into a
805     * hash for fast lookups later. These lookups are used to enable bundles to
806     * specify keyboard shortcuts on a per-locale basis.
807     */
808    private void initStringsToVTCodes() {
809        Field[] fields = KeyEvent.class
810                .getFields();
811
812        for (Field field : fields) {
813            String name = field.getName();
814
815            if (name.startsWith("VK")) {
816                int code = 0;
817                try {
818                    code = field.getInt(null);
819                } catch (IllegalAccessException | IllegalArgumentException e) {
820                    // exceptions make me throw up...
821                }
822
823                String key = name.substring(3);
824
825                // log.debug("VTCode[{}]:'{}'", key, code);
826                stringsToVTCodes.put(key, code);
827            }
828        }
829    }
830
831    /**
832     * The Java run times for 11 and 12 running on macOS have a bug that causes double events for
833     * JCheckBoxMenuItem when invoked by an accelerator key combination.
834     * <p>
835     * The java.version property is parsed to determine the run time version.  If the event occurs
836     * on macOS and Java 11 or 12 and a modifier key was active, true is returned.  The five affected
837     * action events will drop the event and process the second occurrence.
838     * @aparam event The action event.
839     * @return true if the event is affected, otherwise return false.
840     */
841    private boolean fixMacBugOn11(ActionEvent event) {
842        boolean result = false;
843        if (SystemType.isMacOSX()) {
844            if (event.getModifiers() != 0) {
845                // MacOSX and modifier key, test Java version
846                String version = System.getProperty("java.version");
847                if (version.startsWith("1.")) {
848                    version = version.substring(2, 3);
849                } else {
850                    int dot = version.indexOf(".");
851                    if (dot != -1) {
852                        version = version.substring(0, dot);
853                    }
854                }
855                int vers = Integer.parseInt(version);
856                result = (vers == 11 || vers == 12);
857            }
858        }
859        return result;
860     }
861
862    /**
863     * Set up the Option menu.
864     *
865     * @param menuBar to add the option menu to
866     * @return option menu that was added
867     */
868    @SuppressWarnings("deprecation")  // getMenuShortcutKeyMask()
869    private JMenu setupOptionMenu(@Nonnull JMenuBar menuBar) {
870        assert menuBar != null;
871
872        JMenu optionMenu = new JMenu(Bundle.getMessage("MenuOptions"));
873
874        optionMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("OptionsMnemonic")));
875        menuBar.add(optionMenu);
876
877        //
878        //  edit mode
879        //
880        editModeCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("EditMode"));
881        optionMenu.add(editModeCheckBoxMenuItem);
882        editModeCheckBoxMenuItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("EditModeMnemonic")));
883        int primary_modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
884        editModeCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(
885                stringsToVTCodes.get(Bundle.getMessage("EditModeAccelerator")), primary_modifier));
886        editModeCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
887
888            if (fixMacBugOn11(event)) {
889                editModeCheckBoxMenuItem.setSelected(!editModeCheckBoxMenuItem.isSelected());
890                return;
891            }
892
893            setAllEditable(editModeCheckBoxMenuItem.isSelected());
894
895            // show/hide the help bar
896            if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
897                if (floatEditHelpPanel != null) {
898                    floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
899                }
900            } else {
901                helpBarPanel.setVisible(isEditable() && getShowHelpBar());
902            }
903
904            if (isEditable()) {
905                if (!tooltipsAlwaysOrNever) {
906                    setAllShowToolTip(tooltipsInEditMode);
907                    setAllShowLayoutTurnoutToolTip(tooltipsInEditMode);
908                }
909
910                // redo using the "Extra" color to highlight the selected block
911                if (highlightSelectedBlockFlag) {
912                    if (!highlightBlockInComboBox(leToolBarPanel.blockIDComboBox)) {
913                        highlightBlockInComboBox(leToolBarPanel.blockContentsComboBox);
914                    }
915                }
916            } else {
917                if (!tooltipsAlwaysOrNever) {
918                    setAllShowToolTip(tooltipsWithoutEditMode);
919                    setAllShowLayoutTurnoutToolTip(tooltipsWithoutEditMode);
920                }
921
922                // undo using the "Extra" color to highlight the selected block
923                if (highlightSelectedBlockFlag) {
924                    highlightBlock(null);
925                }
926            }
927            awaitingIconChange = false;
928        });
929        editModeCheckBoxMenuItem.setSelected(isEditable());
930
931        //
932        // toolbar
933        //
934        JMenu toolBarMenu = new JMenu(Bundle.getMessage("ToolBar")); // used for ToolBar SubMenu
935        optionMenu.add(toolBarMenu);
936
937        JMenu toolBarSideMenu = new JMenu(Bundle.getMessage("ToolBarSide"));
938        ButtonGroup toolBarSideGroup = new ButtonGroup();
939
940        //
941        // create toolbar side menu items: (top, left, bottom, right)
942        //
943        toolBarSideTopButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideTop"));
944        toolBarSideTopButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eTOP));
945        toolBarSideTopButton.setSelected(toolBarSide.equals(ToolBarSide.eTOP));
946        toolBarSideMenu.add(toolBarSideTopButton);
947        toolBarSideGroup.add(toolBarSideTopButton);
948
949        toolBarSideLeftButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideLeft"));
950        toolBarSideLeftButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eLEFT));
951        toolBarSideLeftButton.setSelected(toolBarSide.equals(ToolBarSide.eLEFT));
952        toolBarSideMenu.add(toolBarSideLeftButton);
953        toolBarSideGroup.add(toolBarSideLeftButton);
954
955        toolBarSideBottomButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideBottom"));
956        toolBarSideBottomButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eBOTTOM));
957        toolBarSideBottomButton.setSelected(toolBarSide.equals(ToolBarSide.eBOTTOM));
958        toolBarSideMenu.add(toolBarSideBottomButton);
959        toolBarSideGroup.add(toolBarSideBottomButton);
960
961        toolBarSideRightButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideRight"));
962        toolBarSideRightButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eRIGHT));
963        toolBarSideRightButton.setSelected(toolBarSide.equals(ToolBarSide.eRIGHT));
964        toolBarSideMenu.add(toolBarSideRightButton);
965        toolBarSideGroup.add(toolBarSideRightButton);
966
967        toolBarSideFloatButton = new JRadioButtonMenuItem(Bundle.getMessage("ToolBarSideFloat"));
968        toolBarSideFloatButton.addActionListener((ActionEvent event) -> setToolBarSide(ToolBarSide.eFLOAT));
969        toolBarSideFloatButton.setSelected(toolBarSide.equals(ToolBarSide.eFLOAT));
970        toolBarSideMenu.add(toolBarSideFloatButton);
971        toolBarSideGroup.add(toolBarSideFloatButton);
972
973        toolBarMenu.add(toolBarSideMenu);
974
975        //
976        // toolbar wide menu
977        //
978        toolBarMenu.add(wideToolBarCheckBoxMenuItem);
979        wideToolBarCheckBoxMenuItem.addActionListener((ActionEvent event) -> setToolBarWide(wideToolBarCheckBoxMenuItem.isSelected()));
980        wideToolBarCheckBoxMenuItem.setSelected(leToolBarPanel.toolBarIsWide);
981        wideToolBarCheckBoxMenuItem.setEnabled(toolBarSide.equals(ToolBarSide.eTOP) || toolBarSide.equals(ToolBarSide.eBOTTOM));
982
983        //
984        // Scroll Bars
985        //
986        scrollMenu = new JMenu(Bundle.getMessage("ComboBoxScrollable")); // used for ScrollBarsSubMenu
987        optionMenu.add(scrollMenu);
988        ButtonGroup scrollGroup = new ButtonGroup();
989        scrollBothMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollBoth"));
990        scrollGroup.add(scrollBothMenuItem);
991        scrollMenu.add(scrollBothMenuItem);
992        scrollBothMenuItem.setSelected(_scrollState == Editor.SCROLL_BOTH);
993        scrollBothMenuItem.addActionListener((ActionEvent event) -> {
994            _scrollState = Editor.SCROLL_BOTH;
995            setScroll(_scrollState);
996            redrawPanel();
997        });
998        scrollNoneMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollNone"));
999        scrollGroup.add(scrollNoneMenuItem);
1000        scrollMenu.add(scrollNoneMenuItem);
1001        scrollNoneMenuItem.setSelected(_scrollState == Editor.SCROLL_NONE);
1002        scrollNoneMenuItem.addActionListener((ActionEvent event) -> {
1003            _scrollState = Editor.SCROLL_NONE;
1004            setScroll(_scrollState);
1005            redrawPanel();
1006        });
1007        scrollHorizontalMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollHorizontal"));
1008        scrollGroup.add(scrollHorizontalMenuItem);
1009        scrollMenu.add(scrollHorizontalMenuItem);
1010        scrollHorizontalMenuItem.setSelected(_scrollState == Editor.SCROLL_HORIZONTAL);
1011        scrollHorizontalMenuItem.addActionListener((ActionEvent event) -> {
1012            _scrollState = Editor.SCROLL_HORIZONTAL;
1013            setScroll(_scrollState);
1014            redrawPanel();
1015        });
1016        scrollVerticalMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("ScrollVertical"));
1017        scrollGroup.add(scrollVerticalMenuItem);
1018        scrollMenu.add(scrollVerticalMenuItem);
1019        scrollVerticalMenuItem.setSelected(_scrollState == Editor.SCROLL_VERTICAL);
1020        scrollVerticalMenuItem.addActionListener((ActionEvent event) -> {
1021            _scrollState = Editor.SCROLL_VERTICAL;
1022            setScroll(_scrollState);
1023            redrawPanel();
1024        });
1025
1026        //
1027        // Tooltips
1028        //
1029        tooltipMenu = new JMenu(Bundle.getMessage("TooltipSubMenu"));
1030        optionMenu.add(tooltipMenu);
1031        ButtonGroup tooltipGroup = new ButtonGroup();
1032        tooltipNoneMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipNone"));
1033        tooltipGroup.add(tooltipNoneMenuItem);
1034        tooltipMenu.add(tooltipNoneMenuItem);
1035        tooltipNoneMenuItem.setSelected((!tooltipsInEditMode) && (!tooltipsWithoutEditMode));
1036        tooltipNoneMenuItem.addActionListener((ActionEvent event) -> {
1037            tooltipsInEditMode = false;
1038            tooltipsWithoutEditMode = false;
1039            tooltipsAlwaysOrNever = true;
1040            setAllShowToolTip(false);
1041            setAllShowLayoutTurnoutToolTip(false);
1042        });
1043        tooltipAlwaysMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipAlways"));
1044        tooltipGroup.add(tooltipAlwaysMenuItem);
1045        tooltipMenu.add(tooltipAlwaysMenuItem);
1046        tooltipAlwaysMenuItem.setSelected((tooltipsInEditMode) && (tooltipsWithoutEditMode));
1047        tooltipAlwaysMenuItem.addActionListener((ActionEvent event) -> {
1048            tooltipsInEditMode = true;
1049            tooltipsWithoutEditMode = true;
1050            tooltipsAlwaysOrNever = true;
1051            setAllShowToolTip(true);
1052            setAllShowLayoutTurnoutToolTip(true);
1053        });
1054        tooltipInEditMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipEdit"));
1055        tooltipGroup.add(tooltipInEditMenuItem);
1056        tooltipMenu.add(tooltipInEditMenuItem);
1057        tooltipInEditMenuItem.setSelected((tooltipsInEditMode) && (!tooltipsWithoutEditMode));
1058        tooltipInEditMenuItem.addActionListener((ActionEvent event) -> {
1059            tooltipsInEditMode = true;
1060            tooltipsWithoutEditMode = false;
1061            tooltipsAlwaysOrNever = false;
1062            setAllShowToolTip(isEditable());
1063            setAllShowLayoutTurnoutToolTip(isEditable());
1064        });
1065        tooltipNotInEditMenuItem = new JRadioButtonMenuItem(Bundle.getMessage("TooltipNotEdit"));
1066        tooltipGroup.add(tooltipNotInEditMenuItem);
1067        tooltipMenu.add(tooltipNotInEditMenuItem);
1068        tooltipNotInEditMenuItem.setSelected((!tooltipsInEditMode) && (tooltipsWithoutEditMode));
1069        tooltipNotInEditMenuItem.addActionListener((ActionEvent event) -> {
1070            tooltipsInEditMode = false;
1071            tooltipsWithoutEditMode = true;
1072            tooltipsAlwaysOrNever = false;
1073            setAllShowToolTip(!isEditable());
1074            setAllShowLayoutTurnoutToolTip(!isEditable());
1075        });
1076
1077        //
1078        // show edit help
1079        //
1080        showHelpCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("ShowEditHelp"));
1081        optionMenu.add(showHelpCheckBoxMenuItem);
1082        showHelpCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1083            boolean newShowHelpBar = showHelpCheckBoxMenuItem.isSelected();
1084            setShowHelpBar(newShowHelpBar);
1085        });
1086        showHelpCheckBoxMenuItem.setSelected(getShowHelpBar());
1087
1088        //
1089        // Allow Repositioning
1090        //
1091        positionableCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AllowRepositioning"));
1092        optionMenu.add(positionableCheckBoxMenuItem);
1093        positionableCheckBoxMenuItem.addActionListener((ActionEvent event) -> setAllPositionable(positionableCheckBoxMenuItem.isSelected()));
1094        positionableCheckBoxMenuItem.setSelected(allPositionable());
1095
1096        //
1097        // Allow Layout Control
1098        //
1099        controlCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AllowLayoutControl"));
1100        optionMenu.add(controlCheckBoxMenuItem);
1101        controlCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1102            setAllControlling(controlCheckBoxMenuItem.isSelected());
1103            redrawPanel();
1104        });
1105        controlCheckBoxMenuItem.setSelected(allControlling());
1106
1107        //
1108        // use direct turnout control
1109        //
1110        useDirectTurnoutControlCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("UseDirectTurnoutControl")); // NOI18N
1111        optionMenu.add(useDirectTurnoutControlCheckBoxMenuItem);
1112        useDirectTurnoutControlCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1113            setDirectTurnoutControl(useDirectTurnoutControlCheckBoxMenuItem.isSelected());
1114        });
1115        useDirectTurnoutControlCheckBoxMenuItem.setSelected(useDirectTurnoutControl);
1116
1117        //
1118        // antialiasing
1119        //
1120        antialiasingOnCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AntialiasingOn"));
1121        optionMenu.add(antialiasingOnCheckBoxMenuItem);
1122        antialiasingOnCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1123            setAntialiasingOn(antialiasingOnCheckBoxMenuItem.isSelected());
1124            redrawPanel();
1125        });
1126        antialiasingOnCheckBoxMenuItem.setSelected(antialiasingOn);
1127
1128        //
1129        // drawLayoutTracksLabel
1130        //
1131        drawLayoutTracksLabelCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("DrawLayoutTracksLabel"));
1132        optionMenu.add(drawLayoutTracksLabelCheckBoxMenuItem);
1133        drawLayoutTracksLabelCheckBoxMenuItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("DrawLayoutTracksMnemonic")));
1134        drawLayoutTracksLabelCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(
1135                stringsToVTCodes.get(Bundle.getMessage("DrawLayoutTracksAccelerator")), primary_modifier));
1136        drawLayoutTracksLabelCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1137
1138            if (fixMacBugOn11(event)) {
1139                drawLayoutTracksLabelCheckBoxMenuItem.setSelected(!drawLayoutTracksLabelCheckBoxMenuItem.isSelected());
1140                return;
1141            }
1142
1143            setDrawLayoutTracksLabel(drawLayoutTracksLabelCheckBoxMenuItem.isSelected());
1144            redrawPanel();
1145        });
1146        drawLayoutTracksLabelCheckBoxMenuItem.setSelected(drawLayoutTracksLabel);
1147
1148        // add "Highlight cursor position" - useful for touchscreens
1149        highlightCursorCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("HighlightCursor"));
1150        optionMenu.add(highlightCursorCheckBoxMenuItem);
1151        highlightCursorCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1152            highlightCursor = highlightCursorCheckBoxMenuItem.isSelected();
1153            redrawPanel();
1154        });
1155        highlightCursorCheckBoxMenuItem.setSelected(highlightCursor);
1156
1157        //
1158        // edit title
1159        //
1160        optionMenu.addSeparator();
1161        JMenuItem titleItem = new JMenuItem(Bundle.getMessage("EditTitle") + "...");
1162        optionMenu.add(titleItem);
1163        titleItem.addActionListener((ActionEvent event) -> {
1164            // prompt for name
1165            String newName = (String) JmriJOptionPane.showInputDialog(getTargetFrame(),
1166                    Bundle.getMessage("MakeLabel", Bundle.getMessage("EnterTitle")),
1167                    Bundle.getMessage("EditTitleMessageTitle"),
1168                    JmriJOptionPane.PLAIN_MESSAGE, null, null, getLayoutName());
1169
1170            if (newName != null) {
1171                if (!newName.equals(getLayoutName())) {
1172                    if (InstanceManager.getDefault(EditorManager.class).contains(newName)) {
1173                        JmriJOptionPane.showMessageDialog(null,
1174                            Bundle.getMessage("CanNotRename", Bundle.getMessage("Panel")),
1175                            Bundle.getMessage("AlreadyExist", Bundle.getMessage("Panel")),
1176                            JmriJOptionPane.ERROR_MESSAGE);
1177                    } else {
1178                        setTitle(newName);
1179                        setLayoutName(newName);
1180                        getLayoutTrackDrawingOptions().setName(newName);
1181                        setDirty();
1182
1183                        if (toolBarSide.equals(ToolBarSide.eFLOAT) && isEditable()) {
1184                            // Rebuild the toolbox after a name change.
1185                            deletefloatingEditToolBoxFrame();
1186                            createfloatingEditToolBoxFrame();
1187                            createFloatingHelpPanel();
1188                        }
1189                    }
1190                }
1191            }
1192        });
1193
1194        //
1195        // set background color
1196        //
1197        JMenuItem backgroundColorMenuItem = new JMenuItem(Bundle.getMessage("SetBackgroundColor", "..."));
1198        optionMenu.add(backgroundColorMenuItem);
1199        backgroundColorMenuItem.addActionListener((ActionEvent event) -> {
1200            Color desiredColor = JmriColorChooser.showDialog(this,
1201                    Bundle.getMessage("SetBackgroundColor", ""),
1202                    defaultBackgroundColor);
1203            if (desiredColor != null && !defaultBackgroundColor.equals(desiredColor)) {
1204                defaultBackgroundColor = desiredColor;
1205                setBackgroundColor(desiredColor);
1206                setDirty();
1207                redrawPanel();
1208            }
1209        });
1210
1211        //
1212        // set default text color
1213        //
1214        JMenuItem textColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultTextColor", "..."));
1215        optionMenu.add(textColorMenuItem);
1216        textColorMenuItem.addActionListener((ActionEvent event) -> {
1217            Color desiredColor = JmriColorChooser.showDialog(this,
1218                    Bundle.getMessage("DefaultTextColor", ""),
1219                    defaultTextColor);
1220            if (desiredColor != null && !defaultTextColor.equals(desiredColor)) {
1221                setDefaultTextColor(desiredColor);
1222                setDirty();
1223                redrawPanel();
1224            }
1225        });
1226
1227        if (editorUseOldLocSize) {
1228            //
1229            //  save location and size
1230            //
1231            JMenuItem locationItem = new JMenuItem(Bundle.getMessage("SetLocation"));
1232            optionMenu.add(locationItem);
1233            locationItem.addActionListener((ActionEvent event) -> {
1234                setCurrentPositionAndSize();
1235                log.debug("Bounds:{}, {}, {}, {}, {}, {}",
1236                        gContext.getUpperLeftX(), gContext.getUpperLeftY(),
1237                        gContext.getWindowWidth(), gContext.getWindowHeight(),
1238                        gContext.getLayoutWidth(), gContext.getLayoutHeight());
1239            });
1240        }
1241
1242        //
1243        // Add Options
1244        //
1245        JMenu optionsAddMenu = new JMenu(Bundle.getMessage("AddMenuTitle"));
1246        optionMenu.add(optionsAddMenu);
1247
1248        // add background image
1249        JMenuItem backgroundItem = new JMenuItem(Bundle.getMessage("AddBackground") + "...");
1250        optionsAddMenu.add(backgroundItem);
1251        backgroundItem.addActionListener((ActionEvent event) -> {
1252            addBackground();
1253            // note: panel resized in addBackground
1254            setDirty();
1255            redrawPanel();
1256        });
1257
1258        // add fast clock
1259        JMenuItem clockItem = new JMenuItem(Bundle.getMessage("AddItem", Bundle.getMessage("FastClock")));
1260        optionsAddMenu.add(clockItem);
1261        clockItem.addActionListener((ActionEvent event) -> {
1262            AnalogClock2Display c = addClock();
1263            unionToPanelBounds(c.getBounds());
1264            setDirty();
1265            redrawPanel();
1266        });
1267
1268        // add turntable
1269        JMenuItem turntableItem = new JMenuItem(Bundle.getMessage("AddTurntable"));
1270        optionsAddMenu.add(turntableItem);
1271        turntableItem.addActionListener((ActionEvent event) -> {
1272            Point2D pt = windowCenter();
1273            if (selectionActive) {
1274                pt = MathUtil.midPoint(getSelectionRect());
1275            }
1276            addTurntable(pt);
1277            // note: panel resized in addTurntable
1278            setDirty();
1279            redrawPanel();
1280        });
1281
1282        // add traverser
1283        JMenuItem traverserItem = new JMenuItem(Bundle.getMessage("AddTraverser"));
1284        optionsAddMenu.add(traverserItem);
1285        traverserItem.addActionListener((ActionEvent event) -> {
1286            Point2D pt = windowCenter();
1287            if (selectionActive) {
1288                pt = MathUtil.midPoint(getSelectionRect());
1289            }
1290            addTraverser(pt);
1291            // note: panel resized in addTraverser
1292            setDirty();
1293            redrawPanel();
1294        });
1295
1296        // add reporter
1297        JMenuItem reporterItem = new JMenuItem(Bundle.getMessage("AddReporter") + "...");
1298        optionsAddMenu.add(reporterItem);
1299        reporterItem.addActionListener((ActionEvent event) -> {
1300            Point2D pt = windowCenter();
1301            if (selectionActive) {
1302                pt = MathUtil.midPoint(getSelectionRect());
1303            }
1304            EnterReporterDialog d = new EnterReporterDialog(this);
1305            d.enterReporter((int) pt.getX(), (int) pt.getY());
1306            // note: panel resized in enterReporter
1307            setDirty();
1308            redrawPanel();
1309        });
1310
1311        for (var positionableFactory : ServiceLoader.load(PositionableFactory.class)) {
1312
1313            JMenuItem item = new JMenuItem(Bundle.getMessage("AddItem", positionableFactory.getDescription()));
1314            optionsAddMenu.add(item);
1315            item.addActionListener((ActionEvent event) -> {
1316
1317                positionableFactory.addPositionable(this, (p) -> {
1318                    unionToPanelBounds(p.getBounds());
1319                    setDirty();
1320                    redrawPanel();
1321                });
1322            });
1323        }
1324
1325        //
1326        // location coordinates format menu
1327        //
1328        JMenu locationMenu = new JMenu(Bundle.getMessage("LocationMenuTitle")); // used for location format SubMenu
1329        optionMenu.add(locationMenu);
1330
1331        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
1332            String windowFrameRef = getWindowFrameRef();
1333            Object prefsProp = prefsMgr.getProperty(windowFrameRef, "LocationFormat");
1334            // log.debug("{}.LocationFormat is {}", windowFrameRef, prefsProp);
1335            if (prefsProp != null) {
1336                getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.valueOf((String) prefsProp));
1337            }
1338        });
1339
1340        // pixels (jmri classic)
1341        locationMenu.add(pixelsCheckBoxMenuItem);
1342        pixelsCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1343            getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.ePIXELS);
1344            selectLocationFormatCheckBoxMenuItem();
1345            redrawPanel();
1346        });
1347
1348        // metric cm's
1349        locationMenu.add(metricCMCheckBoxMenuItem);
1350        metricCMCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1351            getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.eMETRIC_CM);
1352            selectLocationFormatCheckBoxMenuItem();
1353            redrawPanel();
1354        });
1355
1356        // english feet/inches/16th's
1357        locationMenu.add(englishFeetInchesCheckBoxMenuItem);
1358        englishFeetInchesCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1359            getLayoutEditorToolBarPanel().setLocationFormat(LocationFormat.eENGLISH_FEET_INCHES);
1360            selectLocationFormatCheckBoxMenuItem();
1361            redrawPanel();
1362        });
1363        selectLocationFormatCheckBoxMenuItem();
1364
1365        //
1366        // grid menu
1367        //
1368        JMenu gridMenu = new JMenu(Bundle.getMessage("GridMenuTitle")); // used for Grid SubMenu
1369        optionMenu.add(gridMenu);
1370
1371        // show grid
1372        showGridCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("ShowEditGrid"));
1373        showGridCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(
1374                Bundle.getMessage("ShowEditGridAccelerator")), primary_modifier));
1375        gridMenu.add(showGridCheckBoxMenuItem);
1376        showGridCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1377
1378            if (fixMacBugOn11(event)) {
1379                showGridCheckBoxMenuItem.setSelected(!showGridCheckBoxMenuItem.isSelected());
1380                return;
1381            }
1382
1383            drawGrid = showGridCheckBoxMenuItem.isSelected();
1384            redrawPanel();
1385        });
1386        showGridCheckBoxMenuItem.setSelected(getDrawGrid());
1387
1388        // snap to grid on add
1389        snapToGridOnAddCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("SnapToGridOnAdd"));
1390        snapToGridOnAddCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(
1391                Bundle.getMessage("SnapToGridOnAddAccelerator")),
1392                primary_modifier | ActionEvent.SHIFT_MASK));
1393        gridMenu.add(snapToGridOnAddCheckBoxMenuItem);
1394        snapToGridOnAddCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1395
1396            if (fixMacBugOn11(event)) {
1397                snapToGridOnAddCheckBoxMenuItem.setSelected(!snapToGridOnAddCheckBoxMenuItem.isSelected());
1398                return;
1399            }
1400
1401            snapToGridOnAdd = snapToGridOnAddCheckBoxMenuItem.isSelected();
1402            redrawPanel();
1403        });
1404        snapToGridOnAddCheckBoxMenuItem.setSelected(snapToGridOnAdd);
1405
1406        // snap to grid on move
1407        snapToGridOnMoveCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("SnapToGridOnMove"));
1408        snapToGridOnMoveCheckBoxMenuItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(
1409                Bundle.getMessage("SnapToGridOnMoveAccelerator")),
1410                primary_modifier | ActionEvent.SHIFT_MASK));
1411        gridMenu.add(snapToGridOnMoveCheckBoxMenuItem);
1412        snapToGridOnMoveCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1413
1414            if (fixMacBugOn11(event)) {
1415                snapToGridOnMoveCheckBoxMenuItem.setSelected(!snapToGridOnMoveCheckBoxMenuItem.isSelected());
1416                return;
1417            }
1418
1419            snapToGridOnMove = snapToGridOnMoveCheckBoxMenuItem.isSelected();
1420            redrawPanel();
1421        });
1422        snapToGridOnMoveCheckBoxMenuItem.setSelected(snapToGridOnMove);
1423
1424        // specify grid square size
1425        JMenuItem gridSizeItem = new JMenuItem(Bundle.getMessage("SetGridSizes") + "...");
1426        gridMenu.add(gridSizeItem);
1427        gridSizeItem.addActionListener((ActionEvent event) -> {
1428            EnterGridSizesDialog d = new EnterGridSizesDialog(this);
1429            d.enterGridSizes();
1430        });
1431
1432        //
1433        // track menu
1434        //
1435        JMenu trackMenu = new JMenu(Bundle.getMessage("TrackMenuTitle"));
1436        optionMenu.add(trackMenu);
1437
1438        // set track drawing options menu item
1439        JMenuItem jmi = new JMenuItem(Bundle.getMessage("SetTrackDrawingOptions"));
1440        trackMenu.add(jmi);
1441        jmi.setToolTipText(Bundle.getMessage("SetTrackDrawingOptionsToolTip"));
1442        jmi.addActionListener((ActionEvent event) -> {
1443            LayoutTrackDrawingOptionsDialog ltdod
1444                    = new LayoutTrackDrawingOptionsDialog(
1445                            this, true, getLayoutTrackDrawingOptions());
1446            ltdod.setVisible(true);
1447        });
1448
1449        // track colors item menu item
1450        JMenu trkColourMenu = new JMenu(Bundle.getMessage("TrackColorSubMenu"));
1451        trackMenu.add(trkColourMenu);
1452
1453        JMenuItem trackColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultTrackColor"));
1454        trkColourMenu.add(trackColorMenuItem);
1455        trackColorMenuItem.addActionListener((ActionEvent event) -> {
1456            Color desiredColor = JmriColorChooser.showDialog(this,
1457                    Bundle.getMessage("DefaultTrackColor"),
1458                    defaultTrackColor);
1459            if (desiredColor != null && !defaultTrackColor.equals(desiredColor)) {
1460                setDefaultTrackColor(desiredColor);
1461                setDirty();
1462                redrawPanel();
1463            }
1464        });
1465
1466        JMenuItem trackOccupiedColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultOccupiedTrackColor"));
1467        trkColourMenu.add(trackOccupiedColorMenuItem);
1468        trackOccupiedColorMenuItem.addActionListener((ActionEvent event) -> {
1469            Color desiredColor = JmriColorChooser.showDialog(this,
1470                    Bundle.getMessage("DefaultOccupiedTrackColor"),
1471                    defaultOccupiedTrackColor);
1472            if (desiredColor != null && !defaultOccupiedTrackColor.equals(desiredColor)) {
1473                setDefaultOccupiedTrackColor(desiredColor);
1474                setDirty();
1475                redrawPanel();
1476            }
1477        });
1478
1479        JMenuItem trackAlternativeColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultAlternativeTrackColor"));
1480        trkColourMenu.add(trackAlternativeColorMenuItem);
1481        trackAlternativeColorMenuItem.addActionListener((ActionEvent event) -> {
1482            Color desiredColor = JmriColorChooser.showDialog(this,
1483                    Bundle.getMessage("DefaultAlternativeTrackColor"),
1484                    defaultAlternativeTrackColor);
1485            if (desiredColor != null && !defaultAlternativeTrackColor.equals(desiredColor)) {
1486                setDefaultAlternativeTrackColor(desiredColor);
1487                setDirty();
1488                redrawPanel();
1489            }
1490        });
1491
1492        // Set All Tracks To Default Colors
1493        JMenuItem setAllTracksToDefaultColorsMenuItem = new JMenuItem(Bundle.getMessage("SetAllTracksToDefaultColors"));
1494        trkColourMenu.add(setAllTracksToDefaultColorsMenuItem);
1495        setAllTracksToDefaultColorsMenuItem.addActionListener((ActionEvent event) -> {
1496            if (setAllTracksToDefaultColors() > 0) {
1497                setDirty();
1498                redrawPanel();
1499            }
1500        });
1501
1502        // Automatically Assign Blocks to Track
1503        autoAssignBlocksCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AutoAssignBlock"));
1504        trackMenu.add(autoAssignBlocksCheckBoxMenuItem);
1505        autoAssignBlocksCheckBoxMenuItem.addActionListener((ActionEvent event) -> autoAssignBlocks = autoAssignBlocksCheckBoxMenuItem.isSelected());
1506        autoAssignBlocksCheckBoxMenuItem.setSelected(autoAssignBlocks);
1507
1508        // add hideTrackSegmentConstructionLines menu item
1509        hideTrackSegmentConstructionLinesCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("HideTrackConLines"));
1510        trackMenu.add(hideTrackSegmentConstructionLinesCheckBoxMenuItem);
1511        hideTrackSegmentConstructionLinesCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1512            int show = TrackSegmentView.SHOWCON;
1513
1514            if (hideTrackSegmentConstructionLinesCheckBoxMenuItem.isSelected()) {
1515                show = TrackSegmentView.HIDECONALL;
1516            }
1517
1518            for (TrackSegmentView tsv : getTrackSegmentViews()) {
1519                tsv.hideConstructionLines(show);
1520            }
1521            redrawPanel();
1522        });
1523        hideTrackSegmentConstructionLinesCheckBoxMenuItem.setSelected(autoAssignBlocks);
1524
1525        //
1526        // add turnout options submenu
1527        //
1528        JMenu turnoutOptionsMenu = new JMenu(Bundle.getMessage("TurnoutOptions"));
1529        optionMenu.add(turnoutOptionsMenu);
1530
1531        // animation item
1532        animationCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("AllowTurnoutAnimation"));
1533        turnoutOptionsMenu.add(animationCheckBoxMenuItem);
1534        animationCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1535            boolean mode = animationCheckBoxMenuItem.isSelected();
1536            setTurnoutAnimation(mode);
1537        });
1538        animationCheckBoxMenuItem.setSelected(true);
1539
1540        // circle on Turnouts
1541        turnoutCirclesOnCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("TurnoutCirclesOn"));
1542        turnoutOptionsMenu.add(turnoutCirclesOnCheckBoxMenuItem);
1543        turnoutCirclesOnCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1544            turnoutCirclesWithoutEditMode = turnoutCirclesOnCheckBoxMenuItem.isSelected();
1545            redrawPanel();
1546        });
1547        turnoutCirclesOnCheckBoxMenuItem.setSelected(turnoutCirclesWithoutEditMode);
1548
1549        // select turnout circle color
1550        JMenuItem turnoutCircleColorMenuItem = new JMenuItem(Bundle.getMessage("TurnoutCircleColor"));
1551        turnoutCircleColorMenuItem.addActionListener((ActionEvent event) -> {
1552            Color desiredColor = JmriColorChooser.showDialog(this,
1553                    Bundle.getMessage("TurnoutCircleColor"),
1554                    turnoutCircleColor);
1555            if (desiredColor != null && !turnoutCircleColor.equals(desiredColor)) {
1556                setTurnoutCircleColor(desiredColor);
1557                setDirty();
1558                redrawPanel();
1559            }
1560        });
1561        turnoutOptionsMenu.add(turnoutCircleColorMenuItem);
1562
1563        // select turnout circle thrown color
1564        JMenuItem turnoutCircleThrownColorMenuItem = new JMenuItem(Bundle.getMessage("TurnoutCircleThrownColor"));
1565        turnoutCircleThrownColorMenuItem.addActionListener((ActionEvent event) -> {
1566            Color desiredColor = JmriColorChooser.showDialog(this,
1567                    Bundle.getMessage("TurnoutCircleThrownColor"),
1568                    turnoutCircleThrownColor);
1569            if (desiredColor != null && !turnoutCircleThrownColor.equals(desiredColor)) {
1570                setTurnoutCircleThrownColor(desiredColor);
1571                setDirty();
1572                redrawPanel();
1573            }
1574        });
1575        turnoutOptionsMenu.add(turnoutCircleThrownColorMenuItem);
1576
1577        turnoutFillControlCirclesCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("TurnoutFillControlCircles"));
1578        turnoutOptionsMenu.add(turnoutFillControlCirclesCheckBoxMenuItem);
1579        turnoutFillControlCirclesCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1580            turnoutFillControlCircles = turnoutFillControlCirclesCheckBoxMenuItem.isSelected();
1581            redrawPanel();
1582        });
1583        turnoutFillControlCirclesCheckBoxMenuItem.setSelected(turnoutFillControlCircles);
1584
1585        // select turnout circle size
1586        JMenu turnoutCircleSizeMenu = new JMenu(Bundle.getMessage("TurnoutCircleSize"));
1587        turnoutCircleSizeButtonGroup = new ButtonGroup();
1588        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "1", 1);
1589        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "2", 2);
1590        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "3", 3);
1591        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "4", 4);
1592        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "5", 5);
1593        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "6", 6);
1594        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "7", 7);
1595        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "8", 8);
1596        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "9", 9);
1597        addTurnoutCircleSizeMenuEntry(turnoutCircleSizeMenu, "10", 10);
1598        turnoutOptionsMenu.add(turnoutCircleSizeMenu);
1599
1600        // add "enable drawing of unselected leg " menu item (helps when diverging angle is small)
1601        turnoutDrawUnselectedLegCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("TurnoutDrawUnselectedLeg"));
1602        turnoutOptionsMenu.add(turnoutDrawUnselectedLegCheckBoxMenuItem);
1603        turnoutDrawUnselectedLegCheckBoxMenuItem.addActionListener((ActionEvent event) -> {
1604            turnoutDrawUnselectedLeg = turnoutDrawUnselectedLegCheckBoxMenuItem.isSelected();
1605            redrawPanel();
1606        });
1607        turnoutDrawUnselectedLegCheckBoxMenuItem.setSelected(turnoutDrawUnselectedLeg);
1608
1609        return optionMenu;
1610    }
1611
1612    private void selectLocationFormatCheckBoxMenuItem() {
1613        pixelsCheckBoxMenuItem.setSelected(getLayoutEditorToolBarPanel().getLocationFormat() == LocationFormat.ePIXELS);
1614        metricCMCheckBoxMenuItem.setSelected(getLayoutEditorToolBarPanel().getLocationFormat() == LocationFormat.eMETRIC_CM);
1615        englishFeetInchesCheckBoxMenuItem.setSelected(getLayoutEditorToolBarPanel().getLocationFormat() == LocationFormat.eENGLISH_FEET_INCHES);
1616    }
1617
1618    /*============================================*\
1619    |* LayoutTrackDrawingOptions accessor methods *|
1620    \*============================================*/
1621    private LayoutTrackDrawingOptions layoutTrackDrawingOptions = null;
1622
1623    /**
1624     *
1625     * Getter Layout Track Drawing Options. since 4.15.6 split variable
1626     * defaultTrackColor and mainlineTrackColor/sidelineTrackColor <br>
1627     * blockDefaultColor, blockOccupiedColor and blockAlternativeColor added to
1628     * LayoutTrackDrawingOptions <br>
1629     *
1630     * @return LayoutTrackDrawingOptions object
1631     */
1632    @Nonnull
1633    public LayoutTrackDrawingOptions getLayoutTrackDrawingOptions() {
1634        if (layoutTrackDrawingOptions == null) {
1635            layoutTrackDrawingOptions = new LayoutTrackDrawingOptions(getLayoutName());
1636            // integrate LayoutEditor drawing options with previous drawing options
1637            layoutTrackDrawingOptions.setMainBlockLineWidth(gContext.getMainlineTrackWidth());
1638            layoutTrackDrawingOptions.setSideBlockLineWidth(gContext.getSidelineTrackWidth());
1639            layoutTrackDrawingOptions.setMainRailWidth(gContext.getMainlineTrackWidth());
1640            layoutTrackDrawingOptions.setSideRailWidth(gContext.getSidelineTrackWidth());
1641            layoutTrackDrawingOptions.setMainRailColor(mainlineTrackColor);
1642            layoutTrackDrawingOptions.setSideRailColor(sidelineTrackColor);
1643            layoutTrackDrawingOptions.setBlockDefaultColor(defaultTrackColor);
1644            layoutTrackDrawingOptions.setBlockOccupiedColor(defaultOccupiedTrackColor);
1645            layoutTrackDrawingOptions.setBlockAlternativeColor(defaultAlternativeTrackColor);
1646        }
1647        return layoutTrackDrawingOptions;
1648    }
1649
1650    /**
1651     * since 4.15.6 split variable defaultTrackColor and
1652     * mainlineTrackColor/sidelineTrackColor
1653     *
1654     * @param ltdo LayoutTrackDrawingOptions object
1655     */
1656    public void setLayoutTrackDrawingOptions(LayoutTrackDrawingOptions ltdo) {
1657        layoutTrackDrawingOptions = ltdo;
1658
1659        // copy main/side line block widths
1660        gContext.setMainlineBlockWidth(layoutTrackDrawingOptions.getMainBlockLineWidth());
1661        gContext.setSidelineBlockWidth(layoutTrackDrawingOptions.getSideBlockLineWidth());
1662
1663        // copy main/side line track (rail) widths
1664        gContext.setMainlineTrackWidth(layoutTrackDrawingOptions.getMainRailWidth());
1665        gContext.setSidelineTrackWidth(layoutTrackDrawingOptions.getSideRailWidth());
1666
1667        mainlineTrackColor = layoutTrackDrawingOptions.getMainRailColor();
1668        sidelineTrackColor = layoutTrackDrawingOptions.getSideRailColor();
1669        redrawPanel();
1670    }
1671
1672    private JCheckBoxMenuItem skipTurnoutCheckBoxMenuItem = null;
1673    private AddEntryExitPairAction addEntryExitPairAction = null;
1674
1675    /**
1676     * setup the Layout Editor Tools menu
1677     *
1678     * @param menuBar the menu bar to add the Tools menu to
1679     */
1680    private void setupToolsMenu(@Nonnull JMenuBar menuBar) {
1681        JMenu toolsMenu = new JMenu(Bundle.getMessage("MenuTools"));
1682
1683        toolsMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuToolsMnemonic")));
1684        menuBar.add(toolsMenu);
1685
1686        // setup checks menu
1687        getLEChecks().setupChecksMenu(toolsMenu);
1688
1689        // assign blocks to selection
1690        assignBlockToSelectionMenuItem.setToolTipText(Bundle.getMessage("AssignBlockToSelectionToolTip"));
1691        toolsMenu.add(assignBlockToSelectionMenuItem);
1692        assignBlockToSelectionMenuItem.addActionListener((ActionEvent event) -> {
1693            // bring up scale track diagram dialog
1694            assignBlockToSelection();
1695        });
1696        assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
1697
1698        // scale track diagram
1699        JMenuItem jmi = new JMenuItem(Bundle.getMessage("ScaleTrackDiagram") + "...");
1700        jmi.setToolTipText(Bundle.getMessage("ScaleTrackDiagramToolTip"));
1701        toolsMenu.add(jmi);
1702        jmi.addActionListener((ActionEvent event) -> {
1703            // bring up scale track diagram dialog
1704            ScaleTrackDiagramDialog d = new ScaleTrackDiagramDialog(this);
1705            d.scaleTrackDiagram();
1706        });
1707
1708        // translate selection
1709        jmi = new JMenuItem(Bundle.getMessage("TranslateSelection") + "...");
1710        jmi.setToolTipText(Bundle.getMessage("TranslateSelectionToolTip"));
1711        toolsMenu.add(jmi);
1712        jmi.addActionListener((ActionEvent event) -> {
1713            // bring up translate selection dialog
1714            if (!selectionActive || (selectionWidth == 0.0) || (selectionHeight == 0.0)) {
1715                // no selection has been made - nothing to move
1716                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error12"),
1717                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1718            } else {
1719                // bring up move selection dialog
1720                MoveSelectionDialog d = new MoveSelectionDialog(this);
1721                d.moveSelection();
1722            }
1723        });
1724
1725        // undo translate selection
1726        undoTranslateSelectionMenuItem.setToolTipText(Bundle.getMessage("UndoTranslateSelectionToolTip"));
1727        toolsMenu.add(undoTranslateSelectionMenuItem);
1728        undoTranslateSelectionMenuItem.addActionListener((ActionEvent event) -> {
1729            // undo previous move selection
1730            undoMoveSelection();
1731        });
1732        undoTranslateSelectionMenuItem.setEnabled(canUndoMoveSelection);
1733
1734        // rotate selection
1735        jmi = new JMenuItem(Bundle.getMessage("RotateSelection90MenuItemTitle"));
1736        jmi.setToolTipText(Bundle.getMessage("RotateSelection90MenuItemToolTip"));
1737        toolsMenu.add(jmi);
1738        jmi.addActionListener((ActionEvent event) -> rotateSelection90());
1739
1740        // rotate entire layout
1741        jmi = new JMenuItem(Bundle.getMessage("RotateLayout90MenuItemTitle"));
1742        jmi.setToolTipText(Bundle.getMessage("RotateLayout90MenuItemToolTip"));
1743        toolsMenu.add(jmi);
1744        jmi.addActionListener((ActionEvent event) -> rotateLayout90());
1745
1746        // align layout to grid
1747        jmi = new JMenuItem(Bundle.getMessage("AlignLayoutToGridMenuItemTitle") + "...");
1748        jmi.setToolTipText(Bundle.getMessage("AlignLayoutToGridMenuItemToolTip"));
1749        toolsMenu.add(jmi);
1750        jmi.addActionListener((ActionEvent event) -> alignLayoutToGrid());
1751
1752        // align selection to grid
1753        jmi = new JMenuItem(Bundle.getMessage("AlignSelectionToGridMenuItemTitle") + "...");
1754        jmi.setToolTipText(Bundle.getMessage("AlignSelectionToGridMenuItemToolTip"));
1755        toolsMenu.add(jmi);
1756        jmi.addActionListener((ActionEvent event) -> alignSelectionToGrid());
1757
1758        // reset turnout size to program defaults
1759        jmi = new JMenuItem(Bundle.getMessage("ResetTurnoutSize"));
1760        jmi.setToolTipText(Bundle.getMessage("ResetTurnoutSizeToolTip"));
1761        toolsMenu.add(jmi);
1762        jmi.addActionListener((ActionEvent event) -> {
1763            // undo previous move selection
1764            resetTurnoutSize();
1765        });
1766        toolsMenu.addSeparator();
1767
1768        // skip turnout
1769        skipTurnoutCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("SkipInternalTurnout"));
1770        skipTurnoutCheckBoxMenuItem.setToolTipText(Bundle.getMessage("SkipInternalTurnoutToolTip"));
1771        toolsMenu.add(skipTurnoutCheckBoxMenuItem);
1772        skipTurnoutCheckBoxMenuItem.addActionListener((ActionEvent event) -> setIncludedTurnoutSkipped(skipTurnoutCheckBoxMenuItem.isSelected()));
1773        skipTurnoutCheckBoxMenuItem.setSelected(isIncludedTurnoutSkipped());
1774
1775        // set signals at turnout
1776        jmi = new JMenuItem(Bundle.getMessage("SignalsAtTurnout") + "...");
1777        jmi.setToolTipText(Bundle.getMessage("SignalsAtTurnoutToolTip"));
1778        toolsMenu.add(jmi);
1779        jmi.addActionListener((ActionEvent event) -> {
1780            // bring up signals at turnout tool dialog
1781            getLETools().setSignalsAtTurnout(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
1782        });
1783
1784        // set signals at block boundary
1785        jmi = new JMenuItem(Bundle.getMessage("SignalsAtBoundary") + "...");
1786        jmi.setToolTipText(Bundle.getMessage("SignalsAtBoundaryToolTip"));
1787        toolsMenu.add(jmi);
1788        jmi.addActionListener((ActionEvent event) -> {
1789            // bring up signals at block boundary tool dialog
1790            getLETools().setSignalsAtBlockBoundary(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
1791        });
1792
1793        // set signals at crossover turnout
1794        jmi = new JMenuItem(Bundle.getMessage("SignalsAtXoverTurnout") + "...");
1795        jmi.setToolTipText(Bundle.getMessage("SignalsAtXoverTurnoutToolTip"));
1796        toolsMenu.add(jmi);
1797        jmi.addActionListener((ActionEvent event) -> {
1798            // bring up signals at crossover tool dialog
1799            getLETools().setSignalsAtXoverTurnout(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
1800        });
1801
1802        // set signals at level crossing
1803        jmi = new JMenuItem(Bundle.getMessage("SignalsAtLevelXing") + "...");
1804        jmi.setToolTipText(Bundle.getMessage("SignalsAtLevelXingToolTip"));
1805        toolsMenu.add(jmi);
1806        jmi.addActionListener((ActionEvent event) -> {
1807            // bring up signals at level crossing tool dialog
1808            getLETools().setSignalsAtLevelXing(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
1809        });
1810
1811        // set signals at throat-to-throat turnouts
1812        jmi = new JMenuItem(Bundle.getMessage("SignalsAtTToTTurnout") + "...");
1813        jmi.setToolTipText(Bundle.getMessage("SignalsAtTToTTurnoutToolTip"));
1814        toolsMenu.add(jmi);
1815        jmi.addActionListener((ActionEvent event) -> {
1816            // bring up signals at throat-to-throat turnouts tool dialog
1817            getLETools().setSignalsAtThroatToThroatTurnouts(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
1818        });
1819
1820        // set signals at 3-way turnout
1821        jmi = new JMenuItem(Bundle.getMessage("SignalsAt3WayTurnout") + "...");
1822        jmi.setToolTipText(Bundle.getMessage("SignalsAt3WayTurnoutToolTip"));
1823        toolsMenu.add(jmi);
1824        jmi.addActionListener((ActionEvent event) -> {
1825            // bring up signals at 3-way turnout tool dialog
1826            getLETools().setSignalsAt3WayTurnout(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
1827        });
1828
1829        jmi = new JMenuItem(Bundle.getMessage("SignalsAtSlip") + "...");
1830        jmi.setToolTipText(Bundle.getMessage("SignalsAtSlipToolTip"));
1831        toolsMenu.add(jmi);
1832        jmi.addActionListener((ActionEvent event) -> {
1833            // bring up signals at throat-to-throat turnouts tool dialog
1834            getLETools().setSignalsAtSlip(leToolBarPanel.signalIconEditor, leToolBarPanel.signalFrame);
1835        });
1836
1837        jmi = new JMenuItem(Bundle.getMessage("EntryExitTitle") + "...");
1838        jmi.setToolTipText(Bundle.getMessage("EntryExitToolTip"));
1839        toolsMenu.add(jmi);
1840        jmi.addActionListener((ActionEvent event) -> {
1841            if (addEntryExitPairAction == null) {
1842                addEntryExitPairAction = new AddEntryExitPairAction("ENTRY EXIT", LayoutEditor.this);
1843            }
1844            addEntryExitPairAction.actionPerformed(event);
1845        });
1846//        if (true) {   // TODO: disable for production
1847//            jmi = new JMenuItem("GEORGE");
1848//            toolsMenu.add(jmi);
1849//            jmi.addActionListener((ActionEvent event) -> {
1850//                // do GEORGE stuff here!
1851//            });
1852//        }
1853    }   // setupToolsMenu
1854
1855    /**
1856     * get the toolbar side
1857     *
1858     * @return the side where to put the tool bar
1859     */
1860    public ToolBarSide getToolBarSide() {
1861        return toolBarSide;
1862    }
1863
1864    /**
1865     * set the tool bar side
1866     *
1867     * @param newToolBarSide on which side to put the toolbar
1868     */
1869    public void setToolBarSide(ToolBarSide newToolBarSide) {
1870        // null if edit toolbar is not setup yet...
1871        if (!newToolBarSide.equals(toolBarSide)) {
1872            toolBarSide = newToolBarSide;
1873            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setProperty(getWindowFrameRef(), "toolBarSide", toolBarSide.getName()));
1874            toolBarSideTopButton.setSelected(toolBarSide.equals(ToolBarSide.eTOP));
1875            toolBarSideLeftButton.setSelected(toolBarSide.equals(ToolBarSide.eLEFT));
1876            toolBarSideBottomButton.setSelected(toolBarSide.equals(ToolBarSide.eBOTTOM));
1877            toolBarSideRightButton.setSelected(toolBarSide.equals(ToolBarSide.eRIGHT));
1878            toolBarSideFloatButton.setSelected(toolBarSide.equals(ToolBarSide.eFLOAT));
1879
1880            setupToolBar(); // re-layout all the toolbar items
1881
1882            if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
1883                if (editToolBarContainerPanel != null) {
1884                    editToolBarContainerPanel.setVisible(false);
1885                }
1886                if (floatEditHelpPanel != null) {
1887                    floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
1888                }
1889            } else {
1890                if (floatingEditToolBoxFrame != null) {
1891                    deletefloatingEditToolBoxFrame();
1892                }
1893                editToolBarContainerPanel.setVisible(isEditable());
1894                if (getShowHelpBar()) {
1895                    helpBarPanel.setVisible(isEditable());
1896                    // not sure why... but this is the only way I could
1897                    // get everything to layout correctly
1898                    // when the helpbar is visible...
1899                    boolean editMode = isEditable();
1900                    setAllEditable(!editMode);
1901                    setAllEditable(editMode);
1902                }
1903            }
1904            wideToolBarCheckBoxMenuItem.setEnabled(
1905                    toolBarSide.equals(ToolBarSide.eTOP)
1906                    || toolBarSide.equals(ToolBarSide.eBOTTOM));
1907        }
1908    }   // setToolBarSide
1909
1910    //
1911    //
1912    //
1913    private void setToolBarWide(boolean newToolBarIsWide) {
1914        // null if edit toolbar not setup yet...
1915        if (leToolBarPanel.toolBarIsWide != newToolBarIsWide) {
1916            leToolBarPanel.toolBarIsWide = newToolBarIsWide;
1917
1918            wideToolBarCheckBoxMenuItem.setSelected(leToolBarPanel.toolBarIsWide);
1919
1920            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
1921                // Note: since prefs default to false and we want wide to be the default
1922                // we invert it and save it as thin
1923                prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".toolBarThin", !leToolBarPanel.toolBarIsWide);
1924            });
1925
1926            setupToolBar(); // re-layout all the toolbar items
1927
1928            if (getShowHelpBar()) {
1929                // not sure why, but this is the only way I could
1930                // get everything to layout correctly
1931                // when the helpbar is visible...
1932                boolean editMode = isEditable();
1933                setAllEditable(!editMode);
1934                setAllEditable(editMode);
1935            } else {
1936                helpBarPanel.setVisible(isEditable() && getShowHelpBar());
1937            }
1938        }
1939    }   // setToolBarWide
1940
1941    //
1942    //
1943    //
1944    @SuppressWarnings("deprecation")  // getMenuShortcutKeyMask()
1945    private void setupZoomMenu(@Nonnull JMenuBar menuBar) {
1946        zoomMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuZoomMnemonic")));
1947        menuBar.add(zoomMenu);
1948        ButtonGroup zoomButtonGroup = new ButtonGroup();
1949
1950        int primary_modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
1951
1952        // add zoom choices to menu
1953        JMenuItem zoomInItem = new JMenuItem(Bundle.getMessage("ZoomIn"));
1954        zoomInItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("zoomInMnemonic")));
1955        String zoomInAccelerator = Bundle.getMessage("zoomInAccelerator");
1956        // log.debug("zoomInAccelerator: " + zoomInAccelerator);
1957        zoomInItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(zoomInAccelerator), primary_modifier));
1958        zoomMenu.add(zoomInItem);
1959        zoomInItem.addActionListener((ActionEvent event) -> setZoom(getZoom() * 1.1));
1960
1961        JMenuItem zoomOutItem = new JMenuItem(Bundle.getMessage("ZoomOut"));
1962        zoomOutItem.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("zoomOutMnemonic")));
1963        String zoomOutAccelerator = Bundle.getMessage("zoomOutAccelerator");
1964        // log.debug("zoomOutAccelerator: " + zoomOutAccelerator);
1965        zoomOutItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(zoomOutAccelerator), primary_modifier));
1966        zoomMenu.add(zoomOutItem);
1967        zoomOutItem.addActionListener((ActionEvent event) -> setZoom(getZoom() / 1.1));
1968
1969        JMenuItem zoomFitItem = new JMenuItem(Bundle.getMessage("ZoomToFit"));
1970        zoomMenu.add(zoomFitItem);
1971        zoomFitItem.addActionListener((ActionEvent event) -> zoomToFit());
1972        zoomMenu.addSeparator();
1973
1974        // add zoom choices to menu
1975        zoomMenu.add(zoom025Item);
1976        zoom025Item.addActionListener((ActionEvent event) -> setZoom(0.25));
1977        zoomButtonGroup.add(zoom025Item);
1978
1979        zoomMenu.add(zoom05Item);
1980        zoom05Item.addActionListener((ActionEvent event) -> setZoom(0.5));
1981        zoomButtonGroup.add(zoom05Item);
1982
1983        zoomMenu.add(zoom075Item);
1984        zoom075Item.addActionListener((ActionEvent event) -> setZoom(0.75));
1985        zoomButtonGroup.add(zoom075Item);
1986
1987        String zoomNoneAccelerator = Bundle.getMessage("zoomNoneAccelerator");
1988        // log.debug("zoomNoneAccelerator: " + zoomNoneAccelerator);
1989        noZoomItem.setAccelerator(KeyStroke.getKeyStroke(stringsToVTCodes.get(zoomNoneAccelerator), primary_modifier));
1990
1991        zoomMenu.add(noZoomItem);
1992        noZoomItem.addActionListener((ActionEvent event) -> setZoom(1.0));
1993        zoomButtonGroup.add(noZoomItem);
1994
1995        zoomMenu.add(zoom15Item);
1996        zoom15Item.addActionListener((ActionEvent event) -> setZoom(1.5));
1997        zoomButtonGroup.add(zoom15Item);
1998
1999        zoomMenu.add(zoom20Item);
2000        zoom20Item.addActionListener((ActionEvent event) -> setZoom(2.0));
2001        zoomButtonGroup.add(zoom20Item);
2002
2003        zoomMenu.add(zoom30Item);
2004        zoom30Item.addActionListener((ActionEvent event) -> setZoom(3.0));
2005        zoomButtonGroup.add(zoom30Item);
2006
2007        zoomMenu.add(zoom40Item);
2008        zoom40Item.addActionListener((ActionEvent event) -> setZoom(4.0));
2009        zoomButtonGroup.add(zoom40Item);
2010
2011        zoomMenu.add(zoom50Item);
2012        zoom50Item.addActionListener((ActionEvent event) -> setZoom(5.0));
2013        zoomButtonGroup.add(zoom50Item);
2014
2015        zoomMenu.add(zoom60Item);
2016        zoom60Item.addActionListener((ActionEvent event) -> setZoom(6.0));
2017        zoomButtonGroup.add(zoom60Item);
2018
2019        zoomMenu.add(zoom70Item);
2020        zoom70Item.addActionListener((ActionEvent event) -> setZoom(7.0));
2021        zoomButtonGroup.add(zoom70Item);
2022
2023        zoomMenu.add(zoom80Item);
2024        zoom80Item.addActionListener((ActionEvent event) -> setZoom(8.0));
2025        zoomButtonGroup.add(zoom80Item);
2026
2027        // note: because this LayoutEditor object was just instantiated its
2028        // zoom attribute is 1.0; if it's being instantiated from an XML file
2029        // that has a zoom attribute for this object then setZoom will be
2030        // called after this method returns and we'll select the appropriate
2031        // menu item then.
2032        noZoomItem.setSelected(true);
2033
2034        // Note: We have to invoke this stuff later because _targetPanel is not setup yet
2035        SwingUtilities.invokeLater(() -> {
2036            // get the window specific saved zoom user preference
2037            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
2038                Object zoomProp = prefsMgr.getProperty(getWindowFrameRef(), "zoom");
2039                log.debug("{} zoom is {}", getWindowFrameRef(), zoomProp);
2040                if (zoomProp != null) {
2041                    setZoom((Double) zoomProp);
2042                }
2043            }
2044            );
2045
2046            // get the scroll bars from the scroll pane
2047            JScrollPane scrollPane = getPanelScrollPane();
2048            if (scrollPane != null) {
2049                JScrollBar hsb = scrollPane.getHorizontalScrollBar();
2050                JScrollBar vsb = scrollPane.getVerticalScrollBar();
2051
2052                // Increase scroll bar unit increments!!!
2053                vsb.setUnitIncrement(gContext.getGridSize());
2054                hsb.setUnitIncrement(gContext.getGridSize());
2055
2056                // add scroll bar adjustment listeners
2057                vsb.addAdjustmentListener(this::scrollBarAdjusted);
2058                hsb.addAdjustmentListener(this::scrollBarAdjusted);
2059
2060                // remove all mouse wheel listeners
2061                mouseWheelListeners = scrollPane.getMouseWheelListeners();
2062                for (MouseWheelListener mwl : mouseWheelListeners) {
2063                    scrollPane.removeMouseWheelListener(mwl);
2064                }
2065
2066                // add my mouse wheel listener
2067                // (so mouseWheelMoved (below) will be called)
2068                scrollPane.addMouseWheelListener(this);
2069            }
2070        });
2071    }   // setupZoomMenu
2072
2073    private MouseWheelListener[] mouseWheelListeners;
2074
2075    // scroll bar listener to update x & y coordinates in toolbar on scroll
2076    public void scrollBarAdjusted(AdjustmentEvent event) {
2077        // log.warn("scrollBarAdjusted");
2078        if (isEditable()) {
2079            // get the location of the mouse
2080            PointerInfo mpi = MouseInfo.getPointerInfo();
2081            Point mouseLoc = mpi.getLocation();
2082            // convert to target panel coordinates
2083            SwingUtilities.convertPointFromScreen(mouseLoc, getTargetPanel());
2084            // correct for scaling...
2085            double theZoom = getZoom();
2086            xLoc = (int) (mouseLoc.getX() / theZoom);
2087            yLoc = (int) (mouseLoc.getY() / theZoom);
2088            dLoc = new Point2D.Double(xLoc, yLoc);
2089
2090            leToolBarPanel.setLocationText(dLoc);
2091        }
2092        adjustClip();
2093    }
2094
2095    private void adjustScrollBars() {
2096        // log.info("adjustScrollBars()");
2097
2098        // This is the bounds of what's on the screen
2099        JScrollPane scrollPane = getPanelScrollPane();
2100        Rectangle scrollBounds = scrollPane.getViewportBorderBounds();
2101        // log.info("  getViewportBorderBounds: {}", MathUtil.rectangle2DToString(scrollBounds));
2102
2103        // this is the size of the entire scaled layout panel
2104        Dimension targetPanelSize = getTargetPanelSize();
2105        // log.info("  getTargetPanelSize: {}", MathUtil.dimensionToString(targetPanelSize));
2106
2107        // double scale = getZoom();
2108        // determine the relative position of the current horizontal scrollbar
2109        JScrollBar horScroll = scrollPane.getHorizontalScrollBar();
2110        double oldX = horScroll.getValue();
2111        double oldMaxX = horScroll.getMaximum();
2112        double ratioX = (oldMaxX < 1) ? 0 : oldX / oldMaxX;
2113
2114        // calculate the new X maximum and value
2115        int panelWidth = (int) (targetPanelSize.getWidth());
2116        int scrollWidth = (int) scrollBounds.getWidth();
2117        int newMaxX = Math.max(panelWidth - scrollWidth, 0);
2118        int newX = (int) (newMaxX * ratioX);
2119        horScroll.setMaximum(newMaxX);
2120        horScroll.setValue(newX);
2121
2122        // determine the relative position of the current vertical scrollbar
2123        JScrollBar vertScroll = scrollPane.getVerticalScrollBar();
2124        double oldY = vertScroll.getValue();
2125        double oldMaxY = vertScroll.getMaximum();
2126        double ratioY = (oldMaxY < 1) ? 0 : oldY / oldMaxY;
2127
2128        // calculate the new X maximum and value
2129        int tempPanelHeight = (int) (targetPanelSize.getHeight());
2130        int tempScrollHeight = (int) scrollBounds.getHeight();
2131        int newMaxY = Math.max(tempPanelHeight - tempScrollHeight, 0);
2132        int newY = (int) (newMaxY * ratioY);
2133        vertScroll.setMaximum(newMaxY);
2134        vertScroll.setValue(newY);
2135
2136//        log.info("w: {}, x: {}, h: {}, y: {}", "" + newMaxX, "" + newX, "" + newMaxY, "" + newY);
2137        adjustClip();
2138    }
2139
2140    private void adjustClip() {
2141        // log.info("adjustClip()");
2142
2143        // This is the bounds of what's on the screen
2144        JScrollPane scrollPane = getPanelScrollPane();
2145        Rectangle scrollBounds = scrollPane.getViewportBorderBounds();
2146        // log.info("  ViewportBorderBounds: {}", MathUtil.rectangle2DToString(scrollBounds));
2147
2148        JScrollBar horScroll = scrollPane.getHorizontalScrollBar();
2149        int scrollX = horScroll.getValue();
2150        JScrollBar vertScroll = scrollPane.getVerticalScrollBar();
2151        int scrollY = vertScroll.getValue();
2152
2153        Rectangle2D newClipRect = MathUtil.offset(
2154                scrollBounds,
2155                scrollX - scrollBounds.getMinX(),
2156                scrollY - scrollBounds.getMinY());
2157        newClipRect = MathUtil.scale(newClipRect, 1.0 / getZoom());
2158        newClipRect = MathUtil.granulize(newClipRect, 1.0); // round to nearest pixel
2159        layoutEditorComponent.setClip(newClipRect);
2160
2161        redrawPanel();
2162    }
2163
2164    @Override
2165    public void mouseWheelMoved(@Nonnull MouseWheelEvent event) {
2166        // log.warn("mouseWheelMoved");
2167        if (event.isAltDown()) {
2168            // get the mouse position from the event and convert to target panel coordinates
2169            Component component = (Component) event.getSource();
2170            Point eventPoint = event.getPoint();
2171            JComponent targetPanel = getTargetPanel();
2172            Point2D mousePoint = SwingUtilities.convertPoint(component, eventPoint, targetPanel);
2173
2174            // get the old view port position
2175            JScrollPane scrollPane = getPanelScrollPane();
2176            JViewport viewPort = scrollPane.getViewport();
2177            Point2D viewPosition = viewPort.getViewPosition();
2178
2179            // convert from oldZoom (scaled) coordinates to image coordinates
2180            double zoom = getZoom();
2181            Point2D imageMousePoint = MathUtil.divide(mousePoint, zoom);
2182            Point2D imageViewPosition = MathUtil.divide(viewPosition, zoom);
2183            // compute the delta (in image coordinates)
2184            Point2D imageDelta = MathUtil.subtract(imageMousePoint, imageViewPosition);
2185
2186            // compute how much to change zoom
2187            double amount = Math.pow(1.1, event.getScrollAmount());
2188            if (event.getWheelRotation() < 0.0) {
2189                // reciprocal for zoom out
2190                amount = 1.0 / amount;
2191            }
2192            // set the new zoom
2193            double newZoom = setZoom(zoom * amount);
2194            // recalulate the amount (in case setZoom didn't zoom as much as we wanted)
2195            amount = newZoom / zoom;
2196
2197            // convert the old delta to the new
2198            Point2D newImageDelta = MathUtil.divide(imageDelta, amount);
2199            // calculate the new view position (in image coordinates)
2200            Point2D newImageViewPosition = MathUtil.subtract(imageMousePoint, newImageDelta);
2201            // convert from image coordinates to newZoom (scaled) coordinates
2202            Point2D newViewPosition = MathUtil.multiply(newImageViewPosition, newZoom);
2203
2204            // don't let origin go negative
2205            newViewPosition = MathUtil.max(newViewPosition, MathUtil.zeroPoint2D);
2206            // log.info("mouseWheelMoved: newViewPos2D: {}", newViewPosition);
2207
2208            // set new view position
2209            viewPort.setViewPosition(MathUtil.point2DToPoint(newViewPosition));
2210        } else {
2211            JScrollPane scrollPane = getPanelScrollPane();
2212            if (scrollPane != null) {
2213                if (scrollPane.getVerticalScrollBar().isVisible()) {
2214                    // Redispatch the event to the original MouseWheelListeners
2215                    for (MouseWheelListener mwl : mouseWheelListeners) {
2216                        mwl.mouseWheelMoved(event);
2217                    }
2218                } else {
2219                    // proprogate event to ancestor
2220                    Component ancestor = SwingUtilities.getAncestorOfClass(JScrollPane.class,
2221                            scrollPane);
2222                    if (ancestor != null) {
2223                        MouseWheelEvent mwe = new MouseWheelEvent(
2224                                ancestor,
2225                                event.getID(),
2226                                event.getWhen(),
2227                                event.getModifiersEx(),
2228                                event.getX(),
2229                                event.getY(),
2230                                event.getXOnScreen(),
2231                                event.getYOnScreen(),
2232                                event.getClickCount(),
2233                                event.isPopupTrigger(),
2234                                event.getScrollType(),
2235                                event.getScrollAmount(),
2236                                event.getWheelRotation());
2237
2238                        ancestor.dispatchEvent(mwe);
2239                    }
2240                }
2241            }
2242        }
2243    }
2244
2245    /**
2246     * Select the appropriate zoom menu item based on the zoomFactor.
2247     * @param zoomFactor eg. 0.5 ( 1/2 zoom ), 1.0 ( no zoom ), 2.0 ( 2x zoom )
2248     */
2249    private void selectZoomMenuItem(double zoomFactor) {
2250        double zoom = zoomFactor * 100;
2251
2252        // put zoomFactor on 100% increments
2253        int newZoomFactor = (int) MathUtil.granulize(zoom, 100);
2254        noZoomItem.setSelected(newZoomFactor == 100);
2255        zoom20Item.setSelected(newZoomFactor == 200);
2256        zoom30Item.setSelected(newZoomFactor == 300);
2257        zoom40Item.setSelected(newZoomFactor == 400);
2258        zoom50Item.setSelected(newZoomFactor == 500);
2259        zoom60Item.setSelected(newZoomFactor == 600);
2260        zoom70Item.setSelected(newZoomFactor == 700);
2261        zoom80Item.setSelected(newZoomFactor == 800);
2262
2263        // put zoomFactor on 50% increments
2264        newZoomFactor = (int) MathUtil.granulize(zoom, 50);
2265        zoom05Item.setSelected(newZoomFactor == 50);
2266        zoom15Item.setSelected(newZoomFactor == 150);
2267
2268        // put zoomFactor on 25% increments
2269        newZoomFactor = (int) MathUtil.granulize(zoom, 25);
2270        zoom025Item.setSelected(newZoomFactor == 25);
2271        zoom075Item.setSelected(newZoomFactor == 75);
2272    }
2273
2274    /**
2275     * Set panel Zoom factor.
2276     * @param zoomFactor the amount to scale, eg. 2.0 for 2x zoom.
2277     * @return the new scale amount (not necessarily the same as zoomFactor)
2278     */
2279    public double setZoom(double zoomFactor) {
2280        double newZoom = MathUtil.pin(zoomFactor, minZoom, maxZoom);
2281        selectZoomMenuItem(newZoom);
2282
2283        if (!MathUtil.equals(newZoom, getPaintScale())) {
2284            log.debug("zoom: {}", zoomFactor);
2285            // setPaintScale(newZoom);   //<<== don't call; messes up scrollbars
2286            _paintScale = newZoom;      // just set paint scale directly
2287            resetTargetSize();          // calculate new target panel size
2288            adjustScrollBars();         // and adjust the scrollbars ourselves
2289            // adjustClip();
2290
2291            leToolBarPanel.zoomLabel.setText(String.format(Locale.getDefault(), "x%1$,.2f", newZoom));
2292
2293            // save the window specific saved zoom user preference
2294            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent( prefsMgr ->
2295                prefsMgr.setProperty(getWindowFrameRef(), "zoom", zoomFactor));
2296        }
2297        return getPaintScale();
2298    }
2299
2300    /**
2301     * getZoom
2302     *
2303     * @return the zooming scale
2304     */
2305    public double getZoom() {
2306        return getPaintScale();
2307    }
2308
2309    /**
2310     * getMinZoom
2311     *
2312     * @return the minimum zoom scale
2313     */
2314    public double getMinZoom() {
2315        return minZoom;
2316    }
2317
2318    /**
2319     * getMaxZoom
2320     *
2321     * @return the maximum zoom scale
2322     */
2323    public double getMaxZoom() {
2324        return maxZoom;
2325    }
2326
2327    //
2328    // TODO: make this public? (might be useful!)
2329    //
2330    private Rectangle2D calculateMinimumLayoutBounds() {
2331        // calculate a union of the bounds of everything on the layout
2332        Rectangle2D result = new Rectangle2D.Double();
2333
2334        // combine all (onscreen) Components into a list of list of Components
2335        List<List<? extends Positionable>> listOfListsOfComponents = new ArrayList<>();
2336        listOfListsOfComponents.add(backgroundImage);
2337        listOfListsOfComponents.add(sensorImage);
2338        listOfListsOfComponents.add(turnoutImage);
2339        listOfListsOfComponents.add(signalHeadImage);
2340        listOfListsOfComponents.add(markerImage);
2341        listOfListsOfComponents.add(labelImage);
2342        listOfListsOfComponents.add(clocks);
2343        listOfListsOfComponents.add(multiSensors);
2344        listOfListsOfComponents.add(signalList);
2345        listOfListsOfComponents.add(memoryLabelList);
2346        listOfListsOfComponents.add(memoryInputList);
2347        listOfListsOfComponents.add(globalVariableLabelList);
2348        listOfListsOfComponents.add(blockContentsLabelList);
2349        listOfListsOfComponents.add(blockContentsInputList);
2350        listOfListsOfComponents.add(sensorList);
2351        listOfListsOfComponents.add(turnoutList);
2352        listOfListsOfComponents.add(signalMastList);
2353        listOfListsOfComponents.add(factoryPositionables);
2354        // combine their bounds
2355        for (List<? extends Positionable> listOfComponents : listOfListsOfComponents) {
2356            for (Positionable o : listOfComponents) {
2357                if (result.isEmpty()) {
2358                    result = o.getBounds();
2359                } else {
2360                    result = result.createUnion(o.getBounds());
2361                }
2362            }
2363        }
2364
2365        for (LayoutTrackView ov : getLayoutTrackViews()) {
2366            if (result.isEmpty()) {
2367                result = ov.getBounds();
2368            } else {
2369                result = result.createUnion(ov.getBounds());
2370            }
2371        }
2372
2373        for (LayoutShape o : layoutShapes) {
2374            if (result.isEmpty()) {
2375                result = o.getBounds();
2376            } else {
2377                result = result.createUnion(o.getBounds());
2378            }
2379        }
2380
2381        // put a grid size margin around it
2382        result = MathUtil.inset(result, gContext.getGridSize() * gContext.getGridSize2nd() / -2.0);
2383
2384        return result;
2385    }
2386
2387    /**
2388     * resize panel bounds
2389     *
2390     * @param forceFlag if false only grow bigger
2391     * @return the new (?) panel bounds
2392     */
2393    @Override
2394    public Rectangle2D resizePanelBounds(boolean forceFlag) {
2395        Rectangle2D panelBounds = getPanelBounds();
2396        Rectangle2D layoutBounds = calculateMinimumLayoutBounds();
2397
2398        // make sure it includes the origin
2399        layoutBounds.add(MathUtil.zeroPoint2D);
2400
2401        if (forceFlag) {
2402            panelBounds = layoutBounds;
2403        } else {
2404            panelBounds.add(layoutBounds);
2405        }
2406
2407        // don't let origin go negative
2408        panelBounds = panelBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
2409
2410        // log.info("resizePanelBounds: {}", MathUtil.rectangle2DToString(panelBounds));
2411        setPanelBounds(panelBounds);
2412
2413        return panelBounds;
2414    }
2415
2416    private double zoomToFit() {
2417        Rectangle2D layoutBounds = resizePanelBounds(true);
2418
2419        // calculate the bounds for the scroll pane
2420        JScrollPane scrollPane = getPanelScrollPane();
2421        Rectangle2D scrollBounds = scrollPane.getViewportBorderBounds();
2422
2423        // don't let origin go negative
2424        scrollBounds = scrollBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
2425
2426        // calculate the horzontial and vertical scales
2427        double scaleWidth = scrollPane.getWidth() / layoutBounds.getWidth();
2428        double scaleHeight = scrollPane.getHeight() / layoutBounds.getHeight();
2429
2430        // set the new zoom to the smallest of the two
2431        double result = setZoom(Math.min(scaleWidth, scaleHeight));
2432
2433        // set the new zoom (return value may be different)
2434        result = setZoom(result);
2435
2436        // calculate new scroll bounds
2437        scrollBounds = MathUtil.scale(layoutBounds, result);
2438
2439        // don't let origin go negative
2440        scrollBounds = scrollBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
2441
2442        // make sure it includes the origin
2443        scrollBounds.add(MathUtil.zeroPoint2D);
2444
2445        // and scroll to it
2446        scrollPane.scrollRectToVisible(MathUtil.rectangle2DToRectangle(scrollBounds));
2447
2448        return result;
2449    }
2450
2451    private Point2D windowCenter() {
2452        // Returns window's center coordinates converted to layout space
2453        // Used for initial setup of turntables and reporters
2454        return MathUtil.divide(MathUtil.center(getBounds()), getZoom());
2455    }
2456
2457    private void setupMarkerMenu(@Nonnull JMenuBar menuBar) {
2458        JMenu markerMenu = new JMenu(Bundle.getMessage("MenuMarker"));
2459
2460        markerMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuMarkerMnemonic")));
2461        menuBar.add(markerMenu);
2462        markerMenu.add(new AbstractAction(Bundle.getMessage("AddLoco") + "...") {
2463            @Override
2464            public void actionPerformed(ActionEvent event) {
2465                locoMarkerFromInput();
2466            }
2467        });
2468        markerMenu.add(new AbstractAction(Bundle.getMessage("AddLocoRoster") + "...") {
2469            @Override
2470            public void actionPerformed(ActionEvent event) {
2471                locoMarkerFromRoster();
2472            }
2473        });
2474        markerMenu.add(new AbstractAction(Bundle.getMessage("RemoveMarkers")) {
2475            @Override
2476            public void actionPerformed(ActionEvent event) {
2477                removeMarkers();
2478            }
2479        });
2480        InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent(prefsMgr -> {
2481            markerMenu.addSeparator();
2482            disableLocoMarkerPopupMenuItem = new JCheckBoxMenuItem(
2483                    new AbstractAction(Bundle.getMessage("DisableLocoMarkerPopup")) {
2484                        @Override
2485                        public void actionPerformed(ActionEvent e) {
2486                            enableDisableLocoMarkerPopups();
2487                        }
2488            });
2489            disableLocoMarkerPopupMenuItem.setSelected(isLocoMarkerPopupDisabled());
2490            markerMenu.add(disableLocoMarkerPopupMenuItem);
2491        });
2492    }
2493
2494    private void enableDisableLocoMarkerPopups() {
2495        if (disableLocoMarkerPopupMenuItem != null) {
2496            boolean selected = disableLocoMarkerPopupMenuItem.isSelected();
2497            setLocoMarkerPopupDisabled(selected);
2498        }
2499    }
2500
2501    private void setupDispatcherMenu(@Nonnull JMenuBar menuBar) {
2502        JMenu dispMenu = new JMenu(Bundle.getMessage("MenuDispatcher"));
2503
2504        dispMenu.setMnemonic(stringsToVTCodes.get(Bundle.getMessage("MenuDispatcherMnemonic")));
2505        dispMenu.add(new JMenuItem(new DispatcherAction(Bundle.getMessage("MenuItemOpen"))));
2506        menuBar.add(dispMenu);
2507        JMenuItem newTrainItem = new JMenuItem(Bundle.getMessage("MenuItemNewTrain"));
2508        dispMenu.add(newTrainItem);
2509        newTrainItem.addActionListener((ActionEvent event) -> {
2510            if (InstanceManager.getDefault(TransitManager.class).getNamedBeanSet().isEmpty()) {
2511                // Inform the user that there are no Transits available, and don't open the window
2512                JmriJOptionPane.showMessageDialog(
2513                        null,
2514                        ResourceBundle.getBundle("jmri.jmrit.dispatcher.DispatcherBundle").
2515                                getString("NoTransitsMessage"));
2516            } else {
2517                DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class
2518                );
2519                if (!df.getNewTrainActive()) {
2520                    df.getActiveTrainFrame().initiateTrain(event, null, null);
2521                    df.setNewTrainActive(true);
2522                } else {
2523                    df.getActiveTrainFrame().showActivateFrame(null);
2524                }
2525            }
2526        });
2527        menuBar.add(dispMenu);
2528    }
2529
2530    private boolean includedTurnoutSkipped = false;
2531
2532    public boolean isIncludedTurnoutSkipped() {
2533        return includedTurnoutSkipped;
2534    }
2535
2536    public void setIncludedTurnoutSkipped(Boolean boo) {
2537        includedTurnoutSkipped = boo;
2538    }
2539
2540    boolean openDispatcherOnLoad = false;
2541
2542    // TODO: Java standard pattern for boolean getters is "isOpenDispatcherOnLoad()"
2543    public boolean getOpenDispatcherOnLoad() {
2544        return openDispatcherOnLoad;
2545    }
2546
2547    public void setOpenDispatcherOnLoad(Boolean boo) {
2548        openDispatcherOnLoad = boo;
2549    }
2550
2551    /**
2552     * Remove marker icons from panel
2553     */
2554    @Override
2555    public void removeMarkers() {
2556        for (int i = markerImage.size(); i > 0; i--) {
2557            LocoIcon il = markerImage.get(i - 1);
2558
2559            if ((il != null) && (il.isActive())) {
2560                markerImage.remove(i - 1);
2561                il.remove();
2562                il.dispose();
2563                setDirty();
2564            }
2565        }
2566        super.removeMarkers();
2567        redrawPanel();
2568    }
2569
2570    /**
2571     * Assign the block from the toolbar to all selected layout tracks
2572     */
2573    private void assignBlockToSelection() {
2574        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
2575        if (newName == null) {
2576            newName = "";
2577        }
2578        LayoutBlock b = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(newName);
2579        _layoutTrackSelection.forEach((lt) -> lt.setAllLayoutBlocks(b));
2580    }
2581
2582    public boolean translateTrack(float xDel, float yDel) {
2583        Point2D delta = new Point2D.Double(xDel, yDel);
2584        getLayoutTrackViews().forEach((ltv) -> ltv.setCoordsCenter(MathUtil.add(ltv.getCoordsCenter(), delta)));
2585        resizePanelBounds(true);
2586        return true;
2587    }
2588
2589    /**
2590     * scale all LayoutTracks coordinates by the x and y factors.
2591     *
2592     * @param xFactor the amount to scale X coordinates.
2593     * @param yFactor the amount to scale Y coordinates.
2594     * @return true when complete.
2595     */
2596    public boolean scaleTrack(float xFactor, float yFactor) {
2597        getLayoutTrackViews().forEach((ltv) -> ltv.scaleCoords(xFactor, yFactor));
2598
2599        // update the overall scale factors
2600        gContext.setXScale(gContext.getXScale() * xFactor);
2601        gContext.setYScale(gContext.getYScale() * yFactor);
2602
2603        resizePanelBounds(true);
2604        return true;
2605    }
2606
2607    /**
2608     * loop through all LayoutBlocks and set colors to the default colors from
2609     * this LayoutEditor
2610     *
2611     * @return count of changed blocks
2612     */
2613    public int setAllTracksToDefaultColors() {
2614        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class
2615        );
2616        SortedSet<LayoutBlock> lBList = lbm.getNamedBeanSet();
2617        int changed = 0;
2618        for (LayoutBlock lb : lBList) {
2619            lb.setBlockTrackColor(this.getDefaultTrackColorColor());
2620            lb.setBlockOccupiedColor(this.getDefaultOccupiedTrackColorColor());
2621            lb.setBlockExtraColor(this.getDefaultAlternativeTrackColorColor());
2622            changed++;
2623        }
2624        log.info("Track Colors set to default values for {} layoutBlocks.", changed);
2625        return changed;
2626    }
2627
2628    private Rectangle2D undoRect;
2629    private boolean canUndoMoveSelection = false;
2630    private Point2D undoDelta = MathUtil.zeroPoint2D;
2631
2632    /**
2633     * Translate entire layout by x and y amounts.
2634     *
2635     * @param xTranslation horizontal (X) translation value
2636     * @param yTranslation vertical (Y) translation value
2637     */
2638    public void translate(float xTranslation, float yTranslation) {
2639        // here when all numbers read in - translation if entered
2640        if ((xTranslation != 0.0F) || (yTranslation != 0.0F)) {
2641            Point2D delta = new Point2D.Double(xTranslation, yTranslation);
2642            Rectangle2D selectionRect = getSelectionRect();
2643
2644            // set up undo information
2645            undoRect = MathUtil.offset(selectionRect, delta);
2646            undoDelta = MathUtil.subtract(MathUtil.zeroPoint2D, delta);
2647            canUndoMoveSelection = true;
2648            undoTranslateSelectionMenuItem.setEnabled(canUndoMoveSelection);
2649
2650            // apply translation to icon items within the selection
2651            for (Positionable c : _positionableSelection) {
2652                Point2D newPoint = MathUtil.add(c.getLocation(), delta);
2653                c.setLocation((int) newPoint.getX(), (int) newPoint.getY());
2654            }
2655
2656            for (LayoutTrack lt : _layoutTrackSelection) {
2657                LayoutTrackView ltv = getLayoutTrackView(lt);
2658                ltv.setCoordsCenter(MathUtil.add(ltv.getCoordsCenter(), delta));
2659            }
2660
2661            for (LayoutShape ls : _layoutShapeSelection) {
2662                ls.setCoordsCenter(MathUtil.add(ls.getCoordsCenter(), delta));
2663            }
2664
2665            selectionX = undoRect.getX();
2666            selectionY = undoRect.getY();
2667            selectionWidth = undoRect.getWidth();
2668            selectionHeight = undoRect.getHeight();
2669            resizePanelBounds(false);
2670            setDirty();
2671            redrawPanel();
2672        }
2673    }
2674
2675    /**
2676     * undo the move selection
2677     */
2678    void undoMoveSelection() {
2679        if (canUndoMoveSelection) {
2680            _positionableSelection.forEach((c) -> {
2681                Point2D newPoint = MathUtil.add(c.getLocation(), undoDelta);
2682                c.setLocation((int) newPoint.getX(), (int) newPoint.getY());
2683            });
2684
2685            _layoutTrackSelection.forEach(
2686                    (lt) -> {
2687                        LayoutTrackView ltv = getLayoutTrackView(lt);
2688                        ltv.setCoordsCenter(MathUtil.add(ltv.getCoordsCenter(), undoDelta));
2689                    }
2690            );
2691
2692            _layoutShapeSelection.forEach((ls) -> ls.setCoordsCenter(MathUtil.add(ls.getCoordsCenter(), undoDelta)));
2693
2694            undoRect = MathUtil.offset(undoRect, undoDelta);
2695            selectionX = undoRect.getX();
2696            selectionY = undoRect.getY();
2697            selectionWidth = undoRect.getWidth();
2698            selectionHeight = undoRect.getHeight();
2699
2700            resizePanelBounds(false);
2701            redrawPanel();
2702
2703            canUndoMoveSelection = false;
2704            undoTranslateSelectionMenuItem.setEnabled(canUndoMoveSelection);
2705        }
2706    }
2707
2708    /**
2709     * Rotate selection by 90 degrees clockwise.
2710     */
2711    public void rotateSelection90() {
2712        Rectangle2D bounds = getSelectionRect();
2713        Point2D center = MathUtil.midPoint(bounds);
2714
2715        for (Positionable positionable : _positionableSelection) {
2716            Rectangle2D cBounds = positionable.getBounds(new Rectangle());
2717            Point2D oldBottomLeft = new Point2D.Double(cBounds.getMinX(), cBounds.getMaxY());
2718            Point2D newTopLeft = MathUtil.rotateDEG(oldBottomLeft, center, 90);
2719            boolean rotateFlag = true;
2720            if (positionable instanceof PositionableLabel) {
2721                PositionableLabel positionableLabel = (PositionableLabel) positionable;
2722                if (positionableLabel.isBackground()) {
2723                    rotateFlag = false;
2724                }
2725            }
2726            if (rotateFlag) {
2727                positionable.rotate(positionable.getDegrees() + 90);
2728                positionable.setLocation((int) newTopLeft.getX(), (int) newTopLeft.getY());
2729            }
2730        }
2731
2732        for (LayoutTrack lt : _layoutTrackSelection) {
2733            LayoutTrackView ltv = getLayoutTrackView(lt);
2734            ltv.setCoordsCenter(MathUtil.rotateDEG(ltv.getCoordsCenter(), center, 90));
2735            ltv.rotateCoords(90);
2736        }
2737
2738        for (LayoutShape ls : _layoutShapeSelection) {
2739            ls.setCoordsCenter(MathUtil.rotateDEG(ls.getCoordsCenter(), center, 90));
2740            ls.rotateCoords(90);
2741        }
2742
2743        resizePanelBounds(true);
2744        setDirty();
2745        redrawPanel();
2746    }
2747
2748    /**
2749     * Rotate the entire layout by 90 degrees clockwise.
2750     */
2751    public void rotateLayout90() {
2752        List<Positionable> positionables = new ArrayList<>(getContents());
2753        positionables.addAll(backgroundImage);
2754        positionables.addAll(blockContentsLabelList);
2755        positionables.addAll(blockContentsInputList);
2756        positionables.addAll(labelImage);
2757        positionables.addAll(memoryLabelList);
2758        positionables.addAll(memoryInputList);
2759        positionables.addAll(globalVariableLabelList);
2760        positionables.addAll(sensorImage);
2761        positionables.addAll(turnoutImage);
2762        positionables.addAll(sensorList);
2763        positionables.addAll(turnoutList);
2764        positionables.addAll(signalHeadImage);
2765        positionables.addAll(signalList);
2766        positionables.addAll(signalMastList);
2767
2768        // do this to remove duplicates that may be in more than one list
2769        positionables = positionables.stream().distinct().collect(Collectors.toList());
2770
2771        Rectangle2D bounds = getPanelBounds();
2772        Point2D lowerLeft = new Point2D.Double(bounds.getMinX(), bounds.getMaxY());
2773
2774        for (Positionable positionable : positionables) {
2775            Rectangle2D cBounds = positionable.getBounds(new Rectangle());
2776            Point2D newTopLeft = MathUtil.subtract(MathUtil.rotateDEG(positionable.getLocation(), lowerLeft, 90), lowerLeft);
2777            boolean reLocateFlag = true;
2778            if (positionable instanceof PositionableLabel) {
2779                try {
2780                    PositionableLabel positionableLabel = (PositionableLabel) positionable;
2781                    if (positionableLabel.isBackground()) {
2782                        reLocateFlag = false;
2783                    }
2784                    positionableLabel.rotate(positionableLabel.getDegrees() + 90);
2785                } catch (NullPointerException ex) {
2786                    log.warn("previously-ignored NPE", ex);
2787                }
2788            }
2789            if (reLocateFlag) {
2790                try {
2791                    positionable.setLocation((int) (newTopLeft.getX() - cBounds.getHeight()), (int) newTopLeft.getY());
2792                } catch (NullPointerException ex) {
2793                    log.warn("previously-ignored NPE", ex);
2794                }
2795            }
2796        }
2797
2798        for (LayoutTrackView ltv : getLayoutTrackViews()) {
2799            try {
2800                Point2D newPoint = MathUtil.subtract(MathUtil.rotateDEG(ltv.getCoordsCenter(), lowerLeft, 90), lowerLeft);
2801                ltv.setCoordsCenter(newPoint);
2802                ltv.rotateCoords(90);
2803            } catch (NullPointerException ex) {
2804                log.warn("previously-ignored NPE", ex);
2805            }
2806        }
2807
2808        for (LayoutShape ls : layoutShapes) {
2809            Point2D newPoint = MathUtil.subtract(MathUtil.rotateDEG(ls.getCoordsCenter(), lowerLeft, 90), lowerLeft);
2810            ls.setCoordsCenter(newPoint);
2811            ls.rotateCoords(90);
2812        }
2813
2814        resizePanelBounds(true);
2815        setDirty();
2816        redrawPanel();
2817    }
2818
2819    /**
2820     * align the layout to grid
2821     */
2822    public void alignLayoutToGrid() {
2823        // align to grid
2824        List<Positionable> positionables = new ArrayList<>(getContents());
2825        positionables.addAll(backgroundImage);
2826        positionables.addAll(blockContentsLabelList);
2827        positionables.addAll(labelImage);
2828        positionables.addAll(memoryLabelList);
2829        positionables.addAll(memoryInputList);
2830        positionables.addAll(globalVariableLabelList);
2831        positionables.addAll(sensorImage);
2832        positionables.addAll(turnoutImage);
2833        positionables.addAll(sensorList);
2834        positionables.addAll(turnoutList);
2835        positionables.addAll(signalHeadImage);
2836        positionables.addAll(signalList);
2837        positionables.addAll(signalMastList);
2838
2839        // do this to remove duplicates that may be in more than one list
2840        positionables = positionables.stream().distinct().collect(Collectors.toList());
2841        alignToGrid(positionables, getLayoutTracks(), layoutShapes);
2842    }
2843
2844    /**
2845     * align selection to grid
2846     */
2847    public void alignSelectionToGrid() {
2848        alignToGrid(_positionableSelection, _layoutTrackSelection, _layoutShapeSelection);
2849    }
2850
2851    private void alignToGrid(List<Positionable> positionables, List<LayoutTrack> tracks, List<LayoutShape> shapes) {
2852        for (Positionable positionable : positionables) {
2853            Point2D newLocation = MathUtil.granulize(positionable.getLocation(), gContext.getGridSize());
2854            positionable.setLocation((int) (newLocation.getX()), (int) newLocation.getY());
2855        }
2856        for (LayoutTrack lt : tracks) {
2857            LayoutTrackView ltv = getLayoutTrackView(lt);
2858            ltv.setCoordsCenter(MathUtil.granulize(ltv.getCoordsCenter(), gContext.getGridSize()));
2859            if (lt instanceof LayoutTurntable) {
2860                LayoutTurntable tt = (LayoutTurntable) lt;
2861                LayoutTurntableView ttv = getLayoutTurntableView(tt);
2862                for (LayoutTurntable.RayTrack rt : tt.getRayTrackList()) {
2863                    int rayIndex = rt.getConnectionIndex();
2864                    ttv.setRayCoordsIndexed(MathUtil.granulize(ttv.getRayCoordsIndexed(rayIndex), gContext.getGridSize()), rayIndex);
2865                }
2866            }
2867//             if (lt instanceof LayoutTraverser) {
2868//                 Placeholder comment:
2869//                 Do nothing since slot connection points are relative to the traverser center point.
2870//             }
2871        }
2872        for (LayoutShape ls : shapes) {
2873            ls.setCoordsCenter(MathUtil.granulize(ls.getCoordsCenter(), gContext.getGridSize()));
2874            for (int idx = 0; idx < ls.getNumberPoints(); idx++) {
2875                ls.setPoint(idx, MathUtil.granulize(ls.getPoint(idx), gContext.getGridSize()));
2876            }
2877        }
2878
2879        resizePanelBounds(true);
2880        setDirty();
2881        redrawPanel();
2882    }
2883
2884    public void setCurrentPositionAndSize() {
2885        // save current panel location and size
2886        Dimension dim = getSize();
2887
2888        // Compute window size based on LayoutEditor size
2889        gContext.setWindowHeight(dim.height);
2890        gContext.setWindowWidth(dim.width);
2891
2892        // Compute layout size based on LayoutPane size
2893        dim = getTargetPanelSize();
2894        gContext.setLayoutWidth((int) (dim.width / getZoom()));
2895        gContext.setLayoutHeight((int) (dim.height / getZoom()));
2896        adjustScrollBars();
2897
2898        Point pt = getLocationOnScreen();
2899        gContext.setUpperLeftY(pt.x);
2900        gContext.setUpperLeftY(pt.y);
2901
2902        log.debug("setCurrentPositionAndSize Position - {},{} WindowSize - {},{} PanelSize - {},{}", gContext.getUpperLeftX(), gContext.getUpperLeftY(), gContext.getWindowWidth(), gContext.getWindowHeight(), gContext.getLayoutWidth(), gContext.getLayoutHeight());
2903        setDirty();
2904    }
2905
2906    private JRadioButtonMenuItem addButtonGroupMenuEntry(
2907            @Nonnull JMenu inMenu,
2908            ButtonGroup inButtonGroup,
2909            final String inName,
2910            boolean inSelected,
2911            ActionListener inActionListener) {
2912        JRadioButtonMenuItem result = new JRadioButtonMenuItem(inName);
2913        if (inActionListener != null) {
2914            result.addActionListener(inActionListener);
2915        }
2916        if (inButtonGroup != null) {
2917            inButtonGroup.add(result);
2918        }
2919        result.setSelected(inSelected);
2920
2921        inMenu.add(result);
2922
2923        return result;
2924    }
2925
2926    private void addTurnoutCircleSizeMenuEntry(
2927            @Nonnull JMenu inMenu,
2928            @Nonnull String inName,
2929            final int inSize) {
2930        ActionListener a = (ActionEvent event) -> {
2931            if (getTurnoutCircleSize() != inSize) {
2932                setTurnoutCircleSize(inSize);
2933                setDirty();
2934                redrawPanel();
2935            }
2936        };
2937        addButtonGroupMenuEntry(inMenu,
2938                turnoutCircleSizeButtonGroup, inName,
2939                getTurnoutCircleSize() == inSize, a);
2940    }
2941
2942    private void setOptionMenuTurnoutCircleSize() {
2943        String tcs = Integer.toString(getTurnoutCircleSize());
2944        Enumeration<AbstractButton> e = turnoutCircleSizeButtonGroup.getElements();
2945        while (e.hasMoreElements()) {
2946            AbstractButton button = e.nextElement();
2947            String buttonName = button.getText();
2948            button.setSelected(buttonName.equals(tcs));
2949        }
2950    }
2951
2952    @Override
2953    public void setScroll(int state) {
2954        if (isEditable()) {
2955            // In edit mode the scroll bars are always displayed, however we will want to set the scroll for when we exit edit mode
2956            super.setScroll(Editor.SCROLL_BOTH);
2957            _scrollState = state;
2958        } else {
2959            super.setScroll(state);
2960        }
2961    }
2962
2963    /**
2964     * The LE xml load uses the string version of setScroll which went directly to
2965     * Editor.  The string version has been added here so that LE can set the scroll
2966     * selection.
2967     * @param value The new scroll value.
2968     */
2969    @Override
2970    public void setScroll(String value) {
2971        if (value != null) super.setScroll(value);
2972        scrollNoneMenuItem.setSelected(_scrollState == Editor.SCROLL_NONE);
2973        scrollBothMenuItem.setSelected(_scrollState == Editor.SCROLL_BOTH);
2974        scrollHorizontalMenuItem.setSelected(_scrollState == Editor.SCROLL_HORIZONTAL);
2975        scrollVerticalMenuItem.setSelected(_scrollState == Editor.SCROLL_VERTICAL);
2976    }
2977
2978    /**
2979     * Add a layout turntable at location specified
2980     *
2981     * @param pt x,y placement for turntable
2982     */
2983    public void addTurntable(@Nonnull Point2D pt) {
2984        // get unique name
2985        String name = finder.uniqueName("TUR", ++numLayoutTurntables);
2986        LayoutTurntable lt = new LayoutTurntable(name, this);
2987        LayoutTurntableView ltv = new LayoutTurntableView(lt, pt, this);
2988
2989        addLayoutTrack(lt, ltv);
2990
2991        lt.addRay(0.0);
2992        lt.addRay(90.0);
2993        lt.addRay(180.0);
2994        lt.addRay(270.0);
2995
2996        if (leToolBarPanel != null) {
2997            lt.setMainline(leToolBarPanel.mainlineTrack.isSelected());
2998            // check on layout block
2999            String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
3000            if (newName == null) {
3001                newName = "";
3002            }
3003            LayoutBlock b = provideLayoutBlock(newName);
3004
3005            if (b != null) {
3006                lt.setLayoutBlock(b);
3007
3008                // check on occupancy sensor
3009                String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
3010                if (sensorName == null) {
3011                    sensorName = "";
3012                }
3013
3014                if (!sensorName.isEmpty()) {
3015                    if (!validateSensor(sensorName, b, this)) {
3016                        b.setOccupancySensorName("");
3017                    } else {
3018                        leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
3019                    }
3020                }
3021            }
3022        }
3023        setDirty();
3024
3025    }     /**
3026     * Add a layout traverser at location specified
3027     *
3028     * @param pt x,y placement for traverser
3029     */
3030    public void addTraverser(@Nonnull Point2D pt) {
3031        // get unique name
3032        String name = finder.uniqueName("TRV", ++numLayoutTraversers);
3033        LayoutTraverser lt = new LayoutTraverser(name, this);
3034        LayoutTraverserView ltv = new LayoutTraverserView(lt, pt, this);
3035        addLayoutTrack(lt, ltv);
3036        // Initialise with a couple of tracks
3037        lt.addSlotPair();
3038        lt.addSlotPair();
3039
3040        if (leToolBarPanel != null) {
3041            lt.setMainline(leToolBarPanel.mainlineTrack.isSelected());
3042            // check on layout block
3043            String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
3044            if (newName == null) {
3045                newName = "";
3046            }
3047            LayoutBlock b = provideLayoutBlock(newName);
3048
3049            if (b != null) {
3050                lt.setLayoutBlock(b);
3051
3052                // check on occupancy sensor
3053                String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
3054                if (sensorName == null) {
3055                    sensorName = "";
3056                }
3057
3058                if (!sensorName.isEmpty()) {
3059                    if (!validateSensor(sensorName, b, this)) {
3060                        b.setOccupancySensorName("");
3061                    } else {
3062                        leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
3063                    }
3064                }
3065            }
3066        }
3067        setDirty();
3068    }
3069
3070    /**
3071     * Allow external trigger of re-drawHidden
3072     */
3073    @Override
3074    public void redrawPanel() {
3075        repaint();
3076    }
3077
3078    /**
3079     * Allow external set/reset of awaitingIconChange
3080     */
3081    public void setAwaitingIconChange() {
3082        awaitingIconChange = true;
3083    }
3084
3085    public void resetAwaitingIconChange() {
3086        awaitingIconChange = false;
3087    }
3088
3089    /**
3090     * Allow external reset of dirty bit
3091     */
3092    public void resetDirty() {
3093        setDirty(false);
3094        savedEditMode = isEditable();
3095        savedPositionable = allPositionable();
3096        savedControlLayout = allControlling();
3097        savedAnimatingLayout = isAnimating();
3098        savedShowHelpBar = getShowHelpBar();
3099    }
3100
3101    /**
3102     * Allow external set of dirty bit
3103     *
3104     * @param val true/false for panelChanged
3105     */
3106    public void setDirty(boolean val) {
3107        panelChanged = val;
3108    }
3109
3110    @Override
3111    public void setDirty() {
3112        setDirty(true);
3113    }
3114
3115    /**
3116     * Check the dirty state.
3117     *
3118     * @return true if panel has changed
3119     */
3120    @Override
3121    public boolean isDirty() {
3122        return panelChanged;
3123    }
3124
3125    /*
3126    * Get mouse coordinates and adjust for zoom.
3127    * <p>
3128    * Side effects on xLoc, yLoc and dLoc
3129     */
3130    @Nonnull
3131    private Point2D calcLocation(JmriMouseEvent event, int dX, int dY) {
3132        xLoc = (int) ((event.getX() + dX) / getZoom());
3133        yLoc = (int) ((event.getY() + dY) / getZoom());
3134        dLoc = new Point2D.Double(xLoc, yLoc);
3135        return dLoc;
3136    }
3137
3138    private Point2D calcLocation(JmriMouseEvent event) {
3139        return calcLocation(event, 0, 0);
3140    }
3141
3142    /**
3143     * Check for highlighting of cursor position.
3144     *
3145     * If in "highlight" mode, draw a square at the location of the
3146     * event. If there was already a square, just move its location.
3147     * In either case, redraw the panel so the previous square will
3148     * disappear and the new one will appear immediately.
3149     */
3150    private void checkHighlightCursor() {
3151        if (!isEditable() && highlightCursor) {
3152            // rectangle size based on turnout circle size: rectangle should
3153            // be bigger so it can more easily surround turnout on screen
3154            int halfSize = (int)(circleRadius) + 8;
3155            if (_highlightcomponent == null) {
3156                _highlightcomponent = new Rectangle(
3157                        xLoc - halfSize, yLoc - halfSize, halfSize * 2, halfSize * 2);
3158            } else {
3159                _highlightcomponent.setLocation(xLoc - halfSize, yLoc - halfSize);
3160            }
3161            redrawPanel();
3162        }
3163    }
3164
3165    /**
3166     * Check whether an input icon text field is or is becoming active.
3167     * This is based on:
3168     * - The event component is an input icon instance.
3169     * - The mouse event indicates a plain button press.
3170     * @param event The mouse event.
3171     * @return true when active.
3172     */
3173    private boolean isInputTextBox(JmriMouseEvent event) {
3174        if (!(event.getComponent() instanceof PositionableJPanel)) {
3175            return false;
3176        }
3177
3178        if (event.isAltDown() ||
3179                event.isControlDown() ||
3180                event.isMetaDown() ||
3181                event.isPopupTrigger() ||
3182                event.isShiftDown()) {
3183            return false;
3184        }
3185
3186        return true;
3187    }
3188
3189    /**
3190     * Handle a mouse pressed event
3191     * <p>
3192     * Side-effects on _anchorX, _anchorY,_lastX, _lastY, xLoc, yLoc, dLoc,
3193     * selectionActive, xLabel, yLabel
3194     *
3195     * @param event the JmriMouseEvent
3196     */
3197    @Override
3198    public void mousePressed(JmriMouseEvent event) {
3199        if (isInputTextBox(event)) {
3200            return;
3201        }
3202
3203        // initialize cursor position
3204        _anchorX = xLoc;
3205        _anchorY = yLoc;
3206        _lastX = _anchorX;
3207        _lastY = _anchorY;
3208        calcLocation(event);
3209
3210        checkHighlightCursor();
3211
3212        // TODO: Add command-click on nothing to pan view?
3213        if (isEditable()) {
3214            boolean prevSelectionActive = selectionActive;
3215            selectionActive = false;
3216            leToolBarPanel.setLocationText(dLoc);
3217
3218            if (event.isPopupTrigger()) {
3219                if (event.isMetaDown() || event.isAltDown()) {
3220                    // if requesting a popup and it might conflict with moving, delay the request to mouseReleased
3221                    delayedPopupTrigger = true;
3222                } else {
3223                    // no possible conflict with moving, display the popup now
3224                    showEditPopUps(event);
3225                }
3226            }
3227
3228            if (event.isMetaDown() || event.isAltDown()) {
3229                // If dragging an item, identify the item for mouseDragging
3230                selectedObject = null;
3231                selectedHitPointType = HitPointType.NONE;
3232
3233                if (findLayoutTracksHitPoint(dLoc)) {
3234                    selectedObject = foundTrack;
3235                    selectedHitPointType = foundHitPointType;
3236                    startDelta = MathUtil.subtract(foundLocation, dLoc);
3237                    foundTrack = null;
3238                    foundTrackView = null;
3239                } else {
3240                    // Track not hit, find any non LAYOUT_POS_LABEL objects.
3241                    CheckLabel: {
3242                        selectedObject = checkMarkerPopUps(dLoc);
3243                        if (selectedObject != null) {
3244                            selectedHitPointType = HitPointType.MARKER;
3245                            startDelta = MathUtil.subtract(((LocoIcon) selectedObject).getLocation(), dLoc);
3246                            break CheckLabel;
3247                        }
3248
3249                        selectedObject = checkClockPopUps(dLoc);
3250                        if (selectedObject != null) {
3251                            selectedHitPointType = HitPointType.LAYOUT_POS_JCOMP;
3252                            startDelta = MathUtil.subtract(((PositionableJComponent) selectedObject).getLocation(), dLoc);
3253                            break CheckLabel;
3254                        }
3255
3256                        selectedObject = checkMultiSensorPopUps(dLoc);
3257                        if (selectedObject != null) {
3258                            selectedHitPointType = HitPointType.MULTI_SENSOR;
3259                            startDelta = MathUtil.subtract(((MultiSensorIcon) selectedObject).getLocation(), dLoc);
3260                            break CheckLabel;
3261                        }
3262
3263                        selectedObject = checkJPanelPopUps(dLoc);
3264                        if (selectedObject != null) {
3265                            selectedHitPointType = HitPointType.LAYOUT_POS_JPNL;
3266                            startDelta = MathUtil.subtract(((PositionableJPanel) selectedObject).getLocation(), dLoc);
3267                        }
3268                    } // End CheckLabel
3269
3270                    if (selectedObject == null) {
3271                        // Specific objects were not found.
3272                        // The next group are potential LAYOUT_POS_LABEL objects.
3273                        selectedObject = checkSensorIconPopUps(dLoc);
3274                        if (selectedObject == null) {
3275                            selectedObject = checkTurnoutIconPopUps(dLoc);
3276                            if (selectedObject == null) {
3277                                selectedObject = checkSignalHeadIconPopUps(dLoc);
3278                                if (selectedObject == null) {
3279                                    selectedObject = checkLabelImagePopUps(dLoc);
3280                                    if (selectedObject == null) {
3281                                        selectedObject = checkSignalMastIconPopUps(dLoc);
3282                                    }
3283                                }
3284                            }
3285                        }
3286
3287                        // Background objects (level 0 and level 1) are deferred until after the shape objects.
3288                        if (selectedObject != null && !((PositionableLabel) selectedObject).isBackground()) {
3289                            selectedHitPointType = HitPointType.LAYOUT_POS_LABEL;
3290                            startDelta = MathUtil.subtract(((PositionableLabel) selectedObject).getLocation(), dLoc);
3291                            if (selectedObject instanceof MemoryIcon) {
3292                                MemoryIcon pm = (MemoryIcon) selectedObject;
3293
3294                                if (pm.getPopupUtility().getFixedWidth() == 0) {
3295                                    startDelta = new Point2D.Double((pm.getOriginalX() - dLoc.getX()),
3296                                            (pm.getOriginalY() - dLoc.getY()));
3297                                }
3298                            }
3299
3300                            if (selectedObject instanceof GlobalVariableIcon) {
3301                                GlobalVariableIcon pm = (GlobalVariableIcon) selectedObject;
3302
3303                                if (pm.getPopupUtility().getFixedWidth() == 0) {
3304                                    startDelta = new Point2D.Double((pm.getOriginalX() - dLoc.getX()),
3305                                            (pm.getOriginalY() - dLoc.getY()));
3306                                }
3307                            }
3308
3309
3310                        } else {
3311                            // Still nothing found, look for shape objects and then background objects.
3312                            var dragShape = false;
3313
3314                            ListIterator<LayoutShape> listIterator = layoutShapes.listIterator(layoutShapes.size());
3315                            // hit test in front to back order (reverse order of list)
3316                            while (listIterator.hasPrevious()) {
3317                                LayoutShape ls = listIterator.previous();
3318                                selectedHitPointType = ls.findHitPointType(dLoc, true);
3319                                if (LayoutShape.isShapeHitPointType(selectedHitPointType)) {
3320                                    // log.warn("drag selectedObject: ", lt);
3321                                    selectedObject = ls;    // found one!
3322                                    beginLocation = dLoc;
3323                                    currentLocation = beginLocation;
3324                                    startDelta = MathUtil.zeroPoint2D;
3325                                    dragShape = true;
3326                                    break;
3327                                }
3328                            }
3329
3330                            if (!dragShape) {
3331                                // Finally, check for background objects.
3332                                selectedObject = checkBackgroundPopUps(dLoc);
3333
3334                                if (selectedObject != null) {
3335                                    selectedHitPointType = HitPointType.LAYOUT_POS_LABEL;
3336                                    startDelta = MathUtil.subtract(((PositionableLabel) selectedObject).getLocation(), dLoc);
3337                                }
3338                            }
3339                        }
3340                    }
3341                }
3342            } else if (event.isShiftDown() && leToolBarPanel.trackButton.isSelected() && !event.isPopupTrigger()) {
3343                // starting a Track Segment, check for free connection point
3344                selectedObject = null;
3345
3346                if (findLayoutTracksHitPoint(dLoc, true)) {
3347                    // match to a free connection point
3348                    beginTrack = foundTrack;
3349                    beginHitPointType = foundHitPointType;
3350                    beginLocation = foundLocation;
3351                    // BUGFIX: prevents initial drawTrackSegmentInProgress to {0, 0}
3352                    currentLocation = beginLocation;
3353                } else {
3354                    // TODO: auto-add anchor point?
3355                    beginTrack = null;
3356                }
3357            } else if (event.isShiftDown() && leToolBarPanel.shapeButton.isSelected() && !event.isPopupTrigger()) {
3358                // adding or extending a shape
3359                selectedObject = null;  // assume we're adding...
3360                for (LayoutShape ls : layoutShapes) {
3361                    selectedHitPointType = ls.findHitPointType(dLoc, true);
3362                    if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) {
3363                        // log.warn("extend selectedObject: ", lt);
3364                        selectedObject = ls;    // nope, we're extending
3365                        beginLocation = dLoc;
3366                        currentLocation = beginLocation;
3367                        break;
3368                    }
3369                }
3370            } else if (!event.isShiftDown() && !event.isControlDown() && !event.isPopupTrigger()) {
3371                // check if controlling a turnout in edit mode
3372                selectedObject = null;
3373
3374                if (allControlling()) {
3375                    checkControls(false);
3376                }
3377                // initialize starting selection - cancel any previous selection rectangle
3378                selectionActive = true;
3379                selectionX = dLoc.getX();
3380                selectionY = dLoc.getY();
3381                selectionWidth = 0.0;
3382                selectionHeight = 0.0;
3383            }
3384
3385            if (prevSelectionActive) {
3386                redrawPanel();
3387            }
3388
3389        } else if (allControlling()
3390                && !event.isMetaDown() && !event.isPopupTrigger()
3391                && !event.isAltDown() && !event.isShiftDown() && !event.isControlDown()) {
3392            // not in edit mode - check if mouse is on a turnout (using wider search range)
3393            selectedObject = null;
3394            checkControls(true);
3395
3396        } else if ((event.isMetaDown() || event.isAltDown())
3397                && !event.isShiftDown() && !event.isControlDown()) {
3398            // Windows and Linux have meta down on right button press. This prevents isPopTrigger
3399            // reaching the next else-if.
3400
3401            // not in edit mode - check if moving a marker if there are any.  This applies to Windows, Linux and macOS.
3402            selectedObject = checkMarkerPopUps(dLoc);
3403            if (selectedObject != null) {
3404                selectedHitPointType = HitPointType.MARKER;
3405                startDelta = MathUtil.subtract(((LocoIcon) selectedObject).getLocation(), dLoc);
3406                log.debug("mousePressed: ++ MAC/Windows/Linux marker move request");
3407                if (SystemType.isLinux()) {
3408                    // Prepare for a marker popup if the marker move does not occur before mouseReleased.
3409                    // This is only needed for Linux.  Windows handles this in mouseClicked.
3410                    delayedPopupTrigger = true;
3411                    log.debug("mousePressed: ++ Linux marker popup delay");
3412                }
3413            }
3414            if (selectedObject == null) {
3415                selectedObject = checkBlockContentsPopUps(dLoc);
3416                if (selectedObject != null) {
3417                    selectedHitPointType = HitPointType.BLOCKCONTENTSICON;
3418                }
3419            }
3420
3421            // not in edit mode - check if a signal mast popup menu is being requested using Windows or Linux.
3422            var sm = checkSignalMastIconPopUps(dLoc);
3423            if (sm != null) {
3424                delayedPopupTrigger = true;
3425                log.debug("mousePressed: ++ Window/Linux mast popup delay");
3426             }
3427            if (selectedObject == null) {
3428                    selectedObject = checkBlockContentsPopUps(dLoc);
3429                    if (selectedObject != null) {
3430                        selectedHitPointType = HitPointType.BLOCKCONTENTSICON;
3431                    }
3432                }
3433
3434        } else if (event.isPopupTrigger() && !event.isShiftDown()) {
3435
3436            // not in edit mode - check if a marker popup menu is being requested using macOS.
3437            var lo = checkMarkerPopUps(dLoc);
3438            if (lo != null) {
3439                delayedPopupTrigger = true;
3440                log.debug("mousePressed: ++ MAC marker popup delay");
3441            }
3442
3443            // not in edit mode - check if a signal mast popup menu is being requested using macOS.
3444            var sm = checkSignalMastIconPopUps(dLoc);
3445            if (sm != null) {
3446                delayedPopupTrigger = true;
3447                log.debug("mousePressed: ++ MAC mast popup delay");
3448             }
3449
3450        }
3451
3452        if (!event.isPopupTrigger()) {
3453            List<Positionable> selections = getSelectedItems(event);
3454
3455            if (!selections.isEmpty()) {
3456                selections.get(0).doMousePressed(event);
3457            }
3458        }
3459
3460        requestFocusInWindow();
3461
3462    }   // mousePressed
3463
3464// this is a method to iterate over a list of lists of items
3465// calling the predicate tester.test on each one
3466// all matching items are then added to the resulting List
3467// note: currently unused; commented out to avoid findbugs warning
3468// private static List testEachItemInListOfLists(
3469//        @Nonnull List<List> listOfListsOfObjects,
3470//        @Nonnull Predicate<Object> tester) {
3471//    List result = new ArrayList<>();
3472//    for (List<Object> listOfObjects : listOfListsOfObjects) {
3473//        List<Object> l = listOfObjects.stream().filter(o -> tester.test(o)).collect(Collectors.toList());
3474//        result.addAll(l);
3475//    }
3476//    return result;
3477//}
3478// this is a method to iterate over a list of lists of items
3479// calling the predicate tester.test on each one
3480// and return the first one that matches
3481// TODO: make this public? (it is useful! ;-)
3482// note: currently unused; commented out to avoid findbugs warning
3483// private static Object findFirstMatchingItemInListOfLists(
3484//        @Nonnull List<List> listOfListsOfObjects,
3485//        @Nonnull Predicate<Object> tester) {
3486//    Object result = null;
3487//    for (List listOfObjects : listOfListsOfObjects) {
3488//        Optional<Object> opt = listOfObjects.stream().filter(o -> tester.test(o)).findFirst();
3489//        if (opt.isPresent()) {
3490//            result = opt.get();
3491//            break;
3492//        }
3493//    }
3494//    return result;
3495//}
3496    /**
3497     * Called by {@link #mousePressed} to determine if the mouse click was in a
3498     * turnout control location. If so, update selectedHitPointType and
3499     * selectedObject for use by {@link #mouseReleased}.
3500     * <p>
3501     * If there's no match, selectedObject is set to null and
3502     * selectedHitPointType is left referring to the results of the checking the
3503     * last track on the list.
3504     * <p>
3505     * Refers to the current value of {@link #getLayoutTracks()} and
3506     * {@link #dLoc}.
3507     *
3508     * @param useRectangles set true to use rectangle; false for circles.
3509     */
3510    private void checkControls(boolean useRectangles) {
3511        selectedObject = null;  // deliberate side-effect
3512        for (LayoutTrackView theTrackView : getLayoutTrackViews()) {
3513            selectedHitPointType = theTrackView.findHitPointType(dLoc, useRectangles); // deliberate side-effect
3514            if (HitPointType.isControlHitType(selectedHitPointType)) {
3515                selectedObject = theTrackView.getLayoutTrack(); // deliberate side-effect
3516                return;
3517            }
3518        }
3519    }
3520
3521    // This is a geometric search, and should be done with views.
3522    // Hence this form is inevitably temporary.
3523    //
3524    private boolean findLayoutTracksHitPoint(
3525            @Nonnull Point2D loc, boolean requireUnconnected) {
3526        return findLayoutTracksHitPoint(loc, requireUnconnected, null);
3527    }
3528
3529    // This is a geometric search, and should be done with views.
3530    // Hence this form is inevitably temporary.
3531    //
3532    // optional parameter requireUnconnected
3533    private boolean findLayoutTracksHitPoint(@Nonnull Point2D loc) {
3534        return findLayoutTracksHitPoint(loc, false, null);
3535    }
3536
3537    /**
3538     * Internal (private) method to find the track closest to a point, with some
3539     * modifiers to the search. The {@link #foundTrack} and
3540     * {@link #foundHitPointType} members are set from the search.
3541     * <p>
3542     * This is a geometric search, and should be done with views. Hence this
3543     * form is inevitably temporary.
3544     *
3545     * @param loc                Point to search from
3546     * @param requireUnconnected forwarded to {@link #getLayoutTrackView}; if
3547     *                           true, return only free connections
3548     * @param avoid              Don't return this track, keep searching. Note
3549     *                           that {@Link #selectedObject} is also always
3550     *                           avoided automatically
3551     * @returns true if values of {@link #foundTrack} and
3552     * {@link #foundHitPointType} correct; note they may have changed even if
3553     * false is returned.
3554     */
3555    private boolean findLayoutTracksHitPoint(@Nonnull Point2D loc,
3556            boolean requireUnconnected, @CheckForNull LayoutTrack avoid) {
3557        boolean result = false; // assume failure (pessimist!)
3558
3559        foundTrack = null;
3560        foundTrackView = null;
3561        foundHitPointType = HitPointType.NONE;
3562
3563        Optional<LayoutTrack> opt = getLayoutTracks().stream().filter(layoutTrack -> {  // != means can't (yet) loop over Views
3564            if ((layoutTrack != avoid) && (layoutTrack != selectedObject)) {
3565                foundHitPointType = getLayoutTrackView(layoutTrack).findHitPointType(loc, false, requireUnconnected);
3566            }
3567            return (HitPointType.NONE != foundHitPointType);
3568        }).findFirst();
3569
3570        LayoutTrack layoutTrack = null;
3571        if (opt.isPresent()) {
3572            layoutTrack = opt.get();
3573        }
3574
3575        if (layoutTrack != null) {
3576            foundTrack = layoutTrack;
3577            foundTrackView = this.getLayoutTrackView(layoutTrack);
3578
3579            // get screen coordinates
3580            foundLocation = foundTrackView.getCoordsForConnectionType(foundHitPointType);
3581            /// foundNeedsConnect = isDisconnected(foundHitPointType);
3582            result = true;
3583        }
3584        return result;
3585    }
3586
3587    private TrackSegment checkTrackSegmentPopUps(@Nonnull Point2D loc) {
3588        assert loc != null;
3589
3590        TrackSegment result = null;
3591
3592        // NOTE: Rather than calculate all the hit rectangles for all
3593        // the points below and test if this location is in any of those
3594        // rectangles just create a hit rectangle for the location and
3595        // see if any of the points below are in it instead...
3596        Rectangle2D r = layoutEditorControlCircleRectAt(loc);
3597
3598        // check Track Segments, if any
3599        for (TrackSegmentView tsv : getTrackSegmentViews()) {
3600            if (r.contains(tsv.getCentreSeg())) {
3601                result = tsv.getTrackSegment();
3602                break;
3603            }
3604        }
3605        return result;
3606    }
3607
3608    private PositionableLabel checkBackgroundPopUps(@Nonnull Point2D loc) {
3609        assert loc != null;
3610
3611        PositionableLabel result = null;
3612        // check background images, if any
3613        for (int i = backgroundImage.size() - 1; i >= 0; i--) {
3614            PositionableLabel b = backgroundImage.get(i);
3615            Rectangle2D r = b.getBounds();
3616            if (r.contains(loc)) {
3617                result = b;
3618                break;
3619            }
3620        }
3621        return result;
3622    }
3623
3624    private SensorIcon checkSensorIconPopUps(@Nonnull Point2D loc) {
3625        assert loc != null;
3626
3627        SensorIcon result = null;
3628        // check sensor images, if any
3629        for (int i = sensorImage.size() - 1; i >= 0; i--) {
3630            SensorIcon s = sensorImage.get(i);
3631            Rectangle2D r = s.getBounds();
3632            if (r.contains(loc)) {
3633                result = s;
3634            }
3635        }
3636        return result;
3637    }
3638
3639    private TurnoutIcon checkTurnoutIconPopUps(@Nonnull Point2D loc) {
3640        assert loc != null;
3641
3642        TurnoutIcon result = null;
3643        // check turnout images, if any
3644        for (int i = turnoutImage.size() - 1; i >= 0; i--) {
3645            TurnoutIcon s = turnoutImage.get(i);
3646            Rectangle2D r = s.getBounds();
3647            if (r.contains(loc)) {
3648                result = s;
3649            }
3650        }
3651        return result;
3652    }
3653
3654    private SignalHeadIcon checkSignalHeadIconPopUps(@Nonnull Point2D loc) {
3655        assert loc != null;
3656
3657        SignalHeadIcon result = null;
3658        // check signal head images, if any
3659        for (int i = signalHeadImage.size() - 1; i >= 0; i--) {
3660            SignalHeadIcon s = signalHeadImage.get(i);
3661            Rectangle2D r = s.getBounds();
3662            if (r.contains(loc)) {
3663                result = s;
3664                break;
3665            }
3666        }
3667        return result;
3668    }
3669
3670    private SignalMastIcon checkSignalMastIconPopUps(@Nonnull Point2D loc) {
3671        assert loc != null;
3672
3673        SignalMastIcon result = null;
3674        // check signal head images, if any
3675        for (int i = signalMastList.size() - 1; i >= 0; i--) {
3676            SignalMastIcon s = signalMastList.get(i);
3677            Rectangle2D r = s.getBounds();
3678            if (r.contains(loc)) {
3679                result = s;
3680                break;
3681            }
3682        }
3683        return result;
3684    }
3685
3686    private PositionableLabel checkLabelImagePopUps(@Nonnull Point2D loc) {
3687        assert loc != null;
3688
3689        PositionableLabel result = null;
3690        int level = 0;
3691
3692        for (int i = labelImage.size() - 1; i >= 0; i--) {
3693            PositionableLabel s = labelImage.get(i);
3694            double x = s.getX();
3695            double y = s.getY();
3696            double w = 10.0;
3697            double h = 5.0;
3698
3699            if (s.isIcon() || s.isRotated() || s.getPopupUtility().getOrientation() != PositionablePopupUtil.HORIZONTAL) {
3700                w = s.maxWidth();
3701                h = s.maxHeight();
3702            } else if (s.isText()) {
3703                h = s.getFont().getSize();
3704                w = (h * 2 * (s.getText().length())) / 3;
3705            }
3706
3707            Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
3708            if (r.contains(loc)) {
3709                if (s.getDisplayLevel() >= level) {
3710                    // Check to make sure that we are returning the highest level label.
3711                    result = s;
3712                    level = s.getDisplayLevel();
3713                }
3714            }
3715        }
3716        return result;
3717    }
3718
3719    private PositionableJPanel checkJPanelPopUps(@Nonnull Point2D loc) {
3720        assert loc != null;
3721
3722        PositionableJPanel result = null;
3723        int level = 0;
3724
3725        for (int i = memoryInputList.size() - 1; i >= 0; i--) {
3726            PositionableJPanel s = memoryInputList.get(i);
3727            double x = s.getX();
3728            double y = s.getY();
3729            double w = s.getWidth();
3730            double h = s.getHeight();
3731
3732            Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
3733
3734            if (r.contains(loc)) {
3735                if (s.getDisplayLevel() >= level) {
3736                    // Check to make sure that we are returning the highest level label.
3737                    result = s;
3738                    level = s.getDisplayLevel();
3739                }
3740            }
3741        }
3742
3743        if (result == null) {
3744            for (int i = blockContentsInputList.size() - 1; i >= 0; i--) {
3745                PositionableJPanel s = blockContentsInputList.get(i);
3746                double x = s.getX();
3747                double y = s.getY();
3748                double w = s.getWidth();
3749                double h = s.getHeight();
3750
3751                Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
3752
3753                if (r.contains(loc)) {
3754                    if (s.getDisplayLevel() >= level) {
3755                        // Check to make sure that we are returning the highest level label.
3756                        result = s;
3757                        level = s.getDisplayLevel();
3758                    }
3759                }
3760            }
3761        }
3762
3763        return result;
3764    }
3765
3766    private AnalogClock2Display checkClockPopUps(@Nonnull Point2D loc) {
3767        assert loc != null;
3768
3769        AnalogClock2Display result = null;
3770        // check clocks, if any
3771        for (int i = clocks.size() - 1; i >= 0; i--) {
3772            AnalogClock2Display s = clocks.get(i);
3773            Rectangle2D r = s.getBounds();
3774            if (r.contains(loc)) {
3775                result = s;
3776                break;
3777            }
3778        }
3779        return result;
3780    }
3781
3782    private MultiSensorIcon checkMultiSensorPopUps(@Nonnull Point2D loc) {
3783        assert loc != null;
3784
3785        MultiSensorIcon result = null;
3786        // check multi sensor icons, if any
3787        for (int i = multiSensors.size() - 1; i >= 0; i--) {
3788            MultiSensorIcon s = multiSensors.get(i);
3789            Rectangle2D r = s.getBounds();
3790            if (r.contains(loc)) {
3791                result = s;
3792                break;
3793            }
3794        }
3795        return result;
3796    }
3797
3798    private LocoIcon checkMarkerPopUps(@Nonnull Point2D loc) {
3799        assert loc != null;
3800
3801        LocoIcon result = null;
3802        // check marker icons, if any
3803        for (int i = markerImage.size() - 1; i >= 0; i--) {
3804            LocoIcon l = markerImage.get(i);
3805            Rectangle2D r = l.getBounds();
3806            if (r.contains(loc)) {
3807                // mouse was pressed in marker icon
3808                result = l;
3809                break;
3810            }
3811        }
3812        return result;
3813    }
3814
3815    private BlockContentsIcon checkBlockContentsPopUps(@Nonnull Point2D loc) {
3816        assert loc != null;
3817
3818        BlockContentsIcon result = null;
3819        // check marker icons, if any
3820        for (int i = blockContentsLabelList.size() - 1; i >= 0; i--) {
3821            BlockContentsIcon l = blockContentsLabelList.get(i);
3822            Rectangle2D r = l.getBounds();
3823            if (r.contains(loc)) {
3824                // mouse was pressed in marker icon
3825                result = l;
3826                break;
3827            }
3828        }
3829        return result;
3830    }
3831
3832    private LayoutShape checkLayoutShapePopUps(@Nonnull Point2D loc) {
3833        assert loc != null;
3834
3835        LayoutShape result = null;
3836        for (LayoutShape ls : layoutShapes) {
3837            selectedHitPointType = ls.findHitPointType(loc, true);
3838            if (LayoutShape.isShapeHitPointType(selectedHitPointType)) {
3839                result = ls;
3840                break;
3841            }
3842        }
3843        return result;
3844    }
3845
3846    private Positionable checkPositionablePopUps(@Nonnull Point2D loc) {
3847        assert loc != null;
3848
3849        Positionable result = null;
3850        // check factory generated positionables, if any
3851        for (int i = factoryPositionables.size() - 1; i >= 0; i--) {
3852            Positionable s = factoryPositionables.get(i);
3853            Rectangle2D r = s.getBounds();
3854            if (r.contains(loc)) {
3855                result = s;
3856                break;
3857            }
3858        }
3859        return result;
3860    }
3861
3862    /**
3863     * Get the coordinates for the connection type of the specified LayoutTrack
3864     * or subtype.
3865     * <p>
3866     * This uses the current LayoutEditor object to map a LayoutTrack (no
3867     * coordinates) object to _a_ specific LayoutTrackView object in the current
3868     * LayoutEditor i.e. window. This allows the same model object in two
3869     * windows, but not twice in a single window.
3870     * <p>
3871     * This is temporary, and needs to go away as the LayoutTrack doesn't
3872     * logically have position; just the LayoutTrackView does, and multiple
3873     * LayoutTrackViews can refer to one specific LayoutTrack.
3874     *
3875     * @param track          the object (LayoutTrack subclass)
3876     * @param connectionType the type of connection
3877     * @return the coordinates for the connection type of the specified object
3878     */
3879    @Nonnull
3880    public Point2D getCoords(LayoutTrack track, HitPointType connectionType) {
3881        if (track == null) {
3882            log.warn("track is null, HitPointType={}", connectionType);
3883        }
3884        LayoutTrack trk = Objects.requireNonNull(track);
3885        return getCoords(getLayoutTrackView(trk), connectionType);
3886    }
3887
3888    /**
3889     * Get the coordinates for the connection type of the specified
3890     * LayoutTrackView or subtype.
3891     *
3892     * @param trkView        the object (LayoutTrackView subclass)
3893     * @param connectionType the type of connection
3894     * @return the coordinates for the connection type of the specified object
3895     */
3896    @Nonnull
3897    public Point2D getCoords(@Nonnull LayoutTrackView trkView, HitPointType connectionType) {
3898        LayoutTrackView trkv = Objects.requireNonNull(trkView);
3899
3900        return trkv.getCoordsForConnectionType(connectionType);
3901    }
3902
3903    @Override
3904    public void mouseReleased(JmriMouseEvent event) {
3905        super.setToolTip(null);
3906
3907        if (isInputTextBox(event)) {
3908            return;
3909        }
3910
3911        // initialize mouse position
3912        calcLocation(event);
3913
3914        if (!isEditable() && _highlightcomponent != null && highlightCursor) {
3915            _highlightcomponent = null;
3916            // see if we moused up on an object
3917            checkControls(true);
3918            redrawPanel();
3919        }
3920
3921        // if alt modifier is down invert the snap to grid behaviour
3922        snapToGridInvert = event.isAltDown();
3923
3924        if (isEditable()) {
3925            leToolBarPanel.setLocationText(dLoc);
3926
3927            // released the mouse with shift down... see what we're adding
3928            if (!event.isPopupTrigger() && !event.isMetaDown() && event.isShiftDown()) {
3929
3930                currentPoint = new Point2D.Double(xLoc, yLoc);
3931
3932                if (snapToGridOnAdd != snapToGridInvert) {
3933                    // this snaps the current point to the grid
3934                    currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize());
3935                    xLoc = (int) currentPoint.getX();
3936                    yLoc = (int) currentPoint.getY();
3937                    leToolBarPanel.setLocationText(currentPoint);
3938                }
3939
3940                if (leToolBarPanel.turnoutRHButton.isSelected()) {
3941                    addLayoutTurnout(LayoutTurnout.TurnoutType.RH_TURNOUT);
3942                } else if (leToolBarPanel.turnoutLHButton.isSelected()) {
3943                    addLayoutTurnout(LayoutTurnout.TurnoutType.LH_TURNOUT);
3944                } else if (leToolBarPanel.turnoutWYEButton.isSelected()) {
3945                    addLayoutTurnout(LayoutTurnout.TurnoutType.WYE_TURNOUT);
3946                } else if (leToolBarPanel.doubleXoverButton.isSelected()) {
3947                    addLayoutTurnout(LayoutTurnout.TurnoutType.DOUBLE_XOVER);
3948                } else if (leToolBarPanel.rhXoverButton.isSelected()) {
3949                    addLayoutTurnout(LayoutTurnout.TurnoutType.RH_XOVER);
3950                } else if (leToolBarPanel.lhXoverButton.isSelected()) {
3951                    addLayoutTurnout(LayoutTurnout.TurnoutType.LH_XOVER);
3952                } else if (leToolBarPanel.levelXingButton.isSelected()) {
3953                    addLevelXing();
3954                } else if (leToolBarPanel.layoutSingleSlipButton.isSelected()) {
3955                    addLayoutSlip(LayoutSlip.TurnoutType.SINGLE_SLIP);
3956                } else if (leToolBarPanel.layoutDoubleSlipButton.isSelected()) {
3957                    addLayoutSlip(LayoutSlip.TurnoutType.DOUBLE_SLIP);
3958                } else if (leToolBarPanel.endBumperButton.isSelected()) {
3959                    addEndBumper();
3960                } else if (leToolBarPanel.anchorButton.isSelected()) {
3961                    addAnchor();
3962                } else if (leToolBarPanel.edgeButton.isSelected()) {
3963                    addEdgeConnector();
3964                } else if (leToolBarPanel.trackButton.isSelected()) {
3965                    if ((beginTrack != null) && (foundTrack != null)
3966                            && (beginTrack != foundTrack)) {
3967                        addTrackSegment();
3968                        _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
3969                    }
3970                    beginTrack = null;
3971                    foundTrack = null;
3972                    foundTrackView = null;
3973                } else if (leToolBarPanel.multiSensorButton.isSelected()) {
3974                    startMultiSensor();
3975                } else if (leToolBarPanel.sensorButton.isSelected()) {
3976                    addSensor();
3977                } else if (leToolBarPanel.turnoutButton.isSelected()) {
3978                    addTurnout();
3979                } else if (leToolBarPanel.signalButton.isSelected()) {
3980                    addSignalHead();
3981                } else if (leToolBarPanel.textLabelButton.isSelected()) {
3982                    addLabel();
3983                } else if (leToolBarPanel.memoryButton.isSelected()) {
3984                    selectMemoryType();
3985                } else if (leToolBarPanel.globalVariableButton.isSelected()) {
3986                    addGlobalVariable();
3987                } else if (leToolBarPanel.blockContentsButton.isSelected()) {
3988                    selectBlockContentsType();
3989                } else if (leToolBarPanel.iconLabelButton.isSelected()) {
3990                    addIcon();
3991                } else if (leToolBarPanel.logixngButton.isSelected()) {
3992                    addLogixNGIcon();
3993                } else if (leToolBarPanel.audioButton.isSelected()) {
3994                    addAudioIcon();
3995                } else if (leToolBarPanel.shapeButton.isSelected()) {
3996                    LayoutShape ls = (LayoutShape) selectedObject;
3997                    if (ls == null) {
3998                        ls = addLayoutShape(currentPoint);
3999                    } else {
4000                        ls.addPoint(currentPoint, selectedHitPointType.shapePointIndex());
4001                    }
4002                    unionToPanelBounds(ls.getBounds());
4003                    _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
4004                } else if (leToolBarPanel.signalMastButton.isSelected()) {
4005                    addSignalMast();
4006                } else if (leToolBarPanel.turntableButton.isSelected()) {
4007                    addTurntable(currentPoint);
4008                } else if (leToolBarPanel.traverserButton.isSelected()) {
4009                    addTraverser(currentPoint);
4010                } else {
4011                    log.warn("No item selected in panel edit mode");
4012                }
4013                // resizePanelBounds(false);
4014                selectedObject = null;
4015                redrawPanel();
4016            } else if ((event.isPopupTrigger() || delayedPopupTrigger) && !isDragging) {
4017                selectedObject = null;
4018                selectedHitPointType = HitPointType.NONE;
4019                whenReleased = event.getWhen();
4020                showEditPopUps(event);
4021            } else if ((selectedObject != null) && (selectedHitPointType == HitPointType.TURNOUT_CENTER)
4022                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4023                    && !event.isShiftDown() && !event.isControlDown()) {
4024                // controlling turnouts, in edit mode
4025                LayoutTurnout t = (LayoutTurnout) selectedObject;
4026                t.toggleTurnout();
4027            } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.SLIP_LEFT)
4028                    || (selectedHitPointType == HitPointType.SLIP_RIGHT))
4029                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4030                    && !event.isShiftDown() && !event.isControlDown()) {
4031                // controlling slips, in edit mode
4032                LayoutSlip sl = (LayoutSlip) selectedObject;
4033                sl.toggleState(selectedHitPointType);
4034            } else if ((selectedObject != null) && (HitPointType.isTurntableRayHitType(selectedHitPointType))
4035                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4036                    && !event.isShiftDown() && !event.isControlDown()) {
4037                // controlling turntable, in edit mode
4038                LayoutTurntable t = (LayoutTurntable) selectedObject;
4039                t.setPosition(selectedHitPointType.turntableTrackIndex());
4040            } else if ((selectedObject != null) && (HitPointType.isTraverserSlotHitType(selectedHitPointType))
4041                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4042                    && !event.isShiftDown() && !event.isControlDown()) {
4043                // controlling Traverser, in edit mode
4044                LayoutTraverser t = (LayoutTraverser) selectedObject;
4045                t.setPosition(selectedHitPointType.traverserTrackIndex());
4046            } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.TURNOUT_CENTER)
4047                    || (selectedHitPointType == HitPointType.SLIP_CENTER)
4048                    || (selectedHitPointType == HitPointType.SLIP_LEFT)
4049                    || (selectedHitPointType == HitPointType.SLIP_RIGHT))
4050                    && allControlling() && (event.isMetaDown() && !event.isAltDown())
4051                    && !event.isShiftDown() && !event.isControlDown() && isDragging) {
4052                // We just dropped a turnout (or slip)... see if it will connect to anything
4053                hitPointCheckLayoutTurnouts((LayoutTurnout) selectedObject);
4054            } else if ((selectedObject != null) && (selectedHitPointType == HitPointType.POS_POINT)
4055                    && allControlling() && (event.isMetaDown())
4056                    && !event.isShiftDown() && !event.isControlDown() && isDragging) {
4057                // We just dropped a PositionablePoint... see if it will connect to anything
4058                PositionablePoint p = (PositionablePoint) selectedObject;
4059                if ((p.getConnect1() == null) || (p.getConnect2() == null)) {
4060                    checkPointOfPositionable(p);
4061                }
4062            }
4063
4064            if ((leToolBarPanel.trackButton.isSelected()) && (beginTrack != null) && (foundTrack != null)) {
4065                // user let up shift key before releasing the mouse when creating a track segment
4066                _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
4067                beginTrack = null;
4068                foundTrack = null;
4069                foundTrackView = null;
4070                redrawPanel();
4071            }
4072            createSelectionGroups();
4073        } else if ((selectedObject != null) && (selectedHitPointType == HitPointType.TURNOUT_CENTER)
4074                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4075                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4076            // controlling turnout out of edit mode
4077            LayoutTurnout t = (LayoutTurnout) selectedObject;
4078            if (useDirectTurnoutControl) {
4079                t.setState(Turnout.CLOSED);
4080            } else {
4081                t.toggleTurnout();
4082                if (highlightCursor && !t.isDisabled()) {
4083                    // flash the turnout circle a few times so the user knows it's being toggled
4084                    javax.swing.Timer timer = new javax.swing.Timer(150, null);
4085                    timer.addActionListener(new ActionListener(){
4086                        int count = 1;
4087                        @Override
4088                        public void actionPerformed(ActionEvent ae){
4089                          if(count % 2 != 0) t.setDisabled(true);
4090                          else t.setDisabled(false);
4091                          if(++count > 8) timer.stop();
4092                        }
4093                    });
4094                    timer.start();
4095                }
4096            }
4097        } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.SLIP_LEFT)
4098                || (selectedHitPointType == HitPointType.SLIP_RIGHT))
4099                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4100                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4101            // controlling slip out of edit mode
4102            LayoutSlip sl = (LayoutSlip) selectedObject;
4103            sl.toggleState(selectedHitPointType);
4104        } else if ((selectedObject != null) && (HitPointType.isTurntableRayHitType(selectedHitPointType))
4105                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4106                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4107            // controlling turntable out of edit mode
4108            LayoutTurntable t = (LayoutTurntable) selectedObject;
4109            t.setPosition(selectedHitPointType.turntableTrackIndex());
4110        } else if ((selectedObject != null) && (HitPointType.isTraverserSlotHitType(selectedHitPointType))
4111                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4112                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4113            // controlling traverser out of edit mode
4114            LayoutTraverser t = (LayoutTraverser) selectedObject;
4115            t.setPosition(selectedHitPointType.traverserTrackIndex());
4116        } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.BLOCKCONTENTSICON))
4117                && allControlling()  && !event.isAltDown() && !event.isPopupTrigger()
4118                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4119            BlockContentsIcon t = (BlockContentsIcon) selectedObject;
4120            if (t != null) {
4121                showPopUp(t, event);
4122            }
4123        } else if ((event.isPopupTrigger() || delayedPopupTrigger) && (!isDragging)) {
4124            // requesting marker popup out of edit mode
4125            LocoIcon lo = checkMarkerPopUps(dLoc);
4126            if (lo != null) {
4127                showPopUp(lo, event);
4128            } else {
4129                if (findLayoutTracksHitPoint(dLoc)) {
4130                    // show popup menu
4131                    switch (foundHitPointType) {
4132                        case TURNOUT_CENTER: {
4133                            if (useDirectTurnoutControl) {
4134                                LayoutTurnout t = (LayoutTurnout) foundTrack;
4135                                t.setState(Turnout.THROWN);
4136                            } else {
4137                                foundTrackView.showPopup(event);
4138                            }
4139                            break;
4140                        }
4141
4142                        case LEVEL_XING_CENTER:
4143                        case SLIP_RIGHT:
4144                        case SLIP_LEFT: {
4145                            foundTrackView.showPopup(event);
4146                            break;
4147                        }
4148
4149                        default: {
4150                            break;
4151                        }
4152                    }
4153                }
4154                AnalogClock2Display c = checkClockPopUps(dLoc);
4155                if (c != null) {
4156                    showPopUp(c, event);
4157                } else {
4158                    SignalMastIcon sm = checkSignalMastIconPopUps(dLoc);
4159                    if (sm != null) {
4160                        showPopUp(sm, event);
4161                    } else {
4162                        PositionableLabel im = checkLabelImagePopUps(dLoc);
4163                        if (im != null) {
4164                            showPopUp(im, event);
4165                        }
4166                    }
4167                }
4168            }
4169        }
4170
4171        if (!event.isPopupTrigger() && !isDragging) {
4172            List<Positionable> selections = getSelectedItems(event);
4173            if (!selections.isEmpty()) {
4174                selections.get(0).doMouseReleased(event);
4175                whenReleased = event.getWhen();
4176            }
4177        }
4178
4179        // train icon needs to know when moved
4180        if (event.isPopupTrigger() && isDragging) {
4181            List<Positionable> selections = getSelectedItems(event);
4182            if (!selections.isEmpty()) {
4183                selections.get(0).doMouseDragged(event);
4184            }
4185        }
4186
4187        if (selectedObject != null) {
4188            // An object was selected, deselect it
4189            prevSelectedObject = selectedObject;
4190            selectedObject = null;
4191        }
4192
4193        // clear these
4194        beginTrack = null;
4195        foundTrack = null;
4196        foundTrackView = null;
4197
4198        delayedPopupTrigger = false;
4199
4200        if (isDragging) {
4201            resizePanelBounds(true);
4202            isDragging = false;
4203        }
4204
4205        requestFocusInWindow();
4206    }   // mouseReleased
4207
4208    public void addPopupItems(@Nonnull JPopupMenu popup, @Nonnull JmriMouseEvent event) {
4209
4210        List<LayoutTrack> tracks = getLayoutTracks().stream().filter(layoutTrack -> {  // != means can't (yet) loop over Views
4211            HitPointType hitPointType = getLayoutTrackView(layoutTrack).findHitPointType(dLoc, false, false);
4212            return (HitPointType.NONE != hitPointType);
4213        }).collect(Collectors.toList());
4214
4215        List<Positionable> selections = getSelectedItems(event);
4216
4217        if ((tracks.size() > 1) || (selections.size() > 1)) {
4218            JMenu iconsBelowMenu = new JMenu(Bundle.getMessage("MenuItemIconsBelow"));
4219
4220            JMenuItem mi = new JMenuItem(Bundle.getMessage("MenuItemIconsBelow_InfoNotInOrder"));
4221            mi.setEnabled(false);
4222            iconsBelowMenu.add(mi);
4223
4224            if (tracks.size() > 1) {
4225                for (int i=0; i < tracks.size(); i++) {
4226                    LayoutTrack t = tracks.get(i);
4227                    iconsBelowMenu.add(new AbstractAction(Bundle.getMessage(
4228                            "LayoutTrackTypeAndName", t.getTypeName(), t.getName())) {
4229                        @Override
4230                        public void actionPerformed(ActionEvent e) {
4231                            LayoutTrackView ltv = getLayoutTrackView(t);
4232                            ltv.showPopup(event);
4233                        }
4234                    });
4235                }
4236            }
4237            if (selections.size() > 1) {
4238                for (int i=0; i < selections.size(); i++) {
4239                    Positionable pos = selections.get(i);
4240                    iconsBelowMenu.add(new AbstractAction(Bundle.getMessage(
4241                            "PositionableTypeAndName", pos.getTypeString(), pos.getNameString())) {
4242                        @Override
4243                        public void actionPerformed(ActionEvent e) {
4244                            showPopUp(pos, event, new ArrayList<>());
4245                        }
4246                    });
4247                }
4248            }
4249            popup.addSeparator();
4250            popup.add(iconsBelowMenu);
4251        }
4252    }
4253
4254    private void showEditPopUps(@Nonnull JmriMouseEvent event) {
4255        if (findLayoutTracksHitPoint(dLoc)) {
4256            if (HitPointType.isBezierHitType(foundHitPointType)) {
4257                getTrackSegmentView((TrackSegment) foundTrack).showBezierPopUp(event, foundHitPointType);
4258            } else if (HitPointType.isTurntableRayHitType(foundHitPointType)) {
4259                LayoutTurntable t = (LayoutTurntable) foundTrack;
4260                if (t.isTurnoutControlled()) {
4261                    LayoutTurntableView ltview = getLayoutTurntableView((LayoutTurntable) foundTrack);
4262                    ltview.showRayPopUp(event, foundHitPointType.turntableTrackIndex());
4263                }
4264            }else if (HitPointType.isTraverserSlotHitType(foundHitPointType)) {
4265                LayoutTraverser t = (LayoutTraverser) foundTrack;
4266                if (t.isTurnoutControlled()) {
4267                    LayoutTraverserView ltview = getLayoutTraverserView((LayoutTraverser) foundTrack);
4268                    ltview.showSlotPopUp(event, foundHitPointType.traverserTrackIndex());
4269                }
4270            } else if (HitPointType.isPopupHitType(foundHitPointType)) {
4271                foundTrackView.showPopup(event);
4272            } else if (HitPointType.isTurnoutHitType(foundHitPointType)) {
4273                // don't curently have edit popup for these
4274            } else {
4275                log.warn("Unknown foundPointType:{}", foundHitPointType);
4276            }
4277        } else {
4278            do {
4279                TrackSegment ts = checkTrackSegmentPopUps(dLoc);
4280                if (ts != null) {
4281                    TrackSegmentView tsv = getTrackSegmentView(ts);
4282                    tsv.showPopup(event);
4283                    break;
4284                }
4285
4286                SensorIcon s = checkSensorIconPopUps(dLoc);
4287                if (s != null) {
4288                    showPopUp(s, event);
4289                    break;
4290                }
4291
4292                TurnoutIcon t = checkTurnoutIconPopUps(dLoc);
4293                if (t != null) {
4294                    showPopUp(t, event);
4295                    break;
4296                }
4297
4298                LocoIcon lo = checkMarkerPopUps(dLoc);
4299                if (lo != null) {
4300                    showPopUp(lo, event);
4301                    break;
4302                }
4303
4304                SignalHeadIcon sh = checkSignalHeadIconPopUps(dLoc);
4305                if (sh != null) {
4306                    showPopUp(sh, event);
4307                    break;
4308                }
4309
4310                AnalogClock2Display c = checkClockPopUps(dLoc);
4311                if (c != null) {
4312                    showPopUp(c, event);
4313                    break;
4314                }
4315
4316                MultiSensorIcon ms = checkMultiSensorPopUps(dLoc);
4317                if (ms != null) {
4318                    showPopUp(ms, event);
4319                    break;
4320                }
4321
4322                LayoutShape ls = checkLayoutShapePopUps(dLoc);
4323                if (ls != null) {
4324                    ls.showShapePopUp(event, selectedHitPointType);
4325                    break;
4326                }
4327
4328                PositionableLabel lb = checkLabelImagePopUps(dLoc);
4329                if (lb != null) {
4330                    showPopUp(lb, event);
4331                    break;
4332                }
4333
4334                PositionableLabel b = checkBackgroundPopUps(dLoc);
4335                if (b != null) {
4336                    showPopUp(b, event);
4337                    break;
4338                }
4339
4340                PositionableJPanel jp = checkJPanelPopUps(dLoc);
4341                if (jp != null) {
4342                    showPopUp(jp, event);
4343                    break;
4344                }
4345
4346                SignalMastIcon sm = checkSignalMastIconPopUps(dLoc);
4347                if (sm != null) {
4348                    showPopUp(sm, event);
4349                    break;
4350                }
4351
4352                Positionable factPos = checkPositionablePopUps(dLoc);
4353                if (factPos != null) {
4354                    showPopUp(factPos, event);
4355                    break;
4356                }
4357
4358            } while (false);
4359        }
4360    }
4361
4362    /**
4363     * Select the menu items to display for the Positionable's popup.
4364     * @param pos   the item containing or requiring the context menu
4365     * @param event the event triggering the menu
4366     */
4367    public void showPopUp(@Nonnull Positionable pos, @Nonnull JmriMouseEvent event) {
4368        Positionable p = Objects.requireNonNull(pos);
4369
4370        if (!((Component) p).isVisible()) {
4371            return; // component must be showing on the screen to determine its location
4372        }
4373        JPopupMenu popup = new JPopupMenu();
4374
4375        if (p.isEditable()) {
4376            JMenuItem jmi;
4377
4378            if (showAlignPopup()) {
4379                setShowAlignmentMenu(popup);
4380                popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) {
4381                    @Override
4382                    public void actionPerformed(ActionEvent event) {
4383                        deleteSelectedItems();
4384                    }
4385                });
4386            } else {
4387                if (p.doViemMenu()) {
4388                    String objectType = p.getClass().getName();
4389                    objectType = objectType.substring(objectType.lastIndexOf('.') + 1);
4390                    jmi = popup.add(objectType);
4391                    jmi.setEnabled(false);
4392
4393                    jmi = popup.add(p.getNameString());
4394                    jmi.setEnabled(false);
4395
4396                    if (p.isPositionable()) {
4397                        setShowCoordinatesMenu(p, popup);
4398                    }
4399                    setDisplayLevelMenu(p, popup);
4400                    setPositionableMenu(p, popup);
4401                }
4402
4403                boolean popupSet = false;
4404                popupSet |= p.setRotateOrthogonalMenu(popup);
4405                popupSet |= p.setRotateMenu(popup);
4406                popupSet |= p.setScaleMenu(popup);
4407                if (popupSet) {
4408                    popup.addSeparator();
4409                    popupSet = false;
4410                }
4411                // Don't show the icon menu item for the MemoryInputIcon
4412                if (!(p instanceof PositionableJPanel)) {
4413                    popupSet |= p.setEditIconMenu(popup);
4414                }
4415                popupSet |= p.setTextEditMenu(popup);
4416
4417                PositionablePopupUtil util = p.getPopupUtility();
4418
4419                if (util != null) {
4420                    util.setFixedTextMenu(popup);
4421                    util.setTextMarginMenu(popup);
4422                    util.setTextBorderMenu(popup);
4423                    util.setTextFontMenu(popup);
4424                    util.setBackgroundMenu(popup);
4425                    util.setTextJustificationMenu(popup);
4426                    util.setTextOrientationMenu(popup);
4427                    popup.addSeparator();
4428                    util.propertyUtil(popup);
4429                    util.setAdditionalEditPopUpMenu(popup);
4430                    popupSet = true;
4431                }
4432
4433                if (popupSet) {
4434                    popup.addSeparator();
4435                    // popupSet = false;
4436                }
4437                p.setDisableControlMenu(popup);
4438                setShowAlignmentMenu(popup);
4439
4440                // for Positionables with unique settings
4441                p.showPopUp(popup);
4442                setShowToolTipMenu(p, popup);
4443
4444                setRemoveMenu(p, popup);
4445
4446                if (p.doViemMenu()) {
4447                    setHiddenMenu(p, popup);
4448                    setEmptyHiddenMenu(p, popup);
4449                    setValueEditDisabledMenu(p, popup);
4450                    setEditIdMenu(p, popup);
4451                    setEditClassesMenu(p, popup);
4452                    popup.addSeparator();
4453                    setLogixNGPositionableMenu(p, popup);
4454                }
4455            }
4456        } else {
4457            p.showPopUp(popup);
4458            PositionablePopupUtil util = p.getPopupUtility();
4459
4460            if (util != null) {
4461                util.setAdditionalViewPopUpMenu(popup);
4462            }
4463        }
4464
4465        addPopupItems(popup, event);
4466
4467        popup.show((Component) p, p.getWidth() / 2 + (int) ((getZoom() - 1.0) * p.getX()),
4468                p.getHeight() / 2 + (int) ((getZoom() - 1.0) * p.getY()));
4469
4470        /*popup.show((Component)pt, event.getX(), event.getY());*/
4471    }
4472
4473    private long whenReleased = 0; // used to identify event that was popup trigger
4474    private boolean awaitingIconChange = false;
4475
4476    @Override
4477    public void mouseClicked(@Nonnull JmriMouseEvent event) {
4478        if (isInputTextBox(event)) {
4479            return;
4480        }
4481
4482        // initialize mouse position
4483        calcLocation(event);
4484
4485        if (!isEditable() && _highlightcomponent != null && highlightCursor) {
4486            _highlightcomponent = null;
4487            redrawPanel();
4488        }
4489
4490        // if alt modifier is down invert the snap to grid behaviour
4491        snapToGridInvert = event.isAltDown();
4492
4493        if (!event.isMetaDown() && !event.isPopupTrigger() && !event.isAltDown()
4494                && !awaitingIconChange && !event.isShiftDown() && !event.isControlDown()) {
4495            List<Positionable> selections = getSelectedItems(event);
4496
4497            if (!selections.isEmpty()) {
4498                selections.get(0).doMouseClicked(event);
4499            }
4500        } else if (event.isPopupTrigger() && (whenReleased != event.getWhen())) {
4501
4502            if (isEditable()) {
4503                selectedObject = null;
4504                selectedHitPointType = HitPointType.NONE;
4505                showEditPopUps(event);
4506            } else {
4507                LocoIcon lo = checkMarkerPopUps(dLoc);
4508
4509                if (lo != null) {
4510                    showPopUp(lo, event);
4511                }
4512            }
4513        }
4514
4515        if (event.isControlDown() && !event.isPopupTrigger()) {
4516            if (findLayoutTracksHitPoint(dLoc)) {
4517                switch (foundHitPointType) {
4518                    case POS_POINT:
4519                    case TURNOUT_CENTER:
4520                    case LEVEL_XING_CENTER:
4521                    case SLIP_LEFT:
4522                    case SLIP_RIGHT:
4523            case TURNTABLE_CENTER:
4524            case TRAVERSER_CENTER: {
4525                        amendSelectionGroup(foundTrack);
4526                        break;
4527                    }
4528
4529                    default: {
4530                        break;
4531                    }
4532                }
4533            } else {
4534                PositionableLabel s = checkSensorIconPopUps(dLoc);
4535                if (s != null) {
4536                    amendSelectionGroup(s);
4537                } else {
4538                    PositionableLabel t = checkTurnoutIconPopUps(dLoc);
4539                    if (t != null) {
4540                        amendSelectionGroup(t);
4541                    } else {
4542                        PositionableLabel sh = checkSignalHeadIconPopUps(dLoc);
4543                        if (sh != null) {
4544                            amendSelectionGroup(sh);
4545                        } else {
4546                            PositionableLabel ms = checkMultiSensorPopUps(dLoc);
4547                            if (ms != null) {
4548                                amendSelectionGroup(ms);
4549                            } else {
4550                                PositionableLabel lb = checkLabelImagePopUps(dLoc);
4551                                if (lb != null) {
4552                                    amendSelectionGroup(lb);
4553                                } else {
4554                                    PositionableLabel b = checkBackgroundPopUps(dLoc);
4555                                    if (b != null) {
4556                                        amendSelectionGroup(b);
4557                                    } else {
4558                                        PositionableLabel sm = checkSignalMastIconPopUps(dLoc);
4559                                        if (sm != null) {
4560                                            amendSelectionGroup(sm);
4561                                        } else {
4562                                            LayoutShape ls = checkLayoutShapePopUps(dLoc);
4563                                            if (ls != null) {
4564                                                amendSelectionGroup(ls);
4565                                            } else {
4566                                                PositionableJPanel jp = checkJPanelPopUps(dLoc);
4567                                                if (jp != null) {
4568                                                    amendSelectionGroup(jp);
4569                                                }
4570                                            }
4571                                        }
4572                                    }
4573                                }
4574                            }
4575                        }
4576                    }
4577                }
4578            }
4579        } else if ((selectionWidth == 0) || (selectionHeight == 0)) {
4580            clearSelectionGroups();
4581        }
4582        requestFocusInWindow();
4583    }
4584
4585    private void checkPointOfPositionable(@Nonnull PositionablePoint p) {
4586        assert p != null;
4587
4588        TrackSegment t = p.getConnect1();
4589
4590        if (t == null) {
4591            t = p.getConnect2();
4592        }
4593
4594        // Nothing connected to this bit of track so ignore
4595        if (t == null) {
4596            return;
4597        }
4598        beginTrack = p;
4599        beginHitPointType = HitPointType.POS_POINT;
4600        PositionablePointView pv = getPositionablePointView(p);
4601        Point2D loc = pv.getCoordsCenter();
4602
4603        if (findLayoutTracksHitPoint(loc, true, p)) {
4604            switch (foundHitPointType) {
4605                case POS_POINT: {
4606                    PositionablePoint p2 = (PositionablePoint) foundTrack;
4607
4608                    if ((p2.getType() == PositionablePoint.PointType.ANCHOR) && p2.setTrackConnection(t)) {
4609                        if (t.getConnect1() == p) {
4610                            t.setNewConnect1(p2, foundHitPointType);
4611                        } else {
4612                            t.setNewConnect2(p2, foundHitPointType);
4613                        }
4614                        p.removeTrackConnection(t);
4615
4616                        if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4617                            removePositionablePoint(p);
4618                        }
4619                    }
4620                    break;
4621                }
4622                case TURNOUT_A:
4623                case TURNOUT_B:
4624                case TURNOUT_C:
4625                case TURNOUT_D:
4626                case SLIP_A:
4627                case SLIP_B:
4628                case SLIP_C:
4629                case SLIP_D:
4630                case LEVEL_XING_A:
4631                case LEVEL_XING_B:
4632                case LEVEL_XING_C:
4633                case LEVEL_XING_D: {
4634                    try {
4635                        if (foundTrack.getConnection(foundHitPointType) == null) {
4636                            foundTrack.setConnection(foundHitPointType, t, HitPointType.TRACK);
4637
4638                            if (t.getConnect1() == p) {
4639                                t.setNewConnect1(foundTrack, foundHitPointType);
4640                            } else {
4641                                t.setNewConnect2(foundTrack, foundHitPointType);
4642                            }
4643                            p.removeTrackConnection(t);
4644
4645                            if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4646                                removePositionablePoint(p);
4647                            }
4648                        }
4649                    } catch (JmriException e) {
4650                        log.debug("Unable to set location");
4651                    }
4652                    break;
4653                }
4654
4655                default: {
4656                    if (HitPointType.isTurntableRayHitType(foundHitPointType)) {
4657                        LayoutTurntable tt = (LayoutTurntable) foundTrack;
4658                        int ray = foundHitPointType.turntableTrackIndex();
4659
4660                        if (tt.getRayConnectIndexed(ray) == null) {
4661                            tt.setRayConnect(t, ray);
4662
4663                            if (t.getConnect1() == p) {
4664                                t.setNewConnect1(tt, foundHitPointType);
4665                            } else {
4666                                t.setNewConnect2(tt, foundHitPointType);
4667                            }
4668                            p.removeTrackConnection(t);
4669
4670                            if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4671                                removePositionablePoint(p);
4672                            }
4673                        }
4674                    } else if (HitPointType.isTraverserSlotHitType(foundHitPointType)) {
4675                        LayoutTraverser tt = (LayoutTraverser) foundTrack;
4676                        int slot = foundHitPointType.traverserTrackIndex();
4677
4678                        if (tt.getSlotConnectIndexed(slot) == null) {
4679                            tt.setSlotConnect(t, slot);
4680
4681                            if (t.getConnect1() == p) {
4682                                t.setNewConnect1(tt, foundHitPointType);
4683                            } else {
4684                                t.setNewConnect2(tt, foundHitPointType);
4685                            }
4686                            p.removeTrackConnection(t);
4687
4688                            if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4689                                removePositionablePoint(p);
4690                            }
4691                        }
4692                    } else {
4693                        log.debug("No valid point, so will quit");
4694                        return;
4695                    }
4696                    break;
4697                }
4698            }
4699            redrawPanel();
4700
4701            if (t.getLayoutBlock() != null) {
4702                getLEAuxTools().setBlockConnectivityChanged();
4703            }
4704        }
4705        beginTrack = null;
4706    }
4707
4708    // We just dropped a turnout... see if it will connect to anything
4709    private void hitPointCheckLayoutTurnouts(@Nonnull LayoutTurnout lt) {
4710        beginTrack = lt;
4711
4712        LayoutTurnoutView ltv = getLayoutTurnoutView(lt);
4713
4714        if (lt.getConnectA() == null) {
4715            if (lt instanceof LayoutSlip) {
4716                beginHitPointType = HitPointType.SLIP_A;
4717            } else {
4718                beginHitPointType = HitPointType.TURNOUT_A;
4719            }
4720            dLoc = ltv.getCoordsA();
4721            hitPointCheckLayoutTurnoutSubs(dLoc);
4722        }
4723
4724        if (lt.getConnectB() == null) {
4725            if (lt instanceof LayoutSlip) {
4726                beginHitPointType = HitPointType.SLIP_B;
4727            } else {
4728                beginHitPointType = HitPointType.TURNOUT_B;
4729            }
4730            dLoc = ltv.getCoordsB();
4731            hitPointCheckLayoutTurnoutSubs(dLoc);
4732        }
4733
4734        if (lt.getConnectC() == null) {
4735            if (lt instanceof LayoutSlip) {
4736                beginHitPointType = HitPointType.SLIP_C;
4737            } else {
4738                beginHitPointType = HitPointType.TURNOUT_C;
4739            }
4740            dLoc = ltv.getCoordsC();
4741            hitPointCheckLayoutTurnoutSubs(dLoc);
4742        }
4743
4744        if ((lt.getConnectD() == null) && (lt.isTurnoutTypeXover() || lt.isTurnoutTypeSlip())) {
4745            if (lt instanceof LayoutSlip) {
4746                beginHitPointType = HitPointType.SLIP_D;
4747            } else {
4748                beginHitPointType = HitPointType.TURNOUT_D;
4749            }
4750            dLoc = ltv.getCoordsD();
4751            hitPointCheckLayoutTurnoutSubs(dLoc);
4752        }
4753        beginTrack = null;
4754        foundTrack = null;
4755        foundTrackView = null;
4756    }
4757
4758    private void hitPointCheckLayoutTurnoutSubs(@Nonnull Point2D dLoc) {
4759        assert dLoc != null;
4760
4761        if (findLayoutTracksHitPoint(dLoc, true)) {
4762            switch (foundHitPointType) {
4763                case POS_POINT: {
4764                    PositionablePoint p2 = (PositionablePoint) foundTrack;
4765
4766                    if (((p2.getConnect1() == null) && (p2.getConnect2() != null))
4767                            || ((p2.getConnect1() != null) && (p2.getConnect2() == null))) {
4768                        TrackSegment t = p2.getConnect1();
4769
4770                        if (t == null) {
4771                            t = p2.getConnect2();
4772                        }
4773
4774                        if (t == null) {
4775                            return;
4776                        }
4777                        LayoutTurnout lt = (LayoutTurnout) beginTrack;
4778                        try {
4779                            if (lt.getConnection(beginHitPointType) == null) {
4780                                lt.setConnection(beginHitPointType, t, HitPointType.TRACK);
4781                                p2.removeTrackConnection(t);
4782
4783                                if (t.getConnect1() == p2) {
4784                                    t.setNewConnect1(lt, beginHitPointType);
4785                                } else {
4786                                    t.setNewConnect2(lt, beginHitPointType);
4787                                }
4788                                removePositionablePoint(p2);
4789                            }
4790
4791                            if (t.getLayoutBlock() != null) {
4792                                getLEAuxTools().setBlockConnectivityChanged();
4793                            }
4794                        } catch (JmriException e) {
4795                            log.debug("Unable to set location");
4796                        }
4797                    }
4798                    break;
4799                }
4800
4801                case TURNOUT_A:
4802                case TURNOUT_B:
4803                case TURNOUT_C:
4804                case TURNOUT_D:
4805                case SLIP_A:
4806                case SLIP_B:
4807                case SLIP_C:
4808                case SLIP_D: {
4809                    LayoutTurnout ft = (LayoutTurnout) foundTrack;
4810                    addTrackSegment();
4811
4812                    if ((ft.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT) || (ft.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT)) {
4813                        rotateTurnout(ft);
4814                    }
4815
4816                    // Assign a block to the new zero length track segment.
4817                    ((LayoutTurnoutView) foundTrackView).setTrackSegmentBlock(foundHitPointType, true);
4818                    break;
4819                }
4820
4821                default: {
4822                    log.warn("Unexpected foundPointType {} in hitPointCheckLayoutTurnoutSubs", foundHitPointType);
4823                    break;
4824                }
4825            }
4826        }
4827    }
4828
4829    private void rotateTurnout(@Nonnull LayoutTurnout t) {
4830        assert t != null;
4831
4832        LayoutTurnoutView tv = getLayoutTurnoutView(t);
4833
4834        LayoutTurnout be = (LayoutTurnout) beginTrack;
4835        LayoutTurnoutView bev = getLayoutTurnoutView(be);
4836
4837        if (((beginHitPointType == HitPointType.TURNOUT_A) && ((be.getConnectB() != null) || (be.getConnectC() != null)))
4838                || ((beginHitPointType == HitPointType.TURNOUT_B) && ((be.getConnectA() != null) || (be.getConnectC() != null)))
4839                || ((beginHitPointType == HitPointType.TURNOUT_C) && ((be.getConnectB() != null) || (be.getConnectA() != null)))) {
4840            return;
4841        }
4842
4843        if ((be.getTurnoutType() != LayoutTurnout.TurnoutType.RH_TURNOUT) && (be.getTurnoutType() != LayoutTurnout.TurnoutType.LH_TURNOUT)) {
4844            return;
4845        }
4846
4847        Point2D c, diverg, xy2;
4848
4849        if ((foundHitPointType == HitPointType.TURNOUT_C) && (beginHitPointType == HitPointType.TURNOUT_C)) {
4850            c = tv.getCoordsA();
4851            diverg = tv.getCoordsB();
4852            xy2 = MathUtil.subtract(c, diverg);
4853        } else if ((foundHitPointType == HitPointType.TURNOUT_C)
4854                && ((beginHitPointType == HitPointType.TURNOUT_A) || (beginHitPointType == HitPointType.TURNOUT_B))) {
4855
4856            c = tv.getCoordsCenter();
4857            diverg = tv.getCoordsC();
4858
4859            if (beginHitPointType == HitPointType.TURNOUT_A) {
4860                xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
4861            } else {
4862                xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
4863            }
4864        } else if (foundHitPointType == HitPointType.TURNOUT_B) {
4865            c = tv.getCoordsA();
4866            diverg = tv.getCoordsB();
4867
4868            switch (beginHitPointType) {
4869                case TURNOUT_B:
4870                    xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
4871                    break;
4872                case TURNOUT_A:
4873                    xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
4874                    break;
4875                case TURNOUT_C:
4876                default:
4877                    xy2 = MathUtil.subtract(bev.getCoordsCenter(), bev.getCoordsC());
4878                    break;
4879            }
4880        } else if (foundHitPointType == HitPointType.TURNOUT_A) {
4881            c = tv.getCoordsA();
4882            diverg = tv.getCoordsB();
4883
4884            switch (beginHitPointType) {
4885                case TURNOUT_A:
4886                    xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
4887                    break;
4888                case TURNOUT_B:
4889                    xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
4890                    break;
4891                case TURNOUT_C:
4892                default:
4893                    xy2 = MathUtil.subtract(bev.getCoordsC(), bev.getCoordsCenter());
4894                    break;
4895            }
4896        } else {
4897            return;
4898        }
4899        Point2D xy = MathUtil.subtract(diverg, c);
4900        double radius = Math.toDegrees(Math.atan2(xy.getY(), xy.getX()));
4901        double eRadius = Math.toDegrees(Math.atan2(xy2.getY(), xy2.getX()));
4902        bev.rotateCoords(radius - eRadius);
4903
4904        Point2D conCord = bev.getCoordsA();
4905        Point2D tCord = tv.getCoordsC();
4906
4907        if (foundHitPointType == HitPointType.TURNOUT_B) {
4908            tCord = tv.getCoordsB();
4909        }
4910
4911        if (foundHitPointType == HitPointType.TURNOUT_A) {
4912            tCord = tv.getCoordsA();
4913        }
4914
4915        switch (beginHitPointType) {
4916            case TURNOUT_A:
4917                conCord = bev.getCoordsA();
4918                break;
4919            case TURNOUT_B:
4920                conCord = bev.getCoordsB();
4921                break;
4922            case TURNOUT_C:
4923                conCord = bev.getCoordsC();
4924                break;
4925            default:
4926                break;
4927        }
4928        xy = MathUtil.subtract(conCord, tCord);
4929        Point2D offset = MathUtil.subtract(bev.getCoordsCenter(), xy);
4930        bev.setCoordsCenter(offset);
4931    }
4932
4933    public List<Positionable> _positionableSelection = new ArrayList<>();
4934    public List<LayoutTrack> _layoutTrackSelection = new ArrayList<>();
4935    public List<LayoutShape> _layoutShapeSelection = new ArrayList<>();
4936
4937    @Nonnull
4938    public List<Positionable> getPositionalSelection() {
4939        return _positionableSelection;
4940    }
4941
4942    @Nonnull
4943    public List<LayoutTrack> getLayoutTrackSelection() {
4944        return _layoutTrackSelection;
4945    }
4946
4947    @Nonnull
4948    public List<LayoutShape> getLayoutShapeSelection() {
4949        return _layoutShapeSelection;
4950    }
4951
4952    private void createSelectionGroups() {
4953        Rectangle2D selectionRect = getSelectionRect();
4954
4955        getContents().forEach((o) -> {
4956            if (selectionRect.contains(o.getLocation())) {
4957
4958                log.trace("found item o of class {}", o.getClass());
4959                if (!_positionableSelection.contains(o)) {
4960                    _positionableSelection.add(o);
4961                }
4962            }
4963        });
4964
4965        getLayoutTracks().forEach((lt) -> {
4966            LayoutTrackView ltv = getLayoutTrackView(lt);
4967            Point2D center = ltv.getCoordsCenter();
4968            if (selectionRect.contains(center)) {
4969                if (!_layoutTrackSelection.contains(lt)) {
4970                    _layoutTrackSelection.add(lt);
4971                }
4972            }
4973        });
4974        assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
4975
4976        layoutShapes.forEach((ls) -> {
4977            if (selectionRect.intersects(ls.getBounds())) {
4978                if (!_layoutShapeSelection.contains(ls)) {
4979                    _layoutShapeSelection.add(ls);
4980                }
4981            }
4982        });
4983        redrawPanel();
4984    }
4985
4986    public void clearSelectionGroups() {
4987        selectionActive = false;
4988        _positionableSelection.clear();
4989        _layoutTrackSelection.clear();
4990        assignBlockToSelectionMenuItem.setEnabled(false);
4991        _layoutShapeSelection.clear();
4992    }
4993
4994    private boolean noWarnGlobalDelete = false;
4995
4996    private void deleteSelectedItems() {
4997        if (!noWarnGlobalDelete) {
4998            int selectedValue = JmriJOptionPane.showOptionDialog(this,
4999                    Bundle.getMessage("Question6"), Bundle.getMessage("WarningTitle"),
5000                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
5001                    new Object[]{Bundle.getMessage("ButtonYes"),
5002                        Bundle.getMessage("ButtonNo"),
5003                        Bundle.getMessage("ButtonYesPlus")},
5004                    Bundle.getMessage("ButtonNo"));
5005
5006            // array position 1, ButtonNo or Dialog closed.
5007            if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
5008                return; // return without creating if "No" response
5009            }
5010
5011            if (selectedValue == 2) { // array positio 2, ButtonYesPlus
5012                // Suppress future warnings, and continue
5013                noWarnGlobalDelete = true;
5014            }
5015        }
5016
5017        _positionableSelection.forEach(this::remove);
5018
5019        _layoutTrackSelection.forEach((lt) -> {
5020            if (lt instanceof PositionablePoint) {
5021                boolean oldWarning = noWarnPositionablePoint;
5022                noWarnPositionablePoint = true;
5023                removePositionablePoint((PositionablePoint) lt);
5024                noWarnPositionablePoint = oldWarning;
5025            } else if (lt instanceof LevelXing) {
5026                boolean oldWarning = noWarnLevelXing;
5027                noWarnLevelXing = true;
5028                removeLevelXing((LevelXing) lt);
5029                noWarnLevelXing = oldWarning;
5030            } else if (lt instanceof LayoutSlip) {
5031                boolean oldWarning = noWarnSlip;
5032                noWarnSlip = true;
5033                removeLayoutSlip((LayoutSlip) lt);
5034                noWarnSlip = oldWarning;
5035            } else if (lt instanceof LayoutTurntable) {
5036                boolean oldWarning = noWarnTurntable;
5037                noWarnTurntable = true;
5038                removeTurntable((LayoutTurntable) lt);
5039                noWarnTurntable = oldWarning;
5040            } else if (lt instanceof LayoutTraverser) {
5041                boolean oldWarning = noWarnTraverser;
5042                noWarnTraverser = true;
5043                removeTraverser((LayoutTraverser) lt);
5044                noWarnTraverser = oldWarning;
5045            } else if (lt instanceof LayoutTurnout) {  //<== this includes LayoutSlips
5046                boolean oldWarning = noWarnLayoutTurnout;
5047                noWarnLayoutTurnout = true;
5048                removeLayoutTurnout((LayoutTurnout) lt);
5049                noWarnLayoutTurnout = oldWarning;
5050            }
5051        });
5052
5053        layoutShapes.removeAll(_layoutShapeSelection);
5054
5055        clearSelectionGroups();
5056        redrawPanel();
5057    }
5058
5059    private void amendSelectionGroup(@Nonnull Positionable pos) {
5060        Positionable p = Objects.requireNonNull(pos);
5061
5062        if (_positionableSelection.contains(p)) {
5063            _positionableSelection.remove(p);
5064        } else {
5065            _positionableSelection.add(p);
5066        }
5067        redrawPanel();
5068    }
5069
5070    public void amendSelectionGroup(@Nonnull LayoutTrack track) {
5071        LayoutTrack p = Objects.requireNonNull(track);
5072
5073        if (_layoutTrackSelection.contains(p)) {
5074            _layoutTrackSelection.remove(p);
5075        } else {
5076            _layoutTrackSelection.add(p);
5077        }
5078        assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
5079        redrawPanel();
5080    }
5081
5082    public void amendSelectionGroup(@Nonnull LayoutShape shape) {
5083        LayoutShape ls = Objects.requireNonNull(shape);
5084
5085        if (_layoutShapeSelection.contains(ls)) {
5086            _layoutShapeSelection.remove(ls);
5087        } else {
5088            _layoutShapeSelection.add(ls);
5089        }
5090        redrawPanel();
5091    }
5092
5093    public void alignSelection(boolean alignX) {
5094        Point2D minPoint = MathUtil.infinityPoint2D;
5095        Point2D maxPoint = MathUtil.zeroPoint2D;
5096        Point2D sumPoint = MathUtil.zeroPoint2D;
5097        int cnt = 0;
5098
5099        for (Positionable comp : _positionableSelection) {
5100            if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) {
5101                continue;   // skip non-positionables
5102            }
5103            Point2D p = MathUtil.pointToPoint2D(comp.getLocation());
5104            minPoint = MathUtil.min(minPoint, p);
5105            maxPoint = MathUtil.max(maxPoint, p);
5106            sumPoint = MathUtil.add(sumPoint, p);
5107            cnt++;
5108        }
5109
5110        for (LayoutTrack lt : _layoutTrackSelection) {
5111            LayoutTrackView ltv = getLayoutTrackView(lt);
5112            Point2D p = ltv.getCoordsCenter();
5113            minPoint = MathUtil.min(minPoint, p);
5114            maxPoint = MathUtil.max(maxPoint, p);
5115            sumPoint = MathUtil.add(sumPoint, p);
5116            cnt++;
5117        }
5118
5119        for (LayoutShape ls : _layoutShapeSelection) {
5120            Point2D p = ls.getCoordsCenter();
5121            minPoint = MathUtil.min(minPoint, p);
5122            maxPoint = MathUtil.max(maxPoint, p);
5123            sumPoint = MathUtil.add(sumPoint, p);
5124            cnt++;
5125        }
5126
5127        Point2D avePoint = MathUtil.divide(sumPoint, cnt);
5128        int aveX = (int) avePoint.getX();
5129        int aveY = (int) avePoint.getY();
5130
5131        for (Positionable comp : _positionableSelection) {
5132            if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) {
5133                continue;   // skip non-positionables
5134            }
5135
5136            if (alignX) {
5137                comp.setLocation(aveX, comp.getY());
5138            } else {
5139                comp.setLocation(comp.getX(), aveY);
5140            }
5141        }
5142
5143        _layoutTrackSelection.forEach((lt) -> {
5144            LayoutTrackView ltv = getLayoutTrackView(lt);
5145            if (alignX) {
5146                ltv.setCoordsCenter(new Point2D.Double(aveX, ltv.getCoordsCenter().getY()));
5147            } else {
5148                ltv.setCoordsCenter(new Point2D.Double(ltv.getCoordsCenter().getX(), aveY));
5149            }
5150        });
5151
5152        _layoutShapeSelection.forEach((ls) -> {
5153            if (alignX) {
5154                ls.setCoordsCenter(new Point2D.Double(aveX, ls.getCoordsCenter().getY()));
5155            } else {
5156                ls.setCoordsCenter(new Point2D.Double(ls.getCoordsCenter().getX(), aveY));
5157            }
5158        });
5159
5160        redrawPanel();
5161    }
5162
5163    private boolean showAlignPopup() {
5164        return ((!_positionableSelection.isEmpty())
5165                || (!_layoutTrackSelection.isEmpty())
5166                || (!_layoutShapeSelection.isEmpty()));
5167    }
5168
5169    /**
5170     * Offer actions to align the selected Positionable items either
5171     * Horizontally (at average y coord) or Vertically (at average x coord).
5172     *
5173     * @param popup the JPopupMenu to add alignment menu to
5174     * @return true if alignment menu added
5175     */
5176    public boolean setShowAlignmentMenu(@Nonnull JPopupMenu popup) {
5177        if (showAlignPopup()) {
5178            JMenu edit = new JMenu(Bundle.getMessage("EditAlignment"));
5179            edit.add(new AbstractAction(Bundle.getMessage("AlignX")) {
5180                @Override
5181                public void actionPerformed(ActionEvent event) {
5182                    alignSelection(true);
5183                }
5184            });
5185            edit.add(new AbstractAction(Bundle.getMessage("AlignY")) {
5186                @Override
5187                public void actionPerformed(ActionEvent event) {
5188                    alignSelection(false);
5189                }
5190            });
5191            popup.add(edit);
5192
5193            return true;
5194        }
5195        return false;
5196    }
5197
5198    @Override
5199    public void keyPressed(@Nonnull KeyEvent event) {
5200        if (event.getKeyCode() == KeyEvent.VK_DELETE) {
5201            deleteSelectedItems();
5202            return;
5203        }
5204
5205        double deltaX = returnDeltaPositionX(event);
5206        double deltaY = returnDeltaPositionY(event);
5207
5208        if ((deltaX != 0) || (deltaY != 0)) {
5209            selectionX += deltaX;
5210            selectionY += deltaY;
5211
5212            Point2D delta = new Point2D.Double(deltaX, deltaY);
5213            _positionableSelection.forEach((c) -> {
5214                Point2D newPoint = c.getLocation();
5215                if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) {
5216                    MemoryIcon pm = (MemoryIcon) c;
5217                    newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY());
5218                }
5219                newPoint = MathUtil.add(newPoint, delta);
5220                newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
5221                c.setLocation(MathUtil.point2DToPoint(newPoint));
5222            });
5223
5224            _layoutTrackSelection.forEach((lt) -> {
5225                LayoutTrackView ltv = getLayoutTrackView(lt);
5226                Point2D newPoint = MathUtil.add(ltv.getCoordsCenter(), delta);
5227                newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
5228                getLayoutTrackView(lt).setCoordsCenter(newPoint);
5229            });
5230
5231            _layoutShapeSelection.forEach((ls) -> {
5232                Point2D newPoint = MathUtil.add(ls.getCoordsCenter(), delta);
5233                newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
5234                ls.setCoordsCenter(newPoint);
5235            });
5236            redrawPanel();
5237            return;
5238        }
5239        getLayoutEditorToolBarPanel().keyPressed(event);
5240    }
5241
5242    private double returnDeltaPositionX(@Nonnull KeyEvent event) {
5243        double result = 0.0;
5244        double amount = event.isShiftDown() ? 5.0 : 1.0;
5245
5246        switch (event.getKeyCode()) {
5247            case KeyEvent.VK_LEFT: {
5248                result = -amount;
5249                break;
5250            }
5251
5252            case KeyEvent.VK_RIGHT: {
5253                result = +amount;
5254                break;
5255            }
5256
5257            default: {
5258                break;
5259            }
5260        }
5261        return result;
5262    }
5263
5264    private double returnDeltaPositionY(@Nonnull KeyEvent event) {
5265        double result = 0.0;
5266        double amount = event.isShiftDown() ? 5.0 : 1.0;
5267
5268        switch (event.getKeyCode()) {
5269            case KeyEvent.VK_UP: {
5270                result = -amount;
5271                break;
5272            }
5273
5274            case KeyEvent.VK_DOWN: {
5275                result = +amount;
5276                break;
5277            }
5278
5279            default: {
5280                break;
5281            }
5282        }
5283        return result;
5284    }
5285
5286    int _prevNumSel = 0;
5287
5288    @Override
5289    public void mouseMoved(@Nonnull JmriMouseEvent event) {
5290        // initialize mouse position
5291        calcLocation(event);
5292
5293        // if alt modifier is down invert the snap to grid behaviour
5294        snapToGridInvert = event.isAltDown();
5295
5296        if (isEditable()) {
5297            leToolBarPanel.setLocationText(dLoc);
5298        }
5299        List<Positionable> selections = getSelectedItems(event);
5300        Positionable selection = null;
5301        int numSel = selections.size();
5302
5303        if (numSel > 0) {
5304            selection = selections.get(0);
5305        }
5306
5307        if ((selection != null) && (selection.getDisplayLevel() > Editor.BKG) && selection.showToolTip()) {
5308            showToolTip(selection, event);
5309        } else if (_targetPanel.getCursor() != Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)) {
5310            super.setToolTip(null);
5311        }
5312
5313        if (numSel != _prevNumSel) {
5314            redrawPanel();
5315            _prevNumSel = numSel;
5316        }
5317
5318        if (findLayoutTracksHitPoint(dLoc)) {
5319            // log.debug("foundTrack: {}", foundTrack);
5320            if (HitPointType.isControlHitType(foundHitPointType)) {
5321                _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
5322                setTurnoutTooltip();
5323            } else {
5324                _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
5325            }
5326            foundTrack = null;
5327            foundHitPointType = HitPointType.NONE;
5328        } else {
5329            _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
5330        }
5331    }   // mouseMoved
5332
5333    private void setTurnoutTooltip() {
5334        if (foundTrackView instanceof LayoutTurnoutView) {
5335            var ltv = (LayoutTurnoutView) foundTrackView;
5336            var lt = ltv.getLayoutTurnout();
5337            if (lt.showToolTip()) {
5338                var tt = lt.getToolTip();
5339                if (tt != null) {
5340                    tt.setText(lt.getNameString());
5341                    var coords = ltv.getCoordsCenter();
5342                    int offsetY = (int) (getTurnoutCircleSize() * SIZE);
5343                    tt.setLocation((int) coords.getX(), (int) coords.getY() + offsetY);
5344                    setToolTip(tt);
5345                }
5346            }
5347        }
5348    }
5349
5350    public void setAllShowLayoutTurnoutToolTip(boolean state) {
5351        log.debug("setAllShowLayoutTurnoutToolTip: {}", state);
5352        for (LayoutTurnout lt : getLayoutTurnoutsAndSlips()) {
5353            lt.setShowToolTip(state);
5354        }
5355    }
5356
5357    private boolean isDragging = false;
5358
5359    @Override
5360    public void mouseDragged(@Nonnull JmriMouseEvent event) {
5361        // initialize mouse position
5362        calcLocation(event);
5363
5364        checkHighlightCursor();
5365
5366        // ignore this event if still at the original point
5367        if ((!isDragging) && (xLoc == getAnchorX()) && (yLoc == getAnchorY())) {
5368            return;
5369        }
5370
5371        // if alt modifier is down invert the snap to grid behaviour
5372        snapToGridInvert = event.isAltDown();
5373
5374        // process this mouse dragged event
5375        if (isEditable()) {
5376            leToolBarPanel.setLocationText(dLoc);
5377        }
5378        currentPoint = MathUtil.add(dLoc, startDelta);
5379        // don't allow negative placement, objects could become unreachable
5380        currentPoint = MathUtil.max(currentPoint, MathUtil.zeroPoint2D);
5381
5382        if ((selectedObject != null) && (event.isMetaDown() || event.isAltDown())
5383                && (selectedHitPointType == HitPointType.MARKER)) {
5384            // marker moves regardless of editMode or positionable
5385            PositionableLabel pl = (PositionableLabel) selectedObject;
5386            pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5387            isDragging = true;
5388            redrawPanel();
5389            return;
5390        }
5391
5392        if (isEditable()) {
5393            if ((selectedObject != null) && event.isMetaDown() && allPositionable()) {
5394                if (snapToGridOnMove != snapToGridInvert) {
5395                    // this snaps currentPoint to the grid
5396                    currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize());
5397                    xLoc = (int) currentPoint.getX();
5398                    yLoc = (int) currentPoint.getY();
5399                    leToolBarPanel.setLocationText(currentPoint);
5400                }
5401
5402                if ((!_positionableSelection.isEmpty())
5403                        || (!_layoutTrackSelection.isEmpty())
5404                        || (!_layoutShapeSelection.isEmpty())) {
5405                    Point2D lastPoint = new Point2D.Double(_lastX, _lastY);
5406                    Point2D offset = MathUtil.subtract(currentPoint, lastPoint);
5407                    Point2D newPoint;
5408
5409                    for (Positionable c : _positionableSelection) {
5410                        if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) {
5411                            MemoryIcon pm = (MemoryIcon) c;
5412                            newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY());
5413                        } else {
5414                            newPoint = c.getLocation();
5415                        }
5416                        newPoint = MathUtil.add(newPoint, offset);
5417                        // don't allow negative placement, objects could become unreachable
5418                        newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
5419                        c.setLocation(MathUtil.point2DToPoint(newPoint));
5420                    }
5421
5422                    for (LayoutTrack lt : _layoutTrackSelection) {
5423                        LayoutTrackView ltv = getLayoutTrackView(lt);
5424                        Point2D center = ltv.getCoordsCenter();
5425                        newPoint = MathUtil.add(center, offset);
5426                        // don't allow negative placement, objects could become unreachable
5427                        newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
5428                        getLayoutTrackView(lt).setCoordsCenter(newPoint);
5429                    }
5430
5431                    for (LayoutShape ls : _layoutShapeSelection) {
5432                        Point2D center = ls.getCoordsCenter();
5433                        newPoint = MathUtil.add(center, offset);
5434                        // don't allow negative placement, objects could become unreachable
5435                        newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
5436                        ls.setCoordsCenter(newPoint);
5437                    }
5438
5439                    _lastX = xLoc;
5440                    _lastY = yLoc;
5441                } else {
5442                    switch (selectedHitPointType) {
5443                        case POS_POINT:
5444                        case TURNOUT_CENTER:
5445                        case LEVEL_XING_CENTER:
5446                        case SLIP_LEFT:
5447                        case SLIP_RIGHT:
5448                        case TURNTABLE_CENTER:
5449                        case TRAVERSER_CENTER: {
5450                            getLayoutTrackView((LayoutTrack) selectedObject).setCoordsCenter(currentPoint);
5451                            isDragging = true;
5452                            break;
5453                        }
5454
5455                        case TURNOUT_A: {
5456                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsA(currentPoint);
5457                            break;
5458                        }
5459
5460                        case TURNOUT_B: {
5461                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsB(currentPoint);
5462                            break;
5463                        }
5464
5465                        case TURNOUT_C: {
5466                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsC(currentPoint);
5467                            break;
5468                        }
5469
5470                        case TURNOUT_D: {
5471                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsD(currentPoint);
5472                            break;
5473                        }
5474
5475                        case LEVEL_XING_A: {
5476                            getLevelXingView((LevelXing) selectedObject).setCoordsA(currentPoint);
5477                            break;
5478                        }
5479
5480                        case LEVEL_XING_B: {
5481                            getLevelXingView((LevelXing) selectedObject).setCoordsB(currentPoint);
5482                            break;
5483                        }
5484
5485                        case LEVEL_XING_C: {
5486                            getLevelXingView((LevelXing) selectedObject).setCoordsC(currentPoint);
5487                            break;
5488                        }
5489
5490                        case LEVEL_XING_D: {
5491                            getLevelXingView((LevelXing) selectedObject).setCoordsD(currentPoint);
5492                            break;
5493                        }
5494
5495                        case SLIP_A: {
5496                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsA(currentPoint);
5497                            break;
5498                        }
5499
5500                        case SLIP_B: {
5501                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsB(currentPoint);
5502                            break;
5503                        }
5504
5505                        case SLIP_C: {
5506                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsC(currentPoint);
5507                            break;
5508                        }
5509
5510                        case SLIP_D: {
5511                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsD(currentPoint);
5512                            break;
5513                        }
5514
5515                        case LAYOUT_POS_LABEL:
5516                        case MULTI_SENSOR: {
5517                            PositionableLabel pl = (PositionableLabel) selectedObject;
5518                            if (pl.isPositionable()) {
5519                                pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5520                                isDragging = true;
5521                            }
5522                            break;
5523                        }
5524
5525                        case LAYOUT_POS_JCOMP: {
5526                            PositionableJComponent c = (PositionableJComponent) selectedObject;
5527
5528                            if (c.isPositionable()) {
5529                                c.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5530                                isDragging = true;
5531                            }
5532                            break;
5533                        }
5534
5535                        case LAYOUT_POS_JPNL: {
5536                            PositionableJPanel c = (PositionableJPanel) selectedObject;
5537
5538                            if (c.isPositionable()) {
5539                                c.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5540                                isDragging = true;
5541                            }
5542                            break;
5543                        }
5544
5545                        case TRACK_CIRCLE_CENTRE: {
5546                            TrackSegmentView tv = getTrackSegmentView((TrackSegment) selectedObject);
5547                            tv.reCalculateTrackSegmentAngle(currentPoint.getX(), currentPoint.getY());
5548                            break;
5549                        }
5550
5551                        default: {
5552                            if (HitPointType.isBezierHitType(foundHitPointType)) {
5553                                int index = selectedHitPointType.bezierPointIndex();
5554                                getTrackSegmentView((TrackSegment) selectedObject).setBezierControlPoint(currentPoint, index);
5555                            } else if ((selectedHitPointType == HitPointType.SHAPE_CENTER)) {
5556                                ((LayoutShape) selectedObject).setCoordsCenter(currentPoint);
5557                            } else if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) {
5558                                int index = selectedHitPointType.shapePointIndex();
5559                                ((LayoutShape) selectedObject).setPoint(index, currentPoint);
5560                            } else if (HitPointType.isTurntableRayHitType(selectedHitPointType)) {
5561                                LayoutTurntable turn = (LayoutTurntable) selectedObject;
5562                                LayoutTurntableView turnView = getLayoutTurntableView(turn);
5563                                turnView.setRayCoordsIndexed(currentPoint.getX(), currentPoint.getY(),
5564                                        selectedHitPointType.turntableTrackIndex());
5565//                             } else if (HitPointType.isTraverserSlotHitType(selectedHitPointType)) {
5566//                                 Placeholder comment:
5567//                                 The ability to drag the slot connection points is disabled.
5568//                                 Connection point locations are relative to the traverser center point.
5569                            }
5570                            break;
5571                        }
5572                    }
5573                }
5574            } else if ((beginTrack != null)
5575                    && event.isShiftDown()
5576                    && leToolBarPanel.trackButton.isSelected()) {
5577                // dragging from first end of Track Segment
5578                currentLocation = new Point2D.Double(xLoc, yLoc);
5579                boolean needResetCursor = (foundTrack != null);
5580
5581                if (findLayoutTracksHitPoint(currentLocation, true)) {
5582                    // have match to free connection point, change cursor
5583                    _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
5584                } else if (needResetCursor) {
5585                    _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
5586                }
5587            } else if (event.isShiftDown()
5588                    && leToolBarPanel.shapeButton.isSelected() && (selectedObject != null)) {
5589                // dragging from end of shape
5590                currentLocation = new Point2D.Double(xLoc, yLoc);
5591            } else if (selectionActive && !event.isShiftDown() && !event.isMetaDown()) {
5592                selectionWidth = xLoc - selectionX;
5593                selectionHeight = yLoc - selectionY;
5594            }
5595            redrawPanel();
5596        } else {
5597            Rectangle r = new Rectangle(event.getX(), event.getY(), 1, 1);
5598            ((JComponent) event.getSource()).scrollRectToVisible(r);
5599        }   // if (isEditable())
5600    }   // mouseDragged
5601
5602    @Override
5603    public void mouseEntered(@Nonnull JmriMouseEvent event) {
5604        _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
5605    }
5606
5607    /**
5608     * Add an Anchor point.
5609     */
5610    public void addAnchor() {
5611        addAnchor(currentPoint);
5612    }
5613
5614    @Nonnull
5615    public PositionablePoint addAnchor(@Nonnull Point2D point) {
5616        Point2D p = Objects.requireNonNull(point);
5617
5618        // get unique name
5619        String name = finder.uniqueName("A", ++numAnchors);
5620
5621        // create object
5622        PositionablePoint o = new PositionablePoint(name,
5623                PositionablePoint.PointType.ANCHOR, this);
5624        PositionablePointView pv = new PositionablePointView(o, p, this);
5625        addLayoutTrack(o, pv);
5626
5627        setDirty();
5628
5629        return o;
5630    }
5631
5632    /**
5633     * Add an End Bumper point.
5634     */
5635    public void addEndBumper() {
5636        // get unique name
5637        String name = finder.uniqueName("EB", ++numEndBumpers);
5638
5639        // create object
5640        PositionablePoint o = new PositionablePoint(name,
5641                PositionablePoint.PointType.END_BUMPER, this);
5642        PositionablePointView pv = new PositionablePointView(o, currentPoint, this);
5643        addLayoutTrack(o, pv);
5644
5645        setDirty();
5646    }
5647
5648    /**
5649     * Add an Edge Connector point.
5650     */
5651    public void addEdgeConnector() {
5652        // get unique name
5653        String name = finder.uniqueName("EC", ++numEdgeConnectors);
5654
5655        // create object
5656        PositionablePoint o = new PositionablePoint(name,
5657                PositionablePoint.PointType.EDGE_CONNECTOR, this);
5658        PositionablePointView pv = new PositionablePointView(o, currentPoint, this);
5659        addLayoutTrack(o, pv);
5660
5661        setDirty();
5662    }
5663
5664    /**
5665     * Add a Track Segment
5666     */
5667    public void addTrackSegment() {
5668        // get unique name
5669        String name = finder.uniqueName("T", ++numTrackSegments);
5670
5671        // create object
5672        newTrack = new TrackSegment(name, beginTrack, beginHitPointType,
5673                foundTrack, foundHitPointType,
5674                leToolBarPanel.mainlineTrack.isSelected(), this);
5675
5676        TrackSegmentView tsv = new TrackSegmentView(
5677                newTrack,
5678                this
5679        );
5680        addLayoutTrack(newTrack, tsv);
5681
5682        setDirty();
5683
5684        // link to connected objects
5685        setLink(beginTrack, beginHitPointType, newTrack, HitPointType.TRACK);
5686        setLink(foundTrack, foundHitPointType, newTrack, HitPointType.TRACK);
5687
5688        // check on layout block
5689        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5690        if (newName == null) {
5691            newName = "";
5692        }
5693        LayoutBlock b = provideLayoutBlock(newName);
5694
5695        if (b != null) {
5696            newTrack.setLayoutBlock(b);
5697            getLEAuxTools().setBlockConnectivityChanged();
5698
5699            // check on occupancy sensor
5700            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5701            if (sensorName == null) {
5702                sensorName = "";
5703            }
5704
5705            if (!sensorName.isEmpty()) {
5706                if (!validateSensor(sensorName, b, this)) {
5707                    b.setOccupancySensorName("");
5708                } else {
5709                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5710                }
5711            }
5712            newTrack.updateBlockInfo();
5713        }
5714    }
5715
5716    /**
5717     * Add a Level Crossing
5718     */
5719    public void addLevelXing() {
5720        // get unique name
5721        String name = finder.uniqueName("X", ++numLevelXings);
5722
5723        // create object
5724        LevelXing o = new LevelXing(name, this);
5725        LevelXingView ov = new LevelXingView(o, currentPoint, this);
5726        addLayoutTrack(o, ov);
5727
5728        setDirty();
5729
5730        // check on layout block
5731        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5732        if (newName == null) {
5733            newName = "";
5734        }
5735        LayoutBlock b = provideLayoutBlock(newName);
5736
5737        if (b != null) {
5738            o.setLayoutBlockAC(b);
5739            o.setLayoutBlockBD(b);
5740
5741            // check on occupancy sensor
5742            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5743            if (sensorName == null) {
5744                sensorName = "";
5745            }
5746
5747            if (!sensorName.isEmpty()) {
5748                if (!validateSensor(sensorName, b, this)) {
5749                    b.setOccupancySensorName("");
5750                } else {
5751                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5752                }
5753            }
5754        }
5755    }
5756
5757    /**
5758     * Add a LayoutSlip
5759     *
5760     * @param type the slip type
5761     */
5762    public void addLayoutSlip(LayoutTurnout.TurnoutType type) {
5763        // get the rotation entry
5764        double rot;
5765        String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim();
5766
5767        if (s.isEmpty()) {
5768            rot = 0.0;
5769        } else {
5770            try {
5771                rot = IntlUtilities.doubleValue(s);
5772            } catch (ParseException e) {
5773                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " "
5774                        + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
5775
5776                return;
5777            }
5778        }
5779
5780        // get unique name
5781        String name = finder.uniqueName("SL", ++numLayoutSlips);
5782
5783        // create object
5784        LayoutSlip o;
5785        LayoutSlipView ov;
5786
5787        switch (type) {
5788            case DOUBLE_SLIP:
5789                LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this);
5790                o = lds;
5791                ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this);
5792                break;
5793            case SINGLE_SLIP:
5794                LayoutSingleSlip lss = new LayoutSingleSlip(name, this);
5795                o = lss;
5796                ov = new LayoutSingleSlipView(lss, currentPoint, rot, this);
5797                break;
5798            default:
5799                log.error("can't create slip {} with type {}", name, type);
5800                return; // without creating
5801        }
5802
5803        addLayoutTrack(o, ov);
5804
5805        setDirty();
5806
5807        // check on layout block
5808        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5809        if (newName == null) {
5810            newName = "";
5811        }
5812        LayoutBlock b = provideLayoutBlock(newName);
5813
5814        if (b != null) {
5815            ov.setLayoutBlock(b);
5816
5817            // check on occupancy sensor
5818            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5819            if (sensorName == null) {
5820                sensorName = "";
5821            }
5822
5823            if (!sensorName.isEmpty()) {
5824                if (!validateSensor(sensorName, b, this)) {
5825                    b.setOccupancySensorName("");
5826                } else {
5827                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5828                }
5829            }
5830        }
5831
5832        String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName();
5833        if (turnoutName == null) {
5834            turnoutName = "";
5835        }
5836
5837        if (validatePhysicalTurnout(turnoutName, this)) {
5838            // turnout is valid and unique.
5839            o.setTurnout(turnoutName);
5840
5841            if (o.getTurnout().getSystemName().equals(turnoutName)) {
5842                leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout());
5843            }
5844        } else {
5845            o.setTurnout("");
5846            leToolBarPanel.turnoutNameComboBox.setSelectedItem(null);
5847            leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1);
5848        }
5849        turnoutName = leToolBarPanel.extraTurnoutNameComboBox.getSelectedItemDisplayName();
5850        if (turnoutName == null) {
5851            turnoutName = "";
5852        }
5853
5854        if (validatePhysicalTurnout(turnoutName, this)) {
5855            // turnout is valid and unique.
5856            o.setTurnoutB(turnoutName);
5857
5858            if (o.getTurnoutB().getSystemName().equals(turnoutName)) {
5859                leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(o.getTurnoutB());
5860            }
5861        } else {
5862            o.setTurnoutB("");
5863            leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(null);
5864            leToolBarPanel.extraTurnoutNameComboBox.setSelectedIndex(-1);
5865        }
5866    }
5867
5868    /**
5869     * Add a Layout Turnout
5870     *
5871     * @param type the turnout type
5872     */
5873    public void addLayoutTurnout(LayoutTurnout.TurnoutType type) {
5874        // get the rotation entry
5875        double rot;
5876        String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim();
5877
5878        if (s.isEmpty()) {
5879            rot = 0.0;
5880        } else {
5881            try {
5882                rot = IntlUtilities.doubleValue(s);
5883            } catch (ParseException e) {
5884                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " "
5885                        + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
5886
5887                return;
5888            }
5889        }
5890
5891        // get unique name
5892        String name = finder.uniqueName("TO", ++numLayoutTurnouts);
5893
5894        // create object - check all types, although not clear all actually reach here
5895        LayoutTurnout o;
5896        LayoutTurnoutView ov;
5897
5898        switch (type) {
5899
5900            case RH_TURNOUT:
5901                LayoutRHTurnout lrht = new LayoutRHTurnout(name, this);
5902                o = lrht;
5903                ov = new LayoutRHTurnoutView(lrht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5904                break;
5905            case LH_TURNOUT:
5906                LayoutLHTurnout llht = new LayoutLHTurnout(name, this);
5907                o = llht;
5908                ov = new LayoutLHTurnoutView(llht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5909                break;
5910            case WYE_TURNOUT:
5911                LayoutWye lw = new LayoutWye(name, this);
5912                o = lw;
5913                ov = new LayoutWyeView(lw, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5914                break;
5915            case DOUBLE_XOVER:
5916                LayoutDoubleXOver ldx = new LayoutDoubleXOver(name, this);
5917                o = ldx;
5918                ov = new LayoutDoubleXOverView(ldx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5919                break;
5920            case RH_XOVER:
5921                LayoutRHXOver lrx = new LayoutRHXOver(name, this);
5922                o = lrx;
5923                ov = new LayoutRHXOverView(lrx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5924                break;
5925            case LH_XOVER:
5926                LayoutLHXOver llx = new LayoutLHXOver(name, this);
5927                o = llx;
5928                ov = new LayoutLHXOverView(llx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5929                break;
5930
5931            case DOUBLE_SLIP:
5932                LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this);
5933                o = lds;
5934                ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this);
5935                log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name);
5936                break;
5937            case SINGLE_SLIP:
5938                LayoutSingleSlip lss = new LayoutSingleSlip(name, this);
5939                o = lss;
5940                ov = new LayoutSingleSlipView(lss, currentPoint, rot, this);
5941                log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name);
5942                break;
5943
5944            default:
5945                log.error("can't create LayoutTrack {} with type {}", name, type);
5946                return; // without creating
5947        }
5948
5949        addLayoutTrack(o, ov);
5950
5951        setDirty();
5952
5953        // check on layout block
5954        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5955        if (newName == null) {
5956            newName = "";
5957        }
5958        LayoutBlock b = provideLayoutBlock(newName);
5959
5960        if (b != null) {
5961            ov.setLayoutBlock(b);
5962
5963            // check on occupancy sensor
5964            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5965            if (sensorName == null) {
5966                sensorName = "";
5967            }
5968
5969            if (!sensorName.isEmpty()) {
5970                if (!validateSensor(sensorName, b, this)) {
5971                    b.setOccupancySensorName("");
5972                } else {
5973                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5974                }
5975            }
5976        }
5977
5978        // set default continuing route Turnout State
5979        o.setContinuingSense(Turnout.CLOSED);
5980
5981        // check on a physical turnout
5982        String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName();
5983        if (turnoutName == null) {
5984            turnoutName = "";
5985        }
5986
5987        if (validatePhysicalTurnout(turnoutName, this)) {
5988            // turnout is valid and unique.
5989            o.setTurnout(turnoutName);
5990
5991            if (o.getTurnout().getSystemName().equals(turnoutName)) {
5992                leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout());
5993            }
5994        } else {
5995            o.setTurnout("");
5996            leToolBarPanel.turnoutNameComboBox.setSelectedItem(null);
5997            leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1);
5998        }
5999    }
6000
6001    /**
6002     * Validates that a physical turnout exists and is unique among Layout
6003     * Turnouts Returns true if valid turnout was entered, false otherwise
6004     *
6005     * @param inTurnoutName the (system or user) name of the turnout
6006     * @param inOpenPane    the pane over which to show dialogs (null to
6007     *                      suppress dialogs)
6008     * @return true if valid
6009     */
6010    public boolean validatePhysicalTurnout(
6011            @Nonnull String inTurnoutName,
6012            @CheckForNull Component inOpenPane) {
6013        // check if turnout name was entered
6014        if (inTurnoutName.isEmpty()) {
6015            // no turnout entered
6016            return false;
6017        }
6018
6019        // check that the unique turnout name corresponds to a defined physical turnout
6020        Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(inTurnoutName);
6021        if (t == null) {
6022            // There is no turnout corresponding to this name
6023            if (inOpenPane != null) {
6024                JmriJOptionPane.showMessageDialog(inOpenPane,
6025                        MessageFormat.format(Bundle.getMessage("Error8"), inTurnoutName),
6026                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
6027            }
6028            return false;
6029        }
6030
6031        log.debug("validatePhysicalTurnout('{}')", inTurnoutName);
6032        boolean result = true;  // assume success (optimist!)
6033
6034        // ensure that this turnout is unique among Layout Turnouts in this Layout
6035        for (LayoutTurnout lt : getLayoutTurnouts()) {
6036            t = lt.getTurnout();
6037            if (t != null) {
6038                String sname = t.getSystemName();
6039                String uname = t.getUserName();
6040                log.debug("{}: Turnout tested '{}' and '{}'.", lt.getName(), sname, uname);
6041                if ((sname.equals(inTurnoutName))
6042                        || ((uname != null) && (uname.equals(inTurnoutName)))) {
6043                    result = false;
6044                    break;
6045                }
6046            }
6047
6048            // Only check for the second turnout if the type is a double cross over
6049            // otherwise the second turnout is used to throw an additional turnout at
6050            // the same time.
6051            if (lt.isTurnoutTypeXover()) {
6052                t = lt.getSecondTurnout();
6053                if (t != null) {
6054                    String sname = t.getSystemName();
6055                    String uname = t.getUserName();
6056                    log.debug("{}: 2nd Turnout tested '{}' and '{}'.", lt.getName(), sname, uname);
6057                    if ((sname.equals(inTurnoutName))
6058                            || ((uname != null) && (uname.equals(inTurnoutName)))) {
6059                        result = false;
6060                        break;
6061                    }
6062                }
6063            }
6064        }
6065
6066        if (result) {   // only need to test slips if we haven't failed yet...
6067            // ensure that this turnout is unique among Layout slips in this Layout
6068            for (LayoutSlip sl : getLayoutSlips()) {
6069                t = sl.getTurnout();
6070                if (t != null) {
6071                    String sname = t.getSystemName();
6072                    String uname = t.getUserName();
6073                    log.debug("{}: slip Turnout tested '{}' and '{}'.", sl.getName(), sname, uname);
6074                    if ((sname.equals(inTurnoutName))
6075                            || ((uname != null) && (uname.equals(inTurnoutName)))) {
6076                        result = false;
6077                        break;
6078                    }
6079                }
6080
6081                t = sl.getTurnoutB();
6082                if (t != null) {
6083                    String sname = t.getSystemName();
6084                    String uname = t.getUserName();
6085                    log.debug("{}: slip Turnout B tested '{}' and '{}'.", sl.getName(), sname, uname);
6086                    if ((sname.equals(inTurnoutName))
6087                            || ((uname != null) && (uname.equals(inTurnoutName)))) {
6088                        result = false;
6089                        break;
6090                    }
6091                }
6092            }
6093        }
6094
6095        if (result) {   // only need to test Turntable turnouts if we haven't failed yet...
6096            // ensure that this turntable turnout is unique among turnouts in this Layout
6097            for (LayoutTurntable tt : getLayoutTurntables()) {
6098                for (LayoutTurntable.RayTrack ray : tt.getRayTrackList()) {
6099                    t = ray.getTurnout();
6100                    if (t != null) {
6101                        String sname = t.getSystemName();
6102                        String uname = t.getUserName();
6103                        log.debug("{}: Turntable turnout tested '{}' and '{}'.", ray.getTurnoutName(), sname, uname);
6104                        if ((sname.equals(inTurnoutName))
6105                                || ((uname != null) && (uname.equals(inTurnoutName)))) {
6106                            result = false;
6107                            break;
6108                        }
6109                    }
6110                }
6111            }
6112        }
6113
6114        if (result) {   // only need to test Traverser turnouts if we haven't failed yet...
6115            // ensure that this traverser turnout is unique among turnouts in this Layout
6116            for (LayoutTraverser tt : getLayoutTraversers()) {
6117                for (LayoutTraverser.SlotTrack ray : tt.getSlotList()) {
6118                    t = ray.getTurnout();
6119                    if (t != null) {
6120                        String sname = t.getSystemName();
6121                        String uname = t.getUserName();
6122                        log.debug("{}: Traverser turnout tested '{}' and '{}'.", ray.getTurnoutName(), sname, uname);
6123                        if ((sname.equals(inTurnoutName))
6124                                || ((uname != null) && (uname.equals(inTurnoutName)))) {
6125                            result = false;
6126                            break;
6127                        }
6128                    }
6129                }
6130            }
6131        }
6132
6133        if (!result && (inOpenPane != null)) {
6134            JmriJOptionPane.showMessageDialog(inOpenPane,
6135                    MessageFormat.format(Bundle.getMessage("Error4"), inTurnoutName),
6136                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
6137        }
6138        return result;
6139    }
6140
6141    /**
6142     * link the 'from' object and type to the 'to' object and type
6143     *
6144     * @param fromObject    the object to link from
6145     * @param fromPointType the object type to link from
6146     * @param toObject      the object to link to
6147     * @param toPointType   the object type to link to
6148     */
6149    public void setLink(@Nonnull LayoutTrack fromObject, HitPointType fromPointType,
6150            @Nonnull LayoutTrack toObject, HitPointType toPointType) {
6151        switch (fromPointType) {
6152            case POS_POINT: {
6153                if ((toPointType == HitPointType.TRACK) && (fromObject instanceof PositionablePoint)) {
6154                    ((PositionablePoint) fromObject).setTrackConnection((TrackSegment) toObject);
6155                } else {
6156                    log.error("Attempt to link a non-TRACK connection ('{}')to a Positionable Point ('{}')",
6157                            toObject.getName(), fromObject.getName());
6158                }
6159                break;
6160            }
6161
6162            case TURNOUT_A:
6163            case TURNOUT_B:
6164            case TURNOUT_C:
6165            case TURNOUT_D:
6166            case SLIP_A:
6167            case SLIP_B:
6168            case SLIP_C:
6169            case SLIP_D:
6170            case LEVEL_XING_A:
6171            case LEVEL_XING_B:
6172            case LEVEL_XING_C:
6173            case LEVEL_XING_D: {
6174                try {
6175                    fromObject.setConnection(fromPointType, toObject, toPointType);
6176                } catch (JmriException e) {
6177                    // ignore (log.error in setConnection method)
6178                }
6179                break;
6180            }
6181
6182            case TRACK: {
6183                // should never happen, Track Segment links are set in ctor
6184                log.error("Illegal request to set a Track Segment link");
6185                break;
6186            }
6187
6188            default: {
6189                if (HitPointType.isTurntableRayHitType(fromPointType) && (fromObject instanceof LayoutTurntable)) {
6190                    if (toObject instanceof TrackSegment) {
6191                        ((LayoutTurntable) fromObject).setRayConnect((TrackSegment) toObject,
6192                                fromPointType.turntableTrackIndex());
6193                    } else {
6194                        log.warn("setLink found expected toObject type {} with fromPointType {} fromObject type {}",
6195                                toObject.getClass(), fromPointType, fromObject.getClass(), new Exception("traceback"));
6196                    }
6197                } else if (HitPointType.isTraverserSlotHitType(fromPointType) && (fromObject instanceof LayoutTraverser)) {
6198                    if (toObject instanceof TrackSegment) {
6199                        ((LayoutTraverser) fromObject).setSlotConnect((TrackSegment) toObject,
6200                                fromPointType.traverserTrackIndex());
6201                    } else {
6202                        log.warn("setLink found expected toObject type {} with fromPointType {} fromObject type {}",
6203                                toObject.getClass(), fromPointType, fromObject.getClass(), new Exception("traceback"));
6204                    }
6205                } else {
6206                    log.warn("setLink found expected fromObject type {} with fromPointType {} toObject type {}",
6207                            fromObject.getClass(), fromPointType, toObject.getClass(), new Exception("traceback"));
6208                }
6209                break;
6210            }
6211        }
6212    }
6213
6214    /**
6215     * Return a layout block with the entered name, creating a new one if
6216     * needed. Note that the entered name becomes the user name of the
6217     * LayoutBlock, and a system name is automatically created by
6218     * LayoutBlockManager if needed.
6219     * <p>
6220     * If the block name is a system name, then the user will have to supply a
6221     * user name for the block.
6222     * <p>
6223     * Some, but not all, errors pop a Swing error dialog in addition to
6224     * logging.
6225     *
6226     * @param inBlockName the entered name
6227     * @return the provided LayoutBlock
6228     */
6229    public LayoutBlock provideLayoutBlock(@Nonnull String inBlockName) {
6230        LayoutBlock result = null; // assume failure (pessimist!)
6231        LayoutBlock newBlk = null; // assume failure (pessimist!)
6232
6233        if (inBlockName.isEmpty()) {
6234            // nothing entered, try autoAssign
6235            if (autoAssignBlocks) {
6236                newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock();
6237                if (null == newBlk) {
6238                    log.error("provideLayoutBlock: Failure to auto-assign for empty LayoutBlock name");
6239                }
6240            } else {
6241                log.debug("provideLayoutBlock: no name given and not assigning auto block names");
6242            }
6243        } else {
6244            // check if this Layout Block already exists
6245            result = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(inBlockName);
6246            if (result == null) { //(no)
6247                // The combo box name can be either a block system name or a block user name
6248                Block checkBlock = InstanceManager.getDefault(BlockManager.class).getBlock(inBlockName);
6249                if (checkBlock == null) {
6250                    log.error("provideLayoutBlock: The block name '{}' does not return a block.", inBlockName);
6251                } else {
6252                    String checkUserName = checkBlock.getUserName();
6253                    if (checkUserName != null && checkUserName.equals(inBlockName)) {
6254                        // Go ahead and use the name for the layout block
6255                        newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, inBlockName);
6256                        if (newBlk == null) {
6257                            log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}'.", inBlockName);
6258                        }
6259                    } else {
6260                        // Appears to be a system name, request a user name
6261                        String blkUserName = (String)JmriJOptionPane.showInputDialog(getTargetFrame(),
6262                                Bundle.getMessage("BlkUserNameMsg"),
6263                                Bundle.getMessage("BlkUserNameTitle"),
6264                                JmriJOptionPane.PLAIN_MESSAGE, null, null, "");
6265                        if (blkUserName != null && !blkUserName.isEmpty()) {
6266                            // Verify the user name
6267                            Block checkDuplicate = InstanceManager.getDefault(BlockManager.class).getByUserName(blkUserName);
6268                            if (checkDuplicate != null) {
6269                                JmriJOptionPane.showMessageDialog(getTargetFrame(),
6270                                        Bundle.getMessage("BlkUserNameInUse", blkUserName),
6271                                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
6272                            } else {
6273                                // OK to use as a block user name
6274                                checkBlock.setUserName(blkUserName);
6275                                newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, blkUserName);
6276                                if (newBlk == null) {
6277                                    log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}' with a new user name.", blkUserName);
6278                                }
6279                            }
6280                        }
6281                    }
6282                }
6283            }
6284        }
6285
6286        // if we created a new block
6287        if (newBlk != null) {
6288            // initialize the new block
6289            // log.debug("provideLayoutBlock :: Init new block {}", inBlockName);
6290            newBlk.initializeLayoutBlock();
6291            newBlk.initializeLayoutBlockRouting();
6292            newBlk.setBlockTrackColor(defaultTrackColor);
6293            newBlk.setBlockOccupiedColor(defaultOccupiedTrackColor);
6294            newBlk.setBlockExtraColor(defaultAlternativeTrackColor);
6295            result = newBlk;
6296        }
6297
6298        if (result != null) {
6299            // set both new and previously existing block
6300            result.addLayoutEditor(this);
6301            result.incrementUse();
6302            setDirty();
6303        }
6304        return result;
6305    }
6306
6307    /**
6308     * Validates that the supplied occupancy sensor name corresponds to an
6309     * existing sensor and is unique among all blocks. If valid, returns true
6310     * and sets the block sensor name in the block. Else returns false, and does
6311     * nothing to the block.
6312     *
6313     * @param sensorName the sensor name to validate
6314     * @param blk        the LayoutBlock in which to set it
6315     * @param openFrame  the frame (Component) it is in
6316     * @return true if sensor is valid
6317     */
6318    public boolean validateSensor(
6319            @Nonnull String sensorName,
6320            @Nonnull LayoutBlock blk,
6321            @Nonnull Component openFrame) {
6322        boolean result = false; // assume failure (pessimist!)
6323
6324        // check if anything entered
6325        if (!sensorName.isEmpty()) {
6326            // get a validated sensor corresponding to this name and assigned to block
6327            if (blk.getOccupancySensorName().equals(sensorName)) {
6328                result = true;
6329            } else {
6330                Sensor s = blk.validateSensor(sensorName, openFrame);
6331                result = (s != null); // if sensor returned result is true.
6332            }
6333        }
6334        return result;
6335    }
6336
6337    /**
6338     * Return a layout block with the given name if one exists. Registers this
6339     * LayoutEditor with the layout block. This method is designed to be used
6340     * when a panel is loaded. The calling method must handle whether the use
6341     * count should be incremented.
6342     *
6343     * @param blockID the given name
6344     * @return null if blockID does not already exist
6345     */
6346    public LayoutBlock getLayoutBlock(@Nonnull String blockID) {
6347        // check if this Layout Block already exists
6348        LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(blockID);
6349        if (blk == null) {
6350            log.error("LayoutBlock '{}' not found when panel loaded", blockID);
6351            return null;
6352        }
6353        blk.addLayoutEditor(this);
6354        return blk;
6355    }
6356
6357    /**
6358     * Remove object from all Layout Editor temporary lists of items not part of
6359     * track schematic
6360     *
6361     * @param s the object to remove
6362     * @return true if found
6363     */
6364    private boolean remove(@Nonnull Positionable s) {
6365        boolean found = false;
6366
6367        if (backgroundImage.contains(s)) {
6368            backgroundImage.remove(s);
6369            found = true;
6370        }
6371        if (memoryLabelList.contains(s)) {
6372            memoryLabelList.remove(s);
6373            found = true;
6374        }
6375        if (memoryInputList.contains(s)) {
6376            memoryInputList.remove(s);
6377            found = true;
6378        }
6379        if (globalVariableLabelList.contains(s)) {
6380            globalVariableLabelList.remove(s);
6381            found = true;
6382        }
6383        if (blockContentsLabelList.contains(s)) {
6384            blockContentsLabelList.remove(s);
6385            found = true;
6386        }
6387        if (blockContentsInputList.contains(s)) {
6388            blockContentsInputList.remove(s);
6389            found = true;
6390        }
6391        if (multiSensors.contains(s)) {
6392            multiSensors.remove(s);
6393            found = true;
6394        }
6395        if (clocks.contains(s)) {
6396            clocks.remove(s);
6397            found = true;
6398        }
6399        if (labelImage.contains(s)) {
6400            labelImage.remove(s);
6401            found = true;
6402        }
6403
6404        if (sensorImage.contains(s) || sensorList.contains(s)) {
6405            Sensor sensor = ((SensorIcon) s).getSensor();
6406            if (sensor != null) {
6407                if (removeAttachedBean((sensor))) {
6408                    sensorImage.remove(s);
6409                    sensorList.remove(s);
6410                    found = true;
6411                } else {
6412                    return false;
6413                }
6414            }
6415        }
6416
6417        if (turnoutImage.contains(s) || turnoutList.contains(s)) {
6418            Turnout turnout = ((TurnoutIcon) s).getTurnout();
6419            if (turnout != null) {
6420                if (removeAttachedBean((turnout))) {
6421                    turnoutImage.remove(s);
6422                    turnoutList.remove(s);
6423                    found = true;
6424                } else {
6425                    return false;
6426                }
6427            }
6428        }
6429
6430        if (signalHeadImage.contains(s) || signalList.contains(s)) {
6431            SignalHead head = ((SignalHeadIcon) s).getSignalHead();
6432            if (head != null) {
6433                if (removeAttachedBean((head))) {
6434                    signalHeadImage.remove(s);
6435                    signalList.remove(s);
6436                    found = true;
6437                } else {
6438                    return false;
6439                }
6440            }
6441        }
6442
6443        if (signalMastList.contains(s)) {
6444            SignalMast mast = ((SignalMastIcon) s).getSignalMast();
6445            if (mast != null) {
6446                if (removeAttachedBean((mast))) {
6447                    signalMastList.remove(s);
6448                    found = true;
6449                } else {
6450                    return false;
6451                }
6452            }
6453        }
6454
6455        super.removeFromContents(s);
6456
6457        if (found) {
6458            setDirty();
6459            redrawPanel();
6460        }
6461        return found;
6462    }
6463
6464    @Override
6465    public boolean removeFromContents(@Nonnull Positionable l) {
6466        return remove(l);
6467    }
6468
6469    private String findBeanUsage(@Nonnull NamedBean bean) {
6470        PositionablePoint pe;
6471        PositionablePoint pw;
6472        LayoutTurnout lt;
6473        LevelXing lx;
6474        LayoutSlip ls;
6475        boolean found = false;
6476        StringBuilder sb = new StringBuilder();
6477        String msgKey = "DeleteReference";  // NOI18N
6478        String beanKey = "None";  // NOI18N
6479        String beanValue = bean.getDisplayName();
6480
6481        if (bean instanceof SignalMast) {
6482            beanKey = "BeanNameSignalMast";  // NOI18N
6483
6484            if (InstanceManager.getDefault(SignalMastLogicManager.class).isSignalMastUsed((SignalMast) bean)) {
6485                SignalMastLogic sml = InstanceManager.getDefault(
6486                        SignalMastLogicManager.class).getSignalMastLogic((SignalMast) bean);
6487                if ((sml != null) && sml.useLayoutEditor(sml.getDestinationList().get(0))) {
6488                    msgKey = "DeleteSmlReference";  // NOI18N
6489                }
6490            }
6491        } else if (bean instanceof Sensor) {
6492            beanKey = "BeanNameSensor";  // NOI18N
6493        } else if (bean instanceof Turnout) {
6494            beanKey = "BeanNameTurnout";  // NOI18N
6495        } else if (bean instanceof SignalHead) {
6496            beanKey = "BeanNameSignalHead";  // NOI18N
6497        }
6498        if (!beanKey.equals("None")) {  // NOI18N
6499            sb.append(Bundle.getMessage(msgKey, Bundle.getMessage(beanKey), beanValue));
6500        }
6501
6502        if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) {
6503            TrackSegment t1 = pw.getConnect1();
6504            TrackSegment t2 = pw.getConnect2();
6505            if (t1 != null) {
6506                if (t2 != null) {
6507                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6508                    sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName()));  // NOI18N
6509                } else {
6510                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6511                }
6512            }
6513            found = true;
6514        }
6515
6516        if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) {
6517            TrackSegment t1 = pe.getConnect1();
6518            TrackSegment t2 = pe.getConnect2();
6519
6520            if (t1 != null) {
6521                if (t2 != null) {
6522                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6523                    sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName()));  // NOI18N
6524                } else {
6525                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6526                }
6527            }
6528            found = true;
6529        }
6530
6531        if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) {
6532            sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("BeanNameTurnout"), lt.getTurnoutName()));   // NOI18N
6533            found = true;
6534        }
6535
6536        if ((lx = finder.findLevelXingByBean(bean)) != null) {
6537            sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("LevelCrossing"), lx.getId()));   // NOI18N
6538            found = true;
6539        }
6540
6541        if ((ls = finder.findLayoutSlipByBean(bean)) != null) {
6542            sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("Slip"), ls.getTurnoutName()));   // NOI18N
6543            found = true;
6544        }
6545
6546        if (!found) {
6547            return null;
6548        }
6549        return sb.toString();
6550    }
6551
6552    /**
6553     * NX Sensors, Signal Heads and Signal Masts can be attached to positional
6554     * points, turnouts and level crossings. If an attachment exists, present an
6555     * option to cancel the remove action, remove the attachement or retain the
6556     * attachment.
6557     *
6558     * @param bean The named bean to be removed.
6559     * @return true if OK to remove the related icon.
6560     */
6561    private boolean removeAttachedBean(@Nonnull NamedBean bean) {
6562        String usage = findBeanUsage(bean);
6563
6564        if (usage != null) {
6565            usage = String.format("<html>%s</html>", usage);
6566            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6567                    usage, Bundle.getMessage("WarningTitle"),
6568                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6569                    new Object[]{Bundle.getMessage("ButtonYes"),
6570                        Bundle.getMessage("ButtonNo"),
6571                        Bundle.getMessage("ButtonCancel")},
6572                    Bundle.getMessage("ButtonYes"));
6573
6574            if (selectedValue == 1 ) { // array pos 1, No
6575                return true; // return leaving the references in place but allow the icon to be deleted.
6576            }
6577            // array pos 2, cancel or Dialog closed
6578            if (selectedValue == 2 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
6579                return false; // do not delete the item
6580            }
6581            if (bean instanceof Sensor) {
6582                // Additional actions for NX sensor pairs
6583                return getLETools().removeSensorAssignment((Sensor) bean);
6584            } else {
6585                removeBeanRefs(bean);
6586            }
6587        }
6588        return true;
6589    }
6590
6591    private void removeBeanRefs(@Nonnull NamedBean bean) {
6592        PositionablePoint pe;
6593        PositionablePoint pw;
6594        LayoutTurnout lt;
6595        LevelXing lx;
6596        LayoutSlip ls;
6597
6598        if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) {
6599            pw.removeBeanReference(bean);
6600        }
6601
6602        if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) {
6603            pe.removeBeanReference(bean);
6604        }
6605
6606        if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) {
6607            lt.removeBeanReference(bean);
6608        }
6609
6610        if ((lx = finder.findLevelXingByBean(bean)) != null) {
6611            lx.removeBeanReference(bean);
6612        }
6613
6614        if ((ls = finder.findLayoutSlipByBean(bean)) != null) {
6615            ls.removeBeanReference(bean);
6616        }
6617    }
6618
6619    private boolean noWarnPositionablePoint = false;
6620
6621    /**
6622     * Remove a PositionablePoint -- an Anchor or an End Bumper.
6623     *
6624     * @param o the PositionablePoint to remove
6625     * @return true if removed
6626     */
6627    public boolean removePositionablePoint(@Nonnull PositionablePoint o) {
6628        // First verify with the user that this is really wanted, only show message if there is a bit of track connected
6629        if ((o.getConnect1() != null) || (o.getConnect2() != null)) {
6630            if (!noWarnPositionablePoint) {
6631                int selectedValue = JmriJOptionPane.showOptionDialog(this,
6632                        Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"),
6633                        JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6634                        new Object[]{Bundle.getMessage("ButtonYes"),
6635                            Bundle.getMessage("ButtonNo"),
6636                            Bundle.getMessage("ButtonYesPlus")},
6637                        Bundle.getMessage("ButtonNo"));
6638
6639                // array position 1, ButtonNo , or Dialog Closed.
6640                if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
6641                    return false; // return without creating if "No" response
6642                }
6643
6644                if (selectedValue == 2) { // array position 2, ButtonYesPlus
6645                    // Suppress future warnings, and continue
6646                    noWarnPositionablePoint = true;
6647                }
6648            }
6649
6650            // remove from selection information
6651            if (selectedObject == o) {
6652                selectedObject = null;
6653            }
6654
6655            if (prevSelectedObject == o) {
6656                prevSelectedObject = null;
6657            }
6658
6659            // remove connections if any
6660            TrackSegment t1 = o.getConnect1();
6661            TrackSegment t2 = o.getConnect2();
6662
6663            if (t1 != null) {
6664                removeTrackSegment(t1);
6665            }
6666
6667            if (t2 != null) {
6668                removeTrackSegment(t2);
6669            }
6670
6671            // delete from array
6672        }
6673
6674        return removeLayoutTrackAndRedraw(o);
6675    }
6676
6677    private boolean noWarnLayoutTurnout = false;
6678
6679    /**
6680     * Remove a LayoutTurnout
6681     *
6682     * @param o the LayoutTurnout to remove
6683     * @return true if removed
6684     */
6685    public boolean removeLayoutTurnout(@Nonnull LayoutTurnout o) {
6686        // First verify with the user that this is really wanted
6687        if (!noWarnLayoutTurnout) {
6688            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6689                    Bundle.getMessage("Question1r"), Bundle.getMessage("WarningTitle"),
6690                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6691                    new Object[]{Bundle.getMessage("ButtonYes"),
6692                        Bundle.getMessage("ButtonNo"),
6693                        Bundle.getMessage("ButtonYesPlus")},
6694                    Bundle.getMessage("ButtonNo"));
6695
6696            // return without removing if array position 1 "No" response or Dialog closed
6697            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6698                return false;
6699            }
6700
6701            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
6702                // Suppress future warnings, and continue
6703                noWarnLayoutTurnout = true;
6704            }
6705        }
6706
6707        // remove from selection information
6708        if (selectedObject == o) {
6709            selectedObject = null;
6710        }
6711
6712        if (prevSelectedObject == o) {
6713            prevSelectedObject = null;
6714        }
6715
6716        // remove connections if any
6717        TrackSegment t = (TrackSegment) o.getConnectA();
6718
6719        if (t != null) {
6720            substituteAnchor(getLayoutTurnoutView(o).getCoordsA(), o, t);
6721        }
6722        t = (TrackSegment) o.getConnectB();
6723
6724        if (t != null) {
6725            substituteAnchor(getLayoutTurnoutView(o).getCoordsB(), o, t);
6726        }
6727        t = (TrackSegment) o.getConnectC();
6728
6729        if (t != null) {
6730            substituteAnchor(getLayoutTurnoutView(o).getCoordsC(), o, t);
6731        }
6732        t = (TrackSegment) o.getConnectD();
6733
6734        if (t != null) {
6735            substituteAnchor(getLayoutTurnoutView(o).getCoordsD(), o, t);
6736        }
6737
6738        // decrement Block use count(s)
6739        LayoutBlock b = o.getLayoutBlock();
6740
6741        if (b != null) {
6742            b.decrementUse();
6743        }
6744
6745        if (o.isTurnoutTypeXover() || o.isTurnoutTypeSlip()) {
6746            LayoutBlock b2 = o.getLayoutBlockB();
6747
6748            if ((b2 != null) && (b2 != b)) {
6749                b2.decrementUse();
6750            }
6751            LayoutBlock b3 = o.getLayoutBlockC();
6752
6753            if ((b3 != null) && (b3 != b) && (b3 != b2)) {
6754                b3.decrementUse();
6755            }
6756            LayoutBlock b4 = o.getLayoutBlockD();
6757
6758            if ((b4 != null) && (b4 != b)
6759                    && (b4 != b2) && (b4 != b3)) {
6760                b4.decrementUse();
6761            }
6762        }
6763
6764        return removeLayoutTrackAndRedraw(o);
6765    }
6766
6767    private void substituteAnchor(@Nonnull Point2D loc,
6768            @Nonnull LayoutTrack o, @Nonnull TrackSegment t) {
6769        PositionablePoint p = addAnchor(loc);
6770
6771        if (t.getConnect1() == o) {
6772            t.setNewConnect1(p, HitPointType.POS_POINT);
6773        }
6774
6775        if (t.getConnect2() == o) {
6776            t.setNewConnect2(p, HitPointType.POS_POINT);
6777        }
6778        p.setTrackConnection(t);
6779    }
6780
6781    private boolean noWarnLevelXing = false;
6782
6783    /**
6784     * Remove a Level Crossing
6785     *
6786     * @param o the LevelXing to remove
6787     * @return true if removed
6788     */
6789    public boolean removeLevelXing(@Nonnull LevelXing o) {
6790        // First verify with the user that this is really wanted
6791        if (!noWarnLevelXing) {
6792            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6793                    Bundle.getMessage("Question3r"), Bundle.getMessage("WarningTitle"),
6794                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6795                    new Object[]{Bundle.getMessage("ButtonYes"),
6796                        Bundle.getMessage("ButtonNo"),
6797                        Bundle.getMessage("ButtonYesPlus")},
6798                    Bundle.getMessage("ButtonNo"));
6799
6800             // array position 1 Button No, or Dialog closed.
6801            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6802                return false;
6803            }
6804
6805            if (selectedValue == 2 ) { // array position 2 ButtonYesPlus
6806                // Suppress future warnings, and continue
6807                noWarnLevelXing = true;
6808            }
6809        }
6810
6811        // remove from selection information
6812        if (selectedObject == o) {
6813            selectedObject = null;
6814        }
6815
6816        if (prevSelectedObject == o) {
6817            prevSelectedObject = null;
6818        }
6819
6820        // remove connections if any
6821        LevelXingView ov = getLevelXingView(o);
6822
6823        TrackSegment t = (TrackSegment) o.getConnectA();
6824        if (t != null) {
6825            substituteAnchor(ov.getCoordsA(), o, t);
6826        }
6827        t = (TrackSegment) o.getConnectB();
6828
6829        if (t != null) {
6830            substituteAnchor(ov.getCoordsB(), o, t);
6831        }
6832        t = (TrackSegment) o.getConnectC();
6833
6834        if (t != null) {
6835            substituteAnchor(ov.getCoordsC(), o, t);
6836        }
6837        t = (TrackSegment) o.getConnectD();
6838
6839        if (t != null) {
6840            substituteAnchor(ov.getCoordsD(), o, t);
6841        }
6842
6843        // decrement block use count if any blocks in use
6844        LayoutBlock lb = o.getLayoutBlockAC();
6845
6846        if (lb != null) {
6847            lb.decrementUse();
6848        }
6849        LayoutBlock lbx = o.getLayoutBlockBD();
6850
6851        if ((lbx != null) && (lb != null) && (lbx != lb)) {
6852            lb.decrementUse();
6853        }
6854
6855        return removeLayoutTrackAndRedraw(o);
6856    }
6857
6858    private boolean noWarnSlip = false;
6859
6860    /**
6861     * Remove a slip
6862     *
6863     * @param o the LayoutSlip to remove
6864     * @return true if removed
6865     */
6866    public boolean removeLayoutSlip(@Nonnull LayoutTurnout o) {
6867        if (!(o instanceof LayoutSlip)) {
6868            return false;
6869        }
6870
6871        // First verify with the user that this is really wanted
6872        if (!noWarnSlip) {
6873            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6874                    Bundle.getMessage("Question5r"), Bundle.getMessage("WarningTitle"),
6875                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6876                    new Object[]{Bundle.getMessage("ButtonYes"),
6877                        Bundle.getMessage("ButtonNo"),
6878                        Bundle.getMessage("ButtonYesPlus")},
6879                    Bundle.getMessage("ButtonNo"));
6880
6881             // return without removing if array position 1 "No" response or Dialog closed
6882            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6883                return false;
6884            }
6885
6886            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
6887                // Suppress future warnings, and continue
6888                noWarnSlip = true;
6889            }
6890        }
6891
6892        LayoutTurnoutView ov = getLayoutTurnoutView(o);
6893
6894        // remove from selection information
6895        if (selectedObject == o) {
6896            selectedObject = null;
6897        }
6898
6899        if (prevSelectedObject == o) {
6900            prevSelectedObject = null;
6901        }
6902
6903        // remove connections if any
6904        TrackSegment t = (TrackSegment) o.getConnectA();
6905
6906        if (t != null) {
6907            substituteAnchor(ov.getCoordsA(), o, t);
6908        }
6909        t = (TrackSegment) o.getConnectB();
6910
6911        if (t != null) {
6912            substituteAnchor(ov.getCoordsB(), o, t);
6913        }
6914        t = (TrackSegment) o.getConnectC();
6915
6916        if (t != null) {
6917            substituteAnchor(ov.getCoordsC(), o, t);
6918        }
6919        t = (TrackSegment) o.getConnectD();
6920
6921        if (t != null) {
6922            substituteAnchor(ov.getCoordsD(), o, t);
6923        }
6924
6925        // decrement block use count if any blocks in use
6926        LayoutBlock lb = o.getLayoutBlock();
6927
6928        if (lb != null) {
6929            lb.decrementUse();
6930        }
6931
6932        return removeLayoutTrackAndRedraw(o);
6933    }
6934
6935    private boolean noWarnTurntable = false;
6936    private boolean noWarnTraverser = false;
6937
6938    /**
6939     * Remove a Layout Turntable
6940     *
6941     * @param o the LayoutTurntable to remove
6942     * @return true if removed
6943     */
6944    public boolean removeTurntable(@Nonnull LayoutTurntable o) {
6945        // First verify with the user that this is really wanted
6946        if (!noWarnTurntable) {
6947            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6948                    Bundle.getMessage("Question4r"), Bundle.getMessage("WarningTitle"),
6949                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6950                    new Object[]{Bundle.getMessage("ButtonYes"),
6951                        Bundle.getMessage("ButtonNo"),
6952                        Bundle.getMessage("ButtonYesPlus")},
6953                    Bundle.getMessage("ButtonNo"));
6954
6955            // return without removing if array position 1 "No" response or Dialog closed
6956            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6957                return false;
6958            }
6959
6960            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
6961                // Suppress future warnings, and continue
6962                noWarnTurntable = true;
6963            }
6964        }
6965
6966        // Check if removing the turntable will cause errors.
6967        if (!o.isRemoveAllowed()) {
6968            return false;
6969        }
6970
6971        // remove from selection information
6972        if (selectedObject == o) {
6973            selectedObject = null;
6974        }
6975
6976        if (prevSelectedObject == o) {
6977            prevSelectedObject = null;
6978        }
6979
6980        // remove connections if any
6981        LayoutTurntableView ov = getLayoutTurntableView(o);
6982        for (int j = 0; j < o.getNumberRays(); j++) {
6983            TrackSegment t = ov.getRayConnectOrdered(j);
6984
6985            if (t != null) {
6986                substituteAnchor(ov.getRayCoordsIndexed(j), o, t);
6987            }
6988        }
6989
6990        return removeLayoutTrackAndRedraw(o);
6991    }
6992
6993    /**
6994     * Remove a Layout Traverser
6995     *
6996     * @param o the LayoutTraverser to remove
6997     * @return true if removed
6998     */
6999    public boolean removeTraverser(@Nonnull LayoutTraverser o) {
7000        // First verify with the user that this is really wanted
7001        if (!noWarnTraverser) {
7002            int selectedValue = JmriJOptionPane.showOptionDialog(this,
7003                    Bundle.getMessage("Question8r"), Bundle.getMessage("WarningTitle"),
7004                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
7005                    new Object[]{Bundle.getMessage("ButtonYes"),
7006                            Bundle.getMessage("ButtonNo"),
7007                            Bundle.getMessage("ButtonYesPlus")},
7008                    Bundle.getMessage("ButtonNo"));
7009
7010            // return without removing if array position 1 "No" response or Dialog closed
7011            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
7012                return false;
7013            }
7014
7015            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
7016                // Suppress future warnings, and continue
7017                noWarnTraverser = true;
7018            }
7019        }
7020
7021        // Check if removing the traverser will cause errors.
7022        if (!o.isRemoveAllowed()) {
7023            return false;
7024        }
7025
7026        // remove from selection information
7027        if (selectedObject == o) {
7028            selectedObject = null;
7029        }
7030
7031        if (prevSelectedObject == o) {
7032            prevSelectedObject = null;
7033        }
7034
7035        // remove connections if any
7036        LayoutTraverserView ov = getLayoutTraverserView(o);
7037        for (int j = 0; j < o.getNumberSlots(); j++) {
7038            TrackSegment t = ov.getSlotConnectOrdered(j);
7039            if (t != null) {
7040                substituteAnchor(ov.getSlotCoordsIndexed(j), o, t);
7041            }
7042        }
7043        return removeLayoutTrackAndRedraw(o);
7044    }
7045
7046    /**
7047     * Remove a Track Segment
7048     *
7049     * @param o the TrackSegment to remove
7050     */
7051    public void removeTrackSegment(@Nonnull TrackSegment o) {
7052        // save affected blocks
7053        LayoutBlock block1 = null;
7054        LayoutBlock block2 = null;
7055        LayoutBlock block = o.getLayoutBlock();
7056
7057        // remove any connections
7058        HitPointType type = o.getType1();
7059
7060        if (type == HitPointType.POS_POINT) {
7061            PositionablePoint p = (PositionablePoint) (o.getConnect1());
7062
7063            if (p != null) {
7064                p.removeTrackConnection(o);
7065
7066                if (p.getConnect1() != null) {
7067                    block1 = p.getConnect1().getLayoutBlock();
7068                } else if (p.getConnect2() != null) {
7069                    block1 = p.getConnect2().getLayoutBlock();
7070                }
7071            }
7072        } else {
7073            block1 = getAffectedBlock(o.getConnect1(), type);
7074            disconnect(o.getConnect1(), type);
7075        }
7076        type = o.getType2();
7077
7078        if (type == HitPointType.POS_POINT) {
7079            PositionablePoint p = (PositionablePoint) (o.getConnect2());
7080
7081            if (p != null) {
7082                p.removeTrackConnection(o);
7083
7084                if (p.getConnect1() != null) {
7085                    block2 = p.getConnect1().getLayoutBlock();
7086                } else if (p.getConnect2() != null) {
7087                    block2 = p.getConnect2().getLayoutBlock();
7088                }
7089            }
7090        } else {
7091            block2 = getAffectedBlock(o.getConnect2(), type);
7092            disconnect(o.getConnect2(), type);
7093        }
7094
7095        // delete from array
7096        removeLayoutTrack(o);
7097
7098        // update affected blocks
7099        if (block != null) {
7100            // decrement Block use count
7101            block.decrementUse();
7102            getLEAuxTools().setBlockConnectivityChanged();
7103            block.updatePaths();
7104        }
7105
7106        if ((block1 != null) && (block1 != block)) {
7107            block1.updatePaths();
7108        }
7109
7110        if ((block2 != null) && (block2 != block) && (block2 != block1)) {
7111            block2.updatePaths();
7112        }
7113
7114        //
7115        setDirty();
7116        redrawPanel();
7117    }
7118
7119    private void disconnect(@Nonnull LayoutTrack o, HitPointType type) {
7120        switch (type) {
7121            case TURNOUT_A:
7122            case TURNOUT_B:
7123            case TURNOUT_C:
7124            case TURNOUT_D:
7125            case SLIP_A:
7126            case SLIP_B:
7127            case SLIP_C:
7128            case SLIP_D:
7129            case LEVEL_XING_A:
7130            case LEVEL_XING_B:
7131            case LEVEL_XING_C:
7132            case LEVEL_XING_D: {
7133                try {
7134                    o.setConnection(type, null, HitPointType.NONE);
7135                } catch (JmriException e) {
7136                    // ignore (log.error in setConnection method)
7137                }
7138                break;
7139            }
7140
7141            default: {
7142                if (HitPointType.isTurntableRayHitType(type)) {
7143                    ((LayoutTurntable) o).setRayConnect(null, type.turntableTrackIndex());
7144                }
7145                if (HitPointType.isTraverserSlotHitType(type)) {
7146                    ((LayoutTraverser) o).setSlotConnect(null, type.traverserTrackIndex());
7147                }
7148                break;
7149            }
7150        }
7151    }
7152
7153    /**
7154     * Depending on the given type, and the real class of the given LayoutTrack,
7155     * determine the connected LayoutTrack. This provides a variable-indirect
7156     * form of e.g. trk.getLayoutBlockC() for example. Perhaps "Connected Block"
7157     * captures the idea better, but that method name is being used for
7158     * something else.
7159     *
7160     *
7161     * @param track The track who's connected blocks are being examined
7162     * @param type  This point to check for connected blocks, i.e. TURNOUT_B
7163     * @return The block at a particular point on the track object, or null if
7164     *         none.
7165     */
7166    // Temporary - this should certainly be a LayoutTrack method.
7167    public LayoutBlock getAffectedBlock(@Nonnull LayoutTrack track, HitPointType type) {
7168        LayoutBlock result = null;
7169
7170        switch (type) {
7171            case TURNOUT_A:
7172            case SLIP_A: {
7173                if (track instanceof LayoutTurnout) {
7174                    LayoutTurnout lt = (LayoutTurnout) track;
7175                    result = lt.getLayoutBlock();
7176                }
7177                break;
7178            }
7179
7180            case TURNOUT_B:
7181            case SLIP_B: {
7182                if (track instanceof LayoutTurnout) {
7183                    LayoutTurnout lt = (LayoutTurnout) track;
7184                    result = lt.getLayoutBlockB();
7185                }
7186                break;
7187            }
7188
7189            case TURNOUT_C:
7190            case SLIP_C: {
7191                if (track instanceof LayoutTurnout) {
7192                    LayoutTurnout lt = (LayoutTurnout) track;
7193                    result = lt.getLayoutBlockC();
7194                }
7195                break;
7196            }
7197
7198            case TURNOUT_D:
7199            case SLIP_D: {
7200                if (track instanceof LayoutTurnout) {
7201                    LayoutTurnout lt = (LayoutTurnout) track;
7202                    result = lt.getLayoutBlockD();
7203                }
7204                break;
7205            }
7206
7207            case LEVEL_XING_A:
7208            case LEVEL_XING_C: {
7209                if (track instanceof LevelXing) {
7210                    LevelXing lx = (LevelXing) track;
7211                    result = lx.getLayoutBlockAC();
7212                }
7213                break;
7214            }
7215
7216            case LEVEL_XING_B:
7217            case LEVEL_XING_D: {
7218                if (track instanceof LevelXing) {
7219                    LevelXing lx = (LevelXing) track;
7220                    result = lx.getLayoutBlockBD();
7221                }
7222                break;
7223            }
7224
7225            case TRACK: {
7226                if (track instanceof TrackSegment) {
7227                    TrackSegment ts = (TrackSegment) track;
7228                    result = ts.getLayoutBlock();
7229                }
7230                break;
7231            }
7232            default: {
7233                if (HitPointType.isTurntableRayHitType(type) ||
7234                        HitPointType.isTraverserSlotHitType(type)) {
7235                    break;
7236                }
7237                log.warn("Unhandled track type: {}", type);
7238                break;
7239            }
7240        }
7241        return result;
7242    }
7243
7244    /**
7245     * Add a sensor indicator to the Draw Panel
7246     */
7247    void addSensor() {
7248        String newName = leToolBarPanel.sensorComboBox.getSelectedItemDisplayName();
7249        if (newName == null) {
7250            newName = "";
7251        }
7252
7253        if (newName.isEmpty()) {
7254            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"),
7255                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7256            return;
7257        }
7258        SensorIcon l = new SensorIcon(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
7259                "resources/icons/smallschematics/tracksegments/circuit-error.gif"), this);
7260
7261        l.setIcon("SensorStateActive", leToolBarPanel.sensorIconEditor.getIcon(0));
7262        l.setIcon("SensorStateInactive", leToolBarPanel.sensorIconEditor.getIcon(1));
7263        l.setIcon("BeanStateInconsistent", leToolBarPanel.sensorIconEditor.getIcon(2));
7264        l.setIcon("BeanStateUnknown", leToolBarPanel.sensorIconEditor.getIcon(3));
7265        l.setSensor(newName);
7266        l.setDisplayLevel(Editor.SENSORS);
7267
7268        leToolBarPanel.sensorComboBox.setSelectedItem(l.getSensor());
7269        setNextLocation(l);
7270        try {
7271            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7272        } catch (Positionable.DuplicateIdException e) {
7273            // This should never happen
7274            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7275        }
7276    }
7277
7278    public void putSensor(@Nonnull SensorIcon l) {
7279        l.updateSize();
7280        l.setDisplayLevel(Editor.SENSORS);
7281        try {
7282            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7283        } catch (Positionable.DuplicateIdException e) {
7284            // This should never happen
7285            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7286        }
7287    }
7288
7289    /**
7290     * Add a turnout indicator to the Draw Panel
7291     */
7292    void addTurnout() {
7293        String newName = leToolBarPanel.turnoutComboBox.getSelectedItemDisplayName();
7294        if (newName == null) {
7295            newName = "";
7296        }
7297
7298        if (newName.isEmpty()) {
7299            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"),
7300                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7301            return;
7302        }
7303        TurnoutIcon l = new OutputIndicator(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
7304                "resources/icons/smallschematics/tracksegments/circuit-error.gif"), this);
7305
7306        l.setTurnout(newName);
7307
7308        l.setIcon("TurnoutStateThrown", leToolBarPanel.turnoutIconEditor.getIcon(0));
7309        l.setIcon("TurnoutStateClosed", leToolBarPanel.turnoutIconEditor.getIcon(1));
7310        l.setIcon("BeanStateInconsistent", leToolBarPanel.turnoutIconEditor.getIcon(2));
7311        l.setIcon("BeanStateUnknown", leToolBarPanel.turnoutIconEditor.getIcon(3));
7312        l.setDisplayLevel(Editor.TURNOUTS);
7313
7314        leToolBarPanel.turnoutComboBox.setSelectedItem(l.getTurnout());
7315        setNextLocation(l);
7316        try {
7317            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7318        } catch (Positionable.DuplicateIdException e) {
7319            // This should never happen
7320            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7321        }
7322    }
7323
7324    public void putTurnout(@Nonnull TurnoutIcon l) {
7325        l.updateSize();
7326        l.setDisplayLevel(Editor.TURNOUTS);
7327        try {
7328            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7329        } catch (Positionable.DuplicateIdException e) {
7330            // This should never happen
7331            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7332        }
7333    }
7334
7335    /**
7336     * Add a signal head to the Panel
7337     */
7338    void addSignalHead() {
7339        // check for valid signal head entry
7340        String newName = leToolBarPanel.signalHeadComboBox.getSelectedItemDisplayName();
7341        if (newName == null) {
7342            newName = "";
7343        }
7344        SignalHead mHead = null;
7345
7346        if (!newName.isEmpty()) {
7347            mHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(newName);
7348
7349            /*if (mHead == null)
7350            mHead = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(newName);
7351            else */
7352            leToolBarPanel.signalHeadComboBox.setSelectedItem(mHead);
7353        }
7354
7355        if (mHead == null) {
7356            // There is no signal head corresponding to this name
7357            JmriJOptionPane.showMessageDialog(this,
7358                    MessageFormat.format(Bundle.getMessage("Error9"), newName),
7359                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7360            return;
7361        }
7362
7363        // create and set up signal icon
7364        SignalHeadIcon l = new SignalHeadIcon(this);
7365        l.setSignalHead(newName);
7366        l.setIcon("SignalHeadStateRed", leToolBarPanel.signalIconEditor.getIcon(0));
7367        l.setIcon("SignalHeadStateFlashingRed", leToolBarPanel.signalIconEditor.getIcon(1));
7368        l.setIcon("SignalHeadStateYellow", leToolBarPanel.signalIconEditor.getIcon(2));
7369        l.setIcon("SignalHeadStateFlashingYellow", leToolBarPanel.signalIconEditor.getIcon(3));
7370        l.setIcon("SignalHeadStateGreen", leToolBarPanel.signalIconEditor.getIcon(4));
7371        l.setIcon("SignalHeadStateFlashingGreen", leToolBarPanel.signalIconEditor.getIcon(5));
7372        l.setIcon("SignalHeadStateDark", leToolBarPanel.signalIconEditor.getIcon(6));
7373        l.setIcon("SignalHeadStateHeld", leToolBarPanel.signalIconEditor.getIcon(7));
7374        l.setIcon("SignalHeadStateLunar", leToolBarPanel.signalIconEditor.getIcon(8));
7375        l.setIcon("SignalHeadStateFlashingLunar", leToolBarPanel.signalIconEditor.getIcon(9));
7376        unionToPanelBounds(l.getBounds());
7377        setNextLocation(l);
7378        setDirty();
7379        putSignal(l);
7380    }
7381
7382    public void putSignal(@Nonnull SignalHeadIcon l) {
7383        l.updateSize();
7384        l.setDisplayLevel(Editor.SIGNALS);
7385        try {
7386            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7387        } catch (Positionable.DuplicateIdException e) {
7388            // This should never happen
7389            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7390        }
7391    }
7392
7393    @CheckForNull
7394    SignalHead getSignalHead(@Nonnull String name) {
7395        SignalHead sh = InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(name);
7396
7397        if (sh == null) {
7398            sh = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(name);
7399        }
7400
7401        if (sh == null) {
7402            log.warn("did not find a SignalHead named {}", name);
7403        }
7404        return sh;
7405    }
7406
7407    public boolean containsSignalHead(@CheckForNull SignalHead head) {
7408        if (head != null) {
7409            for (SignalHeadIcon h : signalList) {
7410                if (h.getSignalHead() == head) {
7411                    return true;
7412                }
7413            }
7414        }
7415        return false;
7416    }
7417
7418    public void removeSignalHead(@CheckForNull SignalHead head) {
7419        if (head != null) {
7420            for (SignalHeadIcon h : signalList) {
7421                if (h.getSignalHead() == head) {
7422                    signalList.remove(h);
7423                    h.remove();
7424                    h.dispose();
7425                    setDirty();
7426                    redrawPanel();
7427                    break;
7428                }
7429            }
7430        }
7431    }
7432
7433    void addSignalMast() {
7434        // check for valid signal head entry
7435        String newName = leToolBarPanel.signalMastComboBox.getSelectedItemDisplayName();
7436        if (newName == null) {
7437            newName = "";
7438        }
7439        SignalMast mMast = null;
7440
7441        if (!newName.isEmpty()) {
7442            mMast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(newName);
7443            leToolBarPanel.signalMastComboBox.setSelectedItem(mMast);
7444        }
7445
7446        if (mMast == null) {
7447            // There is no signal head corresponding to this name
7448            JmriJOptionPane.showMessageDialog(this,
7449                    MessageFormat.format(Bundle.getMessage("Error9"), newName),
7450                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7451
7452            return;
7453        }
7454
7455        // create and set up signal icon
7456        SignalMastIcon l = new SignalMastIcon(this);
7457        l.setSignalMast(newName);
7458        unionToPanelBounds(l.getBounds());
7459        setNextLocation(l);
7460        setDirty();
7461        putSignalMast(l);
7462    }
7463
7464    public void putSignalMast(@Nonnull SignalMastIcon l) {
7465        l.updateSize();
7466        l.setDisplayLevel(Editor.SIGNALS);
7467        try {
7468            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7469        } catch (Positionable.DuplicateIdException e) {
7470            // This should never happen
7471            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7472        }
7473    }
7474
7475    SignalMast getSignalMast(@Nonnull String name) {
7476        SignalMast sh = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name);
7477
7478        if (sh == null) {
7479            sh = InstanceManager.getDefault(SignalMastManager.class).getByUserName(name);
7480        }
7481
7482        if (sh == null) {
7483            log.warn("did not find a SignalMast named {}", name);
7484        }
7485        return sh;
7486    }
7487
7488    public boolean containsSignalMast(@Nonnull SignalMast mast) {
7489        for (SignalMastIcon h : signalMastList) {
7490            if (h.getSignalMast() == mast) {
7491                return true;
7492            }
7493        }
7494        return false;
7495    }
7496
7497    /**
7498     * Add a label to the Draw Panel
7499     */
7500    void addLabel() {
7501        String labelText = leToolBarPanel.textLabelTextField.getText();
7502        labelText = (labelText != null) ? labelText.trim() : "";
7503
7504        if (labelText.isEmpty()) {
7505            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11"),
7506                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7507            return;
7508        }
7509        PositionableLabel l = super.addLabel(labelText);
7510        unionToPanelBounds(l.getBounds());
7511        setDirty();
7512        l.setForeground(defaultTextColor);
7513    }
7514
7515    @Override
7516    public void putItem(@Nonnull Positionable l) throws Positionable.DuplicateIdException {
7517        putItem(l, false);
7518    }
7519
7520    @Override
7521    public void putItem(@Nonnull Positionable l, boolean factoryPositionable)
7522            throws Positionable.DuplicateIdException {
7523
7524        super.putItem(l, factoryPositionable);
7525
7526        if (l instanceof SensorIcon) {
7527            sensorImage.add((SensorIcon) l);
7528            sensorList.add((SensorIcon) l);
7529        } else if (l instanceof TurnoutIcon) {
7530            turnoutImage.add((TurnoutIcon) l);
7531            turnoutList.add((TurnoutIcon) l);
7532        } else if (l instanceof LocoIcon) {
7533            markerImage.add((LocoIcon) l);
7534        } else if (l instanceof SignalHeadIcon) {
7535            signalHeadImage.add((SignalHeadIcon) l);
7536            signalList.add((SignalHeadIcon) l);
7537        } else if (l instanceof SignalMastIcon) {
7538            signalMastList.add((SignalMastIcon) l);
7539        } else if (l instanceof BlockContentsIcon) {
7540            blockContentsLabelList.add((BlockContentsIcon) l);
7541        } else if (l instanceof MemoryIcon) {
7542            memoryLabelList.add((MemoryIcon) l);
7543        } else if (l instanceof BlockContentsInputIcon) {
7544            blockContentsInputList.add((BlockContentsInputIcon) l);
7545        } else if (l instanceof MemoryInputIcon) {
7546            memoryInputList.add((MemoryInputIcon) l);
7547        } else if (l instanceof GlobalVariableIcon) {
7548            globalVariableLabelList.add((GlobalVariableIcon) l);
7549        } else if (l instanceof AnalogClock2Display) {
7550            clocks.add((AnalogClock2Display) l);
7551        } else if (l instanceof MultiSensorIcon) {
7552            multiSensors.add((MultiSensorIcon) l);
7553        } else if (factoryPositionable) {
7554            factoryPositionables.add(l);
7555        }
7556
7557        if (l instanceof PositionableLabel) {
7558            if (((PositionableLabel) l).isBackground()) {
7559                backgroundImage.add((PositionableLabel) l);
7560            } else {
7561                labelImage.add((PositionableLabel) l);
7562            }
7563        }
7564        unionToPanelBounds(l.getBounds(new Rectangle()));
7565        setDirty();
7566    }
7567
7568    /**
7569     * When adding a memory variable, provide an option to create the normal label
7570     * or create an input text field.  The label requires a pop-up dialog to change the value
7571     * while the text field makes it possible to change the value on the panel.  This also makes
7572     * it possible to change the value using the web server.
7573     */
7574    void selectMemoryType() {
7575        int response = JmriJOptionPane.showConfirmDialog(null,
7576            Bundle.getMessage("MemorySelectType"),
7577            Bundle.getMessage("MemorySelectTitle"),
7578            JmriJOptionPane.YES_NO_OPTION);
7579
7580        if (response == JmriJOptionPane.YES_OPTION) {
7581            addMemory();
7582            return;
7583        }
7584
7585        var length = JmriJOptionPane.showInputDialog(null,
7586            Bundle.getMessage("MemorySelectSize"),
7587            "5");
7588
7589        int textLength;
7590        try {
7591           textLength = Integer.parseInt(length);
7592        }
7593        catch (NumberFormatException e) {
7594           textLength = 5;
7595        }
7596
7597        addInputMemory(textLength);
7598    }
7599
7600    /**
7601     * Add a memory label to the Draw Panel
7602     */
7603    void addMemory() {
7604        String memoryName = leToolBarPanel.textMemoryComboBox.getSelectedItemDisplayName();
7605        if (memoryName == null) {
7606            memoryName = "";
7607        }
7608
7609        if (memoryName.isEmpty()) {
7610            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11a"),
7611                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7612            return;
7613        }
7614        MemoryIcon l = new MemoryIcon(" ", this);
7615        l.setMemory(memoryName);
7616        setNextLocation(l);
7617        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7618        l.setDisplayLevel(Editor.LABELS);
7619        l.setForeground(defaultTextColor);
7620        unionToPanelBounds(l.getBounds());
7621        try {
7622            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7623        } catch (Positionable.DuplicateIdException e) {
7624            // This should never happen
7625            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7626        }
7627    }
7628
7629    void addInputMemory(int textFieldLength) {
7630        String memoryName = leToolBarPanel.textMemoryComboBox.getSelectedItemDisplayName();
7631        if (memoryName == null) {
7632            memoryName = "";
7633        }
7634
7635        if (memoryName.isEmpty()) {
7636            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11a"),
7637                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7638            return;
7639        }
7640
7641        MemoryInputIcon l = new MemoryInputIcon(textFieldLength, this);
7642        l.setMemory(memoryName);
7643        setNextLocation(l);
7644        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7645        l.setDisplayLevel(Editor.MEMORIES);
7646        l.setForeground(defaultTextColor);
7647        unionToPanelBounds(l.getBounds());
7648        try {
7649            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7650        } catch (Positionable.DuplicateIdException e) {
7651            // This should never happen
7652            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7653        }
7654    }
7655
7656
7657    void addGlobalVariable() {
7658        String globalVariableName = leToolBarPanel.textGlobalVariableComboBox.getSelectedItemDisplayName();
7659        if (globalVariableName == null) {
7660            globalVariableName = "";
7661        }
7662
7663        if (globalVariableName.isEmpty()) {
7664            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11c"),
7665                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7666            return;
7667        }
7668        GlobalVariableIcon l = new GlobalVariableIcon(" ", this);
7669        l.setGlobalVariable(globalVariableName);
7670        GlobalVariable xGlobalVariable = l.getGlobalVariable();
7671
7672        if (xGlobalVariable != null) {
7673            String uname = xGlobalVariable.getDisplayName();
7674            if (!uname.equals(globalVariableName)) {
7675                // put the system name in the memory field
7676                leToolBarPanel.textGlobalVariableComboBox.setSelectedItem(xGlobalVariable);
7677            }
7678        }
7679        setNextLocation(l);
7680        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7681        l.setDisplayLevel(Editor.LABELS);
7682        l.setForeground(defaultTextColor);
7683        unionToPanelBounds(l.getBounds());
7684        try {
7685            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7686        } catch (Positionable.DuplicateIdException e) {
7687            // This should never happen
7688            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7689        }
7690    }
7691
7692    /**
7693     * When adding a blopck contents object, provide an option to create the normal label
7694     * or create an input text field.  The label requires a pop-up dialog to change the value
7695     * while the text field makes it possible to change the value on the panel.  This also makes
7696     * it possible to change the value using the web server.
7697     */
7698    void selectBlockContentsType() {
7699        int response = JmriJOptionPane.showConfirmDialog(null,
7700            Bundle.getMessage("BlockSelectType"),
7701            Bundle.getMessage("BlockSelectTitle"),
7702            JmriJOptionPane.YES_NO_OPTION);
7703
7704        if (response == JmriJOptionPane.YES_OPTION) {
7705            addBlockContents();
7706            return;
7707        }
7708
7709        var length = JmriJOptionPane.showInputDialog(null,
7710            Bundle.getMessage("BlockSelectSize"),
7711            "5");
7712
7713        int textLength;
7714        try {
7715           textLength = Integer.parseInt(length);
7716        }
7717        catch (NumberFormatException e) {
7718           textLength = 5;
7719        }
7720
7721        addInputBlockContents(textLength);
7722    }
7723
7724    void addBlockContents() {
7725        String newName = leToolBarPanel.blockContentsComboBox.getSelectedItemDisplayName();
7726        if (newName == null) {
7727            newName = "";
7728        }
7729
7730        if (newName.isEmpty()) {
7731            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11b"),
7732                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7733            return;
7734        }
7735        BlockContentsIcon l = new BlockContentsIcon(" ", this);
7736        l.setBlock(newName);
7737        Block xMemory = l.getBlock();
7738
7739        if (xMemory != null) {
7740            String uname = xMemory.getDisplayName();
7741            if (!uname.equals(newName)) {
7742                // put the system name in the memory field
7743                leToolBarPanel.blockContentsComboBox.setSelectedItem(xMemory);
7744            }
7745        }
7746        setNextLocation(l);
7747        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7748        l.setDisplayLevel(Editor.LABELS);
7749        l.setForeground(defaultTextColor);
7750        try {
7751            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7752        } catch (Positionable.DuplicateIdException e) {
7753            // This should never happen
7754            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7755        }
7756    }
7757
7758    void addInputBlockContents(int textFieldLength) {
7759        String newName = leToolBarPanel.blockContentsComboBox.getSelectedItemDisplayName();
7760        if (newName == null) {
7761            newName = "";
7762        }
7763
7764        if (newName.isEmpty()) {
7765            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11b"),
7766                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7767            return;
7768        }
7769        BlockContentsInputIcon l = new BlockContentsInputIcon(textFieldLength, this);
7770        l.setBlock(newName);
7771        setNextLocation(l);
7772        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7773        l.setDisplayLevel(Editor.MEMORIES);
7774        l.setForeground(defaultTextColor);
7775        try {
7776            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7777        } catch (Positionable.DuplicateIdException e) {
7778            // This should never happen
7779            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7780        }
7781    }
7782
7783    /**
7784     * Add a Reporter Icon to the panel.
7785     *
7786     * @param reporter the reporter icon to add.
7787     * @param xx       the horizontal location.
7788     * @param yy       the vertical location.
7789     */
7790    public void addReporter(@Nonnull Reporter reporter, int xx, int yy) {
7791        ReporterIcon l = new ReporterIcon(this);
7792        l.setReporter(reporter);
7793        l.setLocation(xx, yy);
7794        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7795        l.setDisplayLevel(Editor.LABELS);
7796        unionToPanelBounds(l.getBounds());
7797        try {
7798            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7799        } catch (Positionable.DuplicateIdException e) {
7800            // This should never happen
7801            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7802        }
7803    }
7804
7805    /**
7806     * Add an icon to the target
7807     */
7808    void addIcon() {
7809        PositionableLabel l = new PositionableLabel(leToolBarPanel.iconEditor.getIcon(0), this);
7810        setNextLocation(l);
7811        l.setDisplayLevel(Editor.ICONS);
7812        unionToPanelBounds(l.getBounds());
7813        l.updateSize();
7814        try {
7815            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7816        } catch (Positionable.DuplicateIdException e) {
7817            // This should never happen
7818            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7819        }
7820    }
7821
7822    /**
7823     * Add a LogixNG icon to the target
7824     */
7825    void addLogixNGIcon() {
7826        LogixNGIcon l = new LogixNGIcon(leToolBarPanel.logixngEditor.getIcon(0), this);
7827        setNextLocation(l);
7828        l.setDisplayLevel(Editor.ICONS);
7829        unionToPanelBounds(l.getBounds());
7830        l.updateSize();
7831        try {
7832            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7833        } catch (Positionable.DuplicateIdException e) {
7834            // This should never happen
7835            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7836        }
7837    }
7838
7839    /**
7840     * Add a LogixNG icon to the target
7841     */
7842    void addAudioIcon() {
7843        String audioName = leToolBarPanel.textAudioComboBox.getSelectedItemDisplayName();
7844        if (audioName == null) {
7845            audioName = "";
7846        }
7847
7848        if (audioName.isEmpty()) {
7849            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11d"),
7850                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7851            return;
7852        }
7853
7854        AudioIcon l = new AudioIcon(leToolBarPanel.audioEditor.getIcon(0), this);
7855        l.setAudio(audioName);
7856        Audio xAudio = l.getAudio();
7857
7858        if (xAudio != null) {
7859            String uname = xAudio.getDisplayName();
7860            if (!uname.equals(audioName)) {
7861                // put the system name in the memory field
7862                leToolBarPanel.textAudioComboBox.setSelectedItem(xAudio);
7863            }
7864        }
7865
7866        setNextLocation(l);
7867        l.setDisplayLevel(Editor.ICONS);
7868        unionToPanelBounds(l.getBounds());
7869        l.updateSize();
7870        try {
7871            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7872        } catch (Positionable.DuplicateIdException e) {
7873            // This should never happen
7874            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7875        }
7876    }
7877
7878    /**
7879     * Add a loco marker to the target
7880     */
7881    @Override
7882    public LocoIcon addLocoIcon(@Nonnull String name) {
7883        LocoIcon l = new LocoIcon(this);
7884        Point2D pt = windowCenter();
7885        if (selectionActive) {
7886            pt = MathUtil.midPoint(getSelectionRect());
7887        }
7888        l.setLocation((int) pt.getX(), (int) pt.getY());
7889        putLocoIcon(l, name);
7890        l.setPositionable(true);
7891        unionToPanelBounds(l.getBounds());
7892        return l;
7893    }
7894
7895    @Override
7896    public void putLocoIcon(@Nonnull LocoIcon l, @Nonnull String name) {
7897        super.putLocoIcon(l, name);
7898        markerImage.add(l);
7899        unionToPanelBounds(l.getBounds());
7900    }
7901
7902    private JFileChooser inputFileChooser = null;
7903
7904    /**
7905     * Add a background image
7906     */
7907    public void addBackground() {
7908        if (inputFileChooser == null) {
7909            inputFileChooser = new jmri.util.swing.JmriJFileChooser(
7910                    String.format("%s%sresources%sicons",
7911                            System.getProperty("user.dir"),
7912                            File.separator,
7913                            File.separator));
7914
7915            inputFileChooser.setFileFilter(new FileNameExtensionFilter("Graphics Files", "gif", "jpg", "png"));
7916        }
7917        inputFileChooser.rescanCurrentDirectory();
7918
7919        int retVal = inputFileChooser.showOpenDialog(this);
7920
7921        if (retVal != JFileChooser.APPROVE_OPTION) {
7922            return; // give up if no file selected
7923        }
7924
7925        // NamedIcon icon = new NamedIcon(inputFileChooser.getSelectedFile().getPath(),
7926        // inputFileChooser.getSelectedFile().getPath());
7927        String name = inputFileChooser.getSelectedFile().getPath();
7928
7929        // convert to portable path
7930        name = FileUtil.getPortableFilename(name);
7931
7932        // setup icon
7933        PositionableLabel o = super.setUpBackground(name);
7934        backgroundImage.add(o);
7935        unionToPanelBounds(o.getBounds());
7936        setDirty();
7937    }
7938
7939    // there is no way to call this; could that
7940    //    private boolean remove(@Nonnull Object s)
7941    // is being used instead.
7942    //
7943    ///**
7944    // * Remove a background image from the list of background images
7945    // *
7946    // * @param b PositionableLabel to remove
7947    // */
7948    //private void removeBackground(@Nonnull PositionableLabel b) {
7949    //    if (backgroundImage.contains(b)) {
7950    //        backgroundImage.remove(b);
7951    //        setDirty();
7952    //    }
7953    //}
7954    /**
7955     * add a layout shape to the list of layout shapes
7956     *
7957     * @param p Point2D where the shape should be
7958     * @return the LayoutShape
7959     */
7960    @Nonnull
7961    private LayoutShape addLayoutShape(@Nonnull Point2D p) {
7962        // get unique name
7963        String name = finder.uniqueName("S", getLayoutShapes().size() + 1);
7964
7965        // create object
7966        LayoutShape o = new LayoutShape(name, p, this);
7967        layoutShapes.add(o);
7968        unionToPanelBounds(o.getBounds());
7969        setDirty();
7970        return o;
7971    }
7972
7973    /**
7974     * Remove a layout shape from the list of layout shapes
7975     *
7976     * @param s the LayoutShape to add
7977     * @return true if added
7978     */
7979    public boolean removeLayoutShape(@Nonnull LayoutShape s) {
7980        boolean result = false;
7981        if (layoutShapes.contains(s)) {
7982            layoutShapes.remove(s);
7983            setDirty();
7984            result = true;
7985            redrawPanel();
7986        }
7987        return result;
7988    }
7989
7990    /**
7991     * Invoke a window to allow you to add a MultiSensor indicator to the target
7992     */
7993    private int multiLocX;
7994    private int multiLocY;
7995
7996    void startMultiSensor() {
7997        multiLocX = xLoc;
7998        multiLocY = yLoc;
7999
8000        if (leToolBarPanel.multiSensorFrame == null) {
8001            // create a common edit frame
8002            leToolBarPanel.multiSensorFrame = new MultiSensorIconFrame(this);
8003            leToolBarPanel.multiSensorFrame.initComponents();
8004            leToolBarPanel.multiSensorFrame.pack();
8005        }
8006        leToolBarPanel.multiSensorFrame.setVisible(true);
8007    }
8008
8009    // Invoked when window has new multi-sensor ready
8010    public void addMultiSensor(@Nonnull MultiSensorIcon l) {
8011        l.setLocation(multiLocX, multiLocY);
8012        try {
8013            putItem(l); // note: this calls unionToPanelBounds & setDirty()
8014        } catch (Positionable.DuplicateIdException e) {
8015            // This should never happen
8016            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
8017        }
8018        leToolBarPanel.multiSensorFrame.dispose();
8019        leToolBarPanel.multiSensorFrame = null;
8020    }
8021
8022    /**
8023     * Set object location and size for icon and label object as it is created.
8024     * Size comes from the preferredSize; location comes from the fields where
8025     * the user can spec it.
8026     *
8027     * @param obj the positionable object.
8028     */
8029    @Override
8030    public void setNextLocation(@Nonnull Positionable obj) {
8031        obj.setLocation(xLoc, yLoc);
8032    }
8033
8034    //
8035    // singleton (one per-LayoutEditor) accessors
8036    //
8037    private ConnectivityUtil conTools = null;
8038
8039    @Nonnull
8040    public ConnectivityUtil getConnectivityUtil() {
8041        if (conTools == null) {
8042            conTools = new ConnectivityUtil(this);
8043        }
8044        return conTools;
8045    }
8046
8047    private LayoutEditorTools tools = null;
8048
8049    @Nonnull
8050    public LayoutEditorTools getLETools() {
8051        if (tools == null) {
8052            tools = new LayoutEditorTools(this);
8053        }
8054        return tools;
8055    }
8056
8057    private LayoutEditorAuxTools auxTools = null;
8058
8059    @Override
8060    @Nonnull
8061    public LayoutEditorAuxTools getLEAuxTools() {
8062        if (auxTools == null) {
8063            auxTools = new LayoutEditorAuxTools(this);
8064        }
8065        return auxTools;
8066    }
8067
8068    private LayoutEditorChecks layoutEditorChecks = null;
8069
8070    @Nonnull
8071    public LayoutEditorChecks getLEChecks() {
8072        if (layoutEditorChecks == null) {
8073            layoutEditorChecks = new LayoutEditorChecks(this);
8074        }
8075        return layoutEditorChecks;
8076    }
8077
8078    /**
8079     * Invoked by DeletePanel menu item Validate user intent before deleting
8080     */
8081    @Override
8082    public boolean deletePanel() {
8083        if (canDeletePanel()) {
8084            // verify deletion
8085            if (!super.deletePanel()) {
8086                return false; // return without deleting if "No" response
8087            }
8088            clearLayoutTracks();
8089            return true;
8090        }
8091        return false;
8092    }
8093
8094    /**
8095     * Check for conditions that prevent a delete.
8096     * <ul>
8097     * <li>The panel has active edge connector links</li>
8098     * <li>The panel is used by EntryExit</li>
8099     * </ul>
8100     * @return true if ok to delete
8101     */
8102    public boolean canDeletePanel() {
8103        var messages = new ArrayList<String>();
8104
8105        var points = getPositionablePoints();
8106        for (PositionablePoint point : points) {
8107            if (point.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) {
8108                var panelName = point.getLinkedEditorName();
8109                if (!panelName.isEmpty()) {
8110                    messages.add(Bundle.getMessage("ActiveEdgeConnector", point.getId(), point.getLinkedEditorName()));
8111                }
8112            }
8113        }
8114
8115        var entryExitPairs = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class);
8116        if (!entryExitPairs.getNxSource(this).isEmpty()) {
8117            messages.add(Bundle.getMessage("ActiveEntryExit"));
8118        }
8119
8120        if (!messages.isEmpty()) {
8121            StringBuilder msg = new StringBuilder(Bundle.getMessage("PanelRelationshipsError"));
8122            for (String message : messages) {
8123                msg.append(message);
8124            }
8125            JmriJOptionPane.showMessageDialog(null,
8126                    msg.toString(),
8127                    Bundle.getMessage("ErrorTitle"), // NOI18N
8128                    JmriJOptionPane.ERROR_MESSAGE);
8129        }
8130
8131        return messages.isEmpty();
8132    }
8133
8134    /**
8135     * Control whether target panel items are editable. Does this by invoking
8136     * the {@link Editor#setAllEditable} function of the parent class. This also
8137     * controls the relevant pop-up menu items (which are the primary way that
8138     * items are edited).
8139     *
8140     * @param editable true for editable.
8141     */
8142    @Override
8143    public void setAllEditable(boolean editable) {
8144        int restoreScroll = _scrollState;
8145
8146        super.setAllEditable(editable);
8147
8148        if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
8149            if (editable) {
8150                createfloatingEditToolBoxFrame();
8151                createFloatingHelpPanel();
8152            } else {
8153                deletefloatingEditToolBoxFrame();
8154            }
8155        } else {
8156            editToolBarContainerPanel.setVisible(editable);
8157        }
8158        setShowHidden(editable);
8159
8160        if (editable) {
8161            setScroll(Editor.SCROLL_BOTH);
8162            _scrollState = restoreScroll;
8163        } else {
8164            setScroll(_scrollState);
8165        }
8166
8167        // these may not be set up yet...
8168        if (helpBarPanel != null) {
8169            if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
8170                if (floatEditHelpPanel != null) {
8171                    floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
8172                }
8173            } else {
8174                helpBarPanel.setVisible(editable && getShowHelpBar());
8175            }
8176        }
8177        awaitingIconChange = false;
8178        editModeCheckBoxMenuItem.setSelected(editable);
8179        redrawPanel();
8180    }
8181
8182    /**
8183     * Control whether panel items are positionable. Markers are always
8184     * positionable.
8185     *
8186     * @param state true for positionable.
8187     */
8188    @Override
8189    public void setAllPositionable(boolean state) {
8190        super.setAllPositionable(state);
8191
8192        markerImage.forEach((p) -> p.setPositionable(true));
8193    }
8194
8195    /**
8196     * Control whether target panel items are controlling layout items. Does
8197     * this by invoke the {@link Positionable#setControlling} function of each
8198     * item on the target panel. This also controls the relevant pop-up menu
8199     * items.
8200     *
8201     * @param state true for controlling.
8202     */
8203    public void setTurnoutAnimation(boolean state) {
8204        if (animationCheckBoxMenuItem.isSelected() != state) {
8205            animationCheckBoxMenuItem.setSelected(state);
8206        }
8207
8208        if (animatingLayout != state) {
8209            animatingLayout = state;
8210            redrawPanel();
8211        }
8212    }
8213
8214    public boolean isAnimating() {
8215        return animatingLayout;
8216    }
8217
8218    public boolean getScroll() {
8219        // deprecated but kept to allow opening files
8220        // on version 2.5.1 and earlier
8221        return _scrollState != Editor.SCROLL_NONE;
8222    }
8223
8224//    public Color getDefaultBackgroundColor() {
8225//        return defaultBackgroundColor;
8226//    }
8227    public String getDefaultTrackColor() {
8228        return ColorUtil.colorToColorName(defaultTrackColor);
8229    }
8230
8231    /**
8232     *
8233     * Getter defaultTrackColor.
8234     *
8235     * @return block default color as Color
8236     */
8237    @Nonnull
8238    public Color getDefaultTrackColorColor() {
8239        return defaultTrackColor;
8240    }
8241
8242    @Nonnull
8243    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8244    public String getDefaultOccupiedTrackColor() {
8245        return ColorUtil.colorToColorName(defaultOccupiedTrackColor);
8246    }
8247
8248    /**
8249     *
8250     * Getter defaultOccupiedTrackColor.
8251     *
8252     * @return block default occupied color as Color
8253     */
8254    @Nonnull
8255    public Color getDefaultOccupiedTrackColorColor() {
8256        return defaultOccupiedTrackColor;
8257    }
8258
8259    @Nonnull
8260    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8261    public String getDefaultAlternativeTrackColor() {
8262        return ColorUtil.colorToColorName(defaultAlternativeTrackColor);
8263    }
8264
8265    /**
8266     *
8267     * Getter defaultAlternativeTrackColor.
8268     *
8269     * @return block default alternative color as Color
8270     */
8271    @Nonnull
8272    public Color getDefaultAlternativeTrackColorColor() {
8273        return defaultAlternativeTrackColor;
8274    }
8275
8276    @Nonnull
8277    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8278    public String getDefaultTextColor() {
8279        return ColorUtil.colorToColorName(defaultTextColor);
8280    }
8281
8282    @Nonnull
8283    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8284    public String getTurnoutCircleColor() {
8285        return ColorUtil.colorToColorName(turnoutCircleColor);
8286    }
8287
8288    @Nonnull
8289    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8290    public String getTurnoutCircleThrownColor() {
8291        return ColorUtil.colorToColorName(turnoutCircleThrownColor);
8292    }
8293
8294    public boolean isTurnoutFillControlCircles() {
8295        return turnoutFillControlCircles;
8296    }
8297
8298    public int getTurnoutCircleSize() {
8299        return turnoutCircleSize;
8300    }
8301
8302    public boolean isTurnoutDrawUnselectedLeg() {
8303        return turnoutDrawUnselectedLeg;
8304    }
8305
8306    public boolean isHighlightCursor() {
8307        return highlightCursor;
8308    }
8309
8310    public String getLayoutName() {
8311        return layoutName;
8312    }
8313
8314    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8315    public boolean getShowHelpBar() {
8316        return showHelpBar;
8317    }
8318
8319    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8320    public boolean getDrawGrid() {
8321        return drawGrid;
8322    }
8323
8324    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8325    public boolean getSnapOnAdd() {
8326        return snapToGridOnAdd;
8327    }
8328
8329    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8330    public boolean getSnapOnMove() {
8331        return snapToGridOnMove;
8332    }
8333
8334    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8335    public boolean getAntialiasingOn() {
8336        return antialiasingOn;
8337    }
8338
8339    public boolean isDrawLayoutTracksLabel() {
8340        return drawLayoutTracksLabel;
8341    }
8342
8343    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8344    public boolean getHighlightSelectedBlock() {
8345        return highlightSelectedBlockFlag;
8346    }
8347
8348    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8349    public boolean getTurnoutCircles() {
8350        return turnoutCirclesWithoutEditMode;
8351    }
8352
8353    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8354    public boolean getTooltipsNotEdit() {
8355        return tooltipsWithoutEditMode;
8356    }
8357
8358    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8359    public boolean getTooltipsInEdit() {
8360        return tooltipsInEditMode;
8361    }
8362
8363    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8364    public boolean getAutoBlockAssignment() {
8365        return autoAssignBlocks;
8366    }
8367
8368    public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight) {
8369        setLayoutDimensions(windowWidth, windowHeight, windowX, windowY, panelWidth, panelHeight, false);
8370    }
8371
8372    public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight, boolean merge) {
8373
8374        gContext.setUpperLeftX(windowX);
8375        gContext.setUpperLeftY(windowY);
8376        setLocation(gContext.getUpperLeftX(), gContext.getUpperLeftY());
8377
8378        gContext.setWindowWidth(windowWidth);
8379        gContext.setWindowHeight(windowHeight);
8380        setSize(windowWidth, windowHeight);
8381
8382        Rectangle2D panelBounds = new Rectangle2D.Double(0.0, 0.0, panelWidth, panelHeight);
8383
8384        if (merge) {
8385            panelBounds.add(calculateMinimumLayoutBounds());
8386        }
8387        setPanelBounds(panelBounds);
8388    }
8389
8390    @Nonnull
8391    public Rectangle2D getPanelBounds() {
8392        return new Rectangle2D.Double(0.0, 0.0, gContext.getLayoutWidth(), gContext.getLayoutHeight());
8393    }
8394
8395    public void setPanelBounds(@Nonnull Rectangle2D newBounds) {
8396        // don't let origin go negative
8397        newBounds = newBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8398
8399        if (!getPanelBounds().equals(newBounds)) {
8400            gContext.setLayoutWidth((int) newBounds.getWidth());
8401            gContext.setLayoutHeight((int) newBounds.getHeight());
8402            resetTargetSize();
8403        }
8404        log.debug("setPanelBounds(({})", newBounds);
8405    }
8406
8407    private void resetTargetSize() {
8408        int newTargetWidth = (int) (gContext.getLayoutWidth() * getZoom());
8409        int newTargetHeight = (int) (gContext.getLayoutHeight() * getZoom());
8410
8411        Dimension targetPanelSize = getTargetPanelSize();
8412        int oldTargetWidth = (int) targetPanelSize.getWidth();
8413        int oldTargetHeight = (int) targetPanelSize.getHeight();
8414
8415        if ((newTargetWidth != oldTargetWidth) || (newTargetHeight != oldTargetHeight)) {
8416            setTargetPanelSize(newTargetWidth, newTargetHeight);
8417            adjustScrollBars();
8418        }
8419    }
8420
8421    // this will grow the panel bounds based on items added to the layout
8422    @Nonnull
8423    public Rectangle2D unionToPanelBounds(@Nonnull Rectangle2D bounds) {
8424        Rectangle2D result = getPanelBounds();
8425
8426        // make room to expand
8427        Rectangle2D b = MathUtil.inset(bounds, gContext.getGridSize() * gContext.getGridSize2nd() / -2.0);
8428
8429        // don't let origin go negative
8430        b = b.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8431
8432        result.add(b);
8433
8434        setPanelBounds(result);
8435        return result;
8436    }
8437
8438    /**
8439     * @param color value to set the default track color to.
8440     */
8441    public void setDefaultTrackColor(@Nonnull Color color) {
8442        defaultTrackColor = color;
8443        JmriColorChooser.addRecentColor(color);
8444    }
8445
8446    /**
8447     * @param color value to set the default occupied track color to.
8448     */
8449    public void setDefaultOccupiedTrackColor(@Nonnull Color color) {
8450        defaultOccupiedTrackColor = color;
8451        JmriColorChooser.addRecentColor(color);
8452    }
8453
8454    /**
8455     * @param color value to set the default alternate track color to.
8456     */
8457    public void setDefaultAlternativeTrackColor(@Nonnull Color color) {
8458        defaultAlternativeTrackColor = color;
8459        JmriColorChooser.addRecentColor(color);
8460    }
8461
8462    /**
8463     * @param color new color for turnout circle.
8464     */
8465    public void setTurnoutCircleColor(@CheckForNull Color color) {
8466        if (color == null) {
8467            turnoutCircleColor = getDefaultTrackColorColor();
8468        } else {
8469            turnoutCircleColor = color;
8470            JmriColorChooser.addRecentColor(color);
8471        }
8472    }
8473
8474    /**
8475     * @param color new color for turnout circle.
8476     */
8477    public void setTurnoutCircleThrownColor(@CheckForNull Color color) {
8478        if (color == null) {
8479            turnoutCircleThrownColor = getDefaultTrackColorColor();
8480        } else {
8481            turnoutCircleThrownColor = color;
8482            JmriColorChooser.addRecentColor(color);
8483        }
8484    }
8485
8486    /**
8487     * Should only be invoked on the GUI (Swing) thread.
8488     *
8489     * @param state true to fill in turnout control circles, else false.
8490     */
8491    @InvokeOnGuiThread
8492    public void setTurnoutFillControlCircles(boolean state) {
8493        if (turnoutFillControlCircles != state) {
8494            turnoutFillControlCircles = state;
8495            turnoutFillControlCirclesCheckBoxMenuItem.setSelected(turnoutFillControlCircles);
8496        }
8497    }
8498
8499    public void setTurnoutCircleSize(int size) {
8500        // this is an int
8501        turnoutCircleSize = size;
8502
8503        // these are doubles
8504        circleRadius = SIZE * size;
8505        circleDiameter = 2.0 * circleRadius;
8506
8507        setOptionMenuTurnoutCircleSize();
8508    }
8509
8510    /**
8511     * Should only be invoked on the GUI (Swing) thread.
8512     *
8513     * @param state true to draw unselected legs, else false.
8514     */
8515    @InvokeOnGuiThread
8516    public void setTurnoutDrawUnselectedLeg(boolean state) {
8517        if (turnoutDrawUnselectedLeg != state) {
8518            turnoutDrawUnselectedLeg = state;
8519            turnoutDrawUnselectedLegCheckBoxMenuItem.setSelected(turnoutDrawUnselectedLeg);
8520        }
8521    }
8522
8523    /**
8524     * Should only be invoked on the GUI (Swing) thread.
8525     *
8526     * @param state true to enable highlighting the cursor (mouse/finger press/drag)
8527     */
8528    @InvokeOnGuiThread
8529    public void setHighlightCursor(boolean state) {
8530        if (highlightCursor != state) {
8531            highlightCursor = state;
8532            highlightCursorCheckBoxMenuItem.setSelected(highlightCursor);
8533        }
8534    }
8535
8536    /**
8537     * @param color value to set the default text color to.
8538     */
8539    public void setDefaultTextColor(@Nonnull Color color) {
8540        defaultTextColor = color;
8541        JmriColorChooser.addRecentColor(color);
8542    }
8543
8544    /**
8545     * @param color value to set the panel background to.
8546     */
8547    public void setDefaultBackgroundColor(@Nonnull Color color) {
8548        defaultBackgroundColor = color;
8549        JmriColorChooser.addRecentColor(color);
8550    }
8551
8552    public void setLayoutName(@Nonnull String name) {
8553        layoutName = name;
8554    }
8555
8556    /**
8557     * Should only be invoked on the GUI (Swing) thread.
8558     *
8559     * @param state true to show the help bar, else false.
8560     */
8561    @InvokeOnGuiThread  // due to the setSelected call on a possibly-visible item
8562    public void setShowHelpBar(boolean state) {
8563        if (showHelpBar != state) {
8564            showHelpBar = state;
8565
8566            // these may not be set up yet...
8567            if (showHelpCheckBoxMenuItem != null) {
8568                showHelpCheckBoxMenuItem.setSelected(showHelpBar);
8569            }
8570
8571            if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
8572                if (floatEditHelpPanel != null) {
8573                    floatEditHelpPanel.setVisible(isEditable() && showHelpBar);
8574                }
8575            } else {
8576                if (helpBarPanel != null) {
8577                    helpBarPanel.setVisible(isEditable() && showHelpBar);
8578
8579                }
8580            }
8581            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".showHelpBar", showHelpBar));
8582        }
8583    }
8584
8585    /**
8586     * Should only be invoked on the GUI (Swing) thread.
8587     *
8588     * @param state true to show the draw grid, else false.
8589     */
8590    @InvokeOnGuiThread
8591    public void setDrawGrid(boolean state) {
8592        if (drawGrid != state) {
8593            drawGrid = state;
8594            showGridCheckBoxMenuItem.setSelected(drawGrid);
8595        }
8596    }
8597
8598    /**
8599     * Should only be invoked on the GUI (Swing) thread.
8600     *
8601     * @param state true to set snap to grid on add, else false.
8602     */
8603    @InvokeOnGuiThread
8604    public void setSnapOnAdd(boolean state) {
8605        if (snapToGridOnAdd != state) {
8606            snapToGridOnAdd = state;
8607            snapToGridOnAddCheckBoxMenuItem.setSelected(snapToGridOnAdd);
8608        }
8609    }
8610
8611    /**
8612     * Should only be invoked on the GUI (Swing) thread.
8613     *
8614     * @param state true to set snap on move, else false.
8615     */
8616    @InvokeOnGuiThread
8617    public void setSnapOnMove(boolean state) {
8618        if (snapToGridOnMove != state) {
8619            snapToGridOnMove = state;
8620            snapToGridOnMoveCheckBoxMenuItem.setSelected(snapToGridOnMove);
8621        }
8622    }
8623
8624    /**
8625     * Should only be invoked on the GUI (Swing) thread.
8626     *
8627     * @param state true to set anti-aliasing flag on, else false.
8628     */
8629    @InvokeOnGuiThread
8630    public void setAntialiasingOn(boolean state) {
8631        if (antialiasingOn != state) {
8632            antialiasingOn = state;
8633
8634            // this may not be set up yet...
8635            if (antialiasingOnCheckBoxMenuItem != null) {
8636                antialiasingOnCheckBoxMenuItem.setSelected(antialiasingOn);
8637
8638            }
8639            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".antialiasingOn", antialiasingOn));
8640        }
8641    }
8642
8643    /**
8644     *
8645     * @param state true to set anti-aliasing flag on, else false.
8646     */
8647    public void setDrawLayoutTracksLabel(boolean state) {
8648        if (drawLayoutTracksLabel != state) {
8649            drawLayoutTracksLabel = state;
8650
8651            // this may not be set up yet...
8652            if (drawLayoutTracksLabelCheckBoxMenuItem != null) {
8653                drawLayoutTracksLabelCheckBoxMenuItem.setSelected(drawLayoutTracksLabel);
8654
8655            }
8656            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".drawLayoutTracksLabel", drawLayoutTracksLabel));
8657        }
8658    }
8659
8660    // enable/disable using the "Extra" color to highlight the selected block
8661    public void setHighlightSelectedBlock(boolean state) {
8662        if (highlightSelectedBlockFlag != state) {
8663            highlightSelectedBlockFlag = state;
8664
8665            // this may not be set up yet...
8666            if (leToolBarPanel.highlightBlockCheckBox != null) {
8667                leToolBarPanel.highlightBlockCheckBox.setSelected(highlightSelectedBlockFlag);
8668
8669            }
8670
8671            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".highlightSelectedBlock", highlightSelectedBlockFlag));
8672
8673            // thread this so it won't break the AppVeyor checks
8674            ThreadingUtil.newThread(() -> {
8675                if (highlightSelectedBlockFlag) {
8676                    // use the "Extra" color to highlight the selected block
8677                    if (!highlightBlockInComboBox(leToolBarPanel.blockIDComboBox)) {
8678                        highlightBlockInComboBox(leToolBarPanel.blockContentsComboBox);
8679                    }
8680                } else {
8681                    // undo using the "Extra" color to highlight the selected block
8682                    Block block = leToolBarPanel.blockIDComboBox.getSelectedItem();
8683                    highlightBlock(null);
8684                    leToolBarPanel.blockIDComboBox.setSelectedItem(block);
8685                }
8686            }).start();
8687        }
8688    }
8689
8690    //
8691    // highlight the block selected by the specified combo Box
8692    //
8693    public boolean highlightBlockInComboBox(@Nonnull NamedBeanComboBox<Block> inComboBox) {
8694        return highlightBlock(inComboBox.getSelectedItem());
8695    }
8696
8697    /**
8698     * highlight the specified block
8699     *
8700     * @param inBlock the block
8701     * @return true if block was highlighted
8702     */
8703    public boolean highlightBlock(@CheckForNull Block inBlock) {
8704        boolean result = false; // assume failure (pessimist!)
8705
8706        if (leToolBarPanel.blockIDComboBox.getSelectedItem() != inBlock) {
8707            leToolBarPanel.blockIDComboBox.setSelectedItem(inBlock);
8708        }
8709
8710        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class
8711        );
8712        Set<Block> l = leToolBarPanel.blockIDComboBox.getManager().getNamedBeanSet();
8713        for (Block b : l) {
8714            LayoutBlock lb = lbm.getLayoutBlock(b);
8715            if (lb != null) {
8716                boolean enable = ((inBlock != null) && b.equals(inBlock));
8717                lb.setUseExtraColor(enable);
8718                result |= enable;
8719            }
8720        }
8721        return result;
8722    }
8723
8724    /**
8725     * highlight the specified layout block
8726     *
8727     * @param inLayoutBlock the layout block
8728     * @return true if layout block was highlighted
8729     */
8730    public boolean highlightLayoutBlock(@Nonnull LayoutBlock inLayoutBlock) {
8731        return highlightBlock(inLayoutBlock.getBlock());
8732    }
8733
8734    public void setTurnoutCircles(boolean state) {
8735        if (turnoutCirclesWithoutEditMode != state) {
8736            turnoutCirclesWithoutEditMode = state;
8737            if (turnoutCirclesOnCheckBoxMenuItem != null) {
8738                turnoutCirclesOnCheckBoxMenuItem.setSelected(turnoutCirclesWithoutEditMode);
8739            }
8740        }
8741    }
8742
8743    public void setAutoBlockAssignment(boolean boo) {
8744        if (autoAssignBlocks != boo) {
8745            autoAssignBlocks = boo;
8746            if (autoAssignBlocksCheckBoxMenuItem != null) {
8747                autoAssignBlocksCheckBoxMenuItem.setSelected(autoAssignBlocks);
8748            }
8749        }
8750    }
8751
8752    public void setTooltipsNotEdit(boolean state) {
8753        if (tooltipsWithoutEditMode != state) {
8754            tooltipsWithoutEditMode = state;
8755            setTooltipSubMenu();
8756            setTooltipsAlwaysOrNever();
8757        }
8758    }
8759
8760    public void setTooltipsInEdit(boolean state) {
8761        if (tooltipsInEditMode != state) {
8762            tooltipsInEditMode = state;
8763            setTooltipSubMenu();
8764            setTooltipsAlwaysOrNever();
8765        }
8766    }
8767
8768    private void setTooltipsAlwaysOrNever() {
8769        tooltipsAlwaysOrNever = ((tooltipsInEditMode && tooltipsWithoutEditMode) ||
8770                    (!tooltipsInEditMode && !tooltipsWithoutEditMode));
8771    }
8772
8773    private void setTooltipSubMenu() {
8774        if (tooltipNoneMenuItem != null) {
8775            tooltipNoneMenuItem.setSelected((!tooltipsInEditMode) && (!tooltipsWithoutEditMode));
8776            tooltipAlwaysMenuItem.setSelected((tooltipsInEditMode) && (tooltipsWithoutEditMode));
8777            tooltipInEditMenuItem.setSelected((tooltipsInEditMode) && (!tooltipsWithoutEditMode));
8778            tooltipNotInEditMenuItem.setSelected((!tooltipsInEditMode) && (tooltipsWithoutEditMode));
8779        }
8780    }
8781
8782    // accessor routines for turnout size parameters
8783    public void setTurnoutBX(double bx) {
8784        turnoutBX = bx;
8785        setDirty();
8786    }
8787
8788    public double getTurnoutBX() {
8789        return turnoutBX;
8790    }
8791
8792    public void setTurnoutCX(double cx) {
8793        turnoutCX = cx;
8794        setDirty();
8795    }
8796
8797    public double getTurnoutCX() {
8798        return turnoutCX;
8799    }
8800
8801    public void setTurnoutWid(double wid) {
8802        turnoutWid = wid;
8803        setDirty();
8804    }
8805
8806    public double getTurnoutWid() {
8807        return turnoutWid;
8808    }
8809
8810    public void setXOverLong(double lg) {
8811        xOverLong = lg;
8812        setDirty();
8813    }
8814
8815    public double getXOverLong() {
8816        return xOverLong;
8817    }
8818
8819    public void setXOverHWid(double hwid) {
8820        xOverHWid = hwid;
8821        setDirty();
8822    }
8823
8824    public double getXOverHWid() {
8825        return xOverHWid;
8826    }
8827
8828    public void setXOverShort(double sh) {
8829        xOverShort = sh;
8830        setDirty();
8831    }
8832
8833    public double getXOverShort() {
8834        return xOverShort;
8835    }
8836
8837    // reset turnout sizes to program defaults
8838    private void resetTurnoutSize() {
8839        turnoutBX = LayoutTurnout.turnoutBXDefault;
8840        turnoutCX = LayoutTurnout.turnoutCXDefault;
8841        turnoutWid = LayoutTurnout.turnoutWidDefault;
8842        xOverLong = LayoutTurnout.xOverLongDefault;
8843        xOverHWid = LayoutTurnout.xOverHWidDefault;
8844        xOverShort = LayoutTurnout.xOverShortDefault;
8845        setDirty();
8846    }
8847
8848    public void setDirectTurnoutControl(boolean boo) {
8849        useDirectTurnoutControl = boo;
8850        useDirectTurnoutControlCheckBoxMenuItem.setSelected(useDirectTurnoutControl);
8851    }
8852
8853    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8854    public boolean getDirectTurnoutControl() {
8855        return useDirectTurnoutControl;
8856    }
8857
8858    // final initialization routine for loading a LayoutEditor
8859    public void setConnections() {
8860        getLayoutTracks().forEach((lt) -> lt.setObjects(this));
8861        getLEAuxTools().initializeBlockConnectivity();
8862        log.debug("Initializing Block Connectivity for {}", getLayoutName());
8863
8864        // reset the panel changed bit
8865        resetDirty();
8866    }
8867
8868    // these are convenience methods to return rectangles
8869    // to use when (hit point-in-rect testing
8870    //
8871    // compute the control point rect at inPoint
8872    public @Nonnull
8873    Rectangle2D layoutEditorControlRectAt(@Nonnull Point2D inPoint) {
8874        return new Rectangle2D.Double(inPoint.getX() - SIZE,
8875                inPoint.getY() - SIZE, SIZE2, SIZE2);
8876    }
8877
8878    // compute the turnout circle control rect at inPoint
8879    public @Nonnull
8880    Rectangle2D layoutEditorControlCircleRectAt(@Nonnull Point2D inPoint) {
8881        return new Rectangle2D.Double(inPoint.getX() - circleRadius,
8882                inPoint.getY() - circleRadius, circleDiameter, circleDiameter);
8883    }
8884
8885    /**
8886     * Special internal class to allow drawing of layout to a JLayeredPane This
8887     * is the 'target' pane where the layout is displayed
8888     */
8889    @Override
8890    public void paintTargetPanel(@Nonnull Graphics g) {
8891        // Nothing to do here
8892        // All drawing has been moved into LayoutEditorComponent
8893        // which calls draw.
8894        // This is so the layout is drawn at level three
8895        // (above or below the Positionables)
8896    }
8897
8898    // get selection rectangle
8899    @Nonnull
8900    public Rectangle2D getSelectionRect() {
8901        double selX = Math.min(selectionX, selectionX + selectionWidth);
8902        double selY = Math.min(selectionY, selectionY + selectionHeight);
8903        return new Rectangle2D.Double(selX, selY,
8904                Math.abs(selectionWidth), Math.abs(selectionHeight));
8905    }
8906
8907    // set selection rectangle
8908    public void setSelectionRect(@Nonnull Rectangle2D selectionRect) {
8909        // selectionRect = selectionRect.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8910        selectionX = selectionRect.getX();
8911        selectionY = selectionRect.getY();
8912        selectionWidth = selectionRect.getWidth();
8913        selectionHeight = selectionRect.getHeight();
8914
8915        // There's already code in the super class (Editor) to draw
8916        // the selection rect... We just have to set _selectRect
8917        _selectRect = MathUtil.rectangle2DToRectangle(selectionRect);
8918
8919        selectionRect = MathUtil.scale(selectionRect, getZoom());
8920
8921        JComponent targetPanel = getTargetPanel();
8922        Rectangle targetRect = targetPanel.getVisibleRect();
8923        // this will make it the size of the targetRect
8924        // (effectively centering it onscreen)
8925        Rectangle2D selRect2D = MathUtil.inset(selectionRect,
8926                (selectionRect.getWidth() - targetRect.getWidth()) / 2.0,
8927                (selectionRect.getHeight() - targetRect.getHeight()) / 2.0);
8928        // don't let the origin go negative
8929        selRect2D = selRect2D.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8930        Rectangle selRect = MathUtil.rectangle2DToRectangle(selRect2D);
8931        if (!targetRect.contains(selRect)) {
8932            targetPanel.scrollRectToVisible(selRect);
8933        }
8934
8935        clearSelectionGroups();
8936        selectionActive = true;
8937        createSelectionGroups();
8938        // redrawPanel(); // createSelectionGroups already calls this
8939    }
8940
8941    public void setSelectRect(Rectangle rectangle) {
8942        _selectRect = rectangle;
8943    }
8944
8945    /*
8946    // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class
8947    public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<LayoutTrack> layoutTrackClass) {
8948    return getLayoutTracks().stream()
8949    .filter(item -> item instanceof PositionablePoint)
8950    .filter(layoutTrackClass::isInstance)
8951    //.map(layoutTrackClass::cast)  // TODO: Do we need this? if not dead-code-strip
8952    .collect(Collectors.toList());
8953    }
8954
8955    // TODO: This compiles but I can't get the syntax correct to pass the array of (sub-)classes
8956    public List<LayoutTrack> getLayoutTracksOfClasses(@Nonnull List<Class<? extends LayoutTrack>> layoutTrackClasses) {
8957    return getLayoutTracks().stream()
8958    .filter(o -> layoutTrackClasses.contains(o.getClass()))
8959    .collect(Collectors.toList());
8960    }
8961
8962    // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class
8963    public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<? extends LayoutTrack> layoutTrackClass) {
8964    return getLayoutTracksOfClasses(new ArrayList<>(Arrays.asList(layoutTrackClass)));
8965    }
8966
8967    public List<PositionablePoint> getPositionablePoints() {
8968    return getLayoutTracksOfClass(PositionablePoint);
8969    }
8970     */
8971    @Override
8972    public @Nonnull
8973    Stream<LayoutTrack> getLayoutTracksOfClass(Class<? extends LayoutTrack> layoutTrackClass) {
8974        return getLayoutTracks().stream()
8975                .filter(layoutTrackClass::isInstance)
8976                .map(layoutTrackClass::cast);
8977    }
8978
8979    @Override
8980    public @Nonnull
8981    Stream<LayoutTrackView> getLayoutTrackViewsOfClass(Class<? extends LayoutTrackView> layoutTrackViewClass) {
8982        return getLayoutTrackViews().stream()
8983                .filter(layoutTrackViewClass::isInstance)
8984                .map(layoutTrackViewClass::cast);
8985    }
8986
8987    @Override
8988    public @Nonnull
8989    List<PositionablePointView> getPositionablePointViews() {
8990        return getLayoutTrackViewsOfClass(PositionablePointView.class)
8991                .map(PositionablePointView.class::cast)
8992                .collect(Collectors.toCollection(ArrayList::new));
8993    }
8994
8995    @Override
8996    public @Nonnull
8997    List<PositionablePoint> getPositionablePoints() {
8998        return getLayoutTracksOfClass(PositionablePoint.class)
8999                .map(PositionablePoint.class::cast)
9000                .collect(Collectors.toCollection(ArrayList::new));
9001    }
9002
9003    public @Nonnull
9004    List<LayoutSlipView> getLayoutSlipViews() {
9005        return getLayoutTrackViewsOfClass(LayoutSlipView.class)
9006                .map(LayoutSlipView.class::cast)
9007                .collect(Collectors.toCollection(ArrayList::new));
9008    }
9009
9010    @Override
9011    public @Nonnull
9012    List<LayoutSlip> getLayoutSlips() {
9013        return getLayoutTracksOfClass(LayoutSlip.class)
9014                .map(LayoutSlip.class::cast)
9015                .collect(Collectors.toCollection(ArrayList::new));
9016    }
9017
9018    @Override
9019    public @Nonnull
9020    List<TrackSegmentView> getTrackSegmentViews() {
9021        return getLayoutTrackViewsOfClass(TrackSegmentView.class)
9022                .map(TrackSegmentView.class::cast)
9023                .collect(Collectors.toCollection(ArrayList::new));
9024    }
9025
9026    @Override
9027    public @Nonnull
9028    List<TrackSegment> getTrackSegments() {
9029        return getLayoutTracksOfClass(TrackSegment.class)
9030                .map(TrackSegment.class::cast)
9031                .collect(Collectors.toCollection(ArrayList::new));
9032    }
9033
9034    public @Nonnull
9035    List<LayoutTurnoutView> getLayoutTurnoutViews() { // this specifically does not include slips
9036        return getLayoutTrackViews().stream() // next line excludes LayoutSlips
9037                .filter((o) -> (!(o instanceof LayoutSlipView) && (o instanceof LayoutTurnoutView)))
9038                .map(LayoutTurnoutView.class::cast)
9039                .collect(Collectors.toCollection(ArrayList::new));
9040    }
9041
9042    @Override
9043    public @Nonnull
9044    List<LayoutTurnout> getLayoutTurnouts() { // this specifically does not include slips
9045        return getLayoutTracks().stream() // next line excludes LayoutSlips
9046                .filter((o) -> (!(o instanceof LayoutSlip) && (o instanceof LayoutTurnout)))
9047                .map(LayoutTurnout.class::cast)
9048                .collect(Collectors.toCollection(ArrayList::new));
9049    }
9050
9051    @Override
9052    public @Nonnull
9053    List<LayoutTurntable> getLayoutTurntables() {
9054        return getLayoutTracksOfClass(LayoutTurntable.class)
9055                .map(LayoutTurntable.class::cast)
9056                .collect(Collectors.toCollection(ArrayList::new));
9057    }
9058
9059    public @Nonnull
9060    List<LayoutTurntableView> getLayoutTurntableViews() {
9061        return getLayoutTrackViewsOfClass(LayoutTurntableView.class)
9062                .map(LayoutTurntableView.class::cast)
9063                .collect(Collectors.toCollection(ArrayList::new));
9064    }
9065
9066    @Override
9067    public @Nonnull
9068    List<LayoutTraverser> getLayoutTraversers() {
9069        return getLayoutTracksOfClass(LayoutTraverser.class)
9070                .map(LayoutTraverser.class::cast)
9071                .collect(Collectors.toCollection(ArrayList::new));
9072    }
9073
9074    public @Nonnull
9075    List<LayoutTraverserView> getLayoutTraverserViews() {
9076        return getLayoutTrackViewsOfClass(LayoutTraverserView.class)
9077                .map(LayoutTraverserView.class::cast)
9078                .collect(Collectors.toCollection(ArrayList::new));
9079    }
9080
9081    @Override
9082    public @Nonnull
9083    List<LevelXing> getLevelXings() {
9084        return getLayoutTracksOfClass(LevelXing.class)
9085                .map(LevelXing.class::cast)
9086                .collect(Collectors.toCollection(ArrayList::new));
9087    }
9088
9089    @Override
9090    public @Nonnull
9091    List<LevelXingView> getLevelXingViews() {
9092        return getLayoutTrackViewsOfClass(LevelXingView.class)
9093                .map(LevelXingView.class::cast)
9094                .collect(Collectors.toCollection(ArrayList::new));
9095    }
9096
9097    /**
9098     * Read-only access to the list of LayoutTrack family objects. The returned
9099     * list will throw UnsupportedOperationException if you attempt to modify
9100     * it.
9101     *
9102     * @return unmodifiable copy of layout track list.
9103     */
9104    @Override
9105    @Nonnull
9106    public final List<LayoutTrack> getLayoutTracks() {
9107        return Collections.unmodifiableList(layoutTrackList);
9108    }
9109
9110    public @Nonnull
9111    List<LayoutTurnoutView> getLayoutTurnoutAndSlipViews() {
9112        return getLayoutTrackViewsOfClass(LayoutTurnoutView.class
9113        )
9114                .map(LayoutTurnoutView.class::cast)
9115                .collect(Collectors.toCollection(ArrayList::new));
9116    }
9117
9118    @Override
9119    public @Nonnull
9120    List<LayoutTurnout> getLayoutTurnoutsAndSlips() {
9121        return getLayoutTracksOfClass(LayoutTurnout.class
9122        )
9123                .map(LayoutTurnout.class::cast)
9124                .collect(Collectors.toCollection(ArrayList::new));
9125    }
9126
9127    /**
9128     * Read-only access to the list of LayoutTrackView family objects. The
9129     * returned list will throw UnsupportedOperationException if you attempt to
9130     * modify it.
9131     *
9132     * @return unmodifiable copy of track views.
9133     */
9134    @Override
9135    @Nonnull
9136    public final List<LayoutTrackView> getLayoutTrackViews() {
9137        return Collections.unmodifiableList(layoutTrackViewList);
9138    }
9139
9140    private final List<LayoutTrack> layoutTrackList = new ArrayList<>();
9141    private final List<LayoutTrackView> layoutTrackViewList = new ArrayList<>();
9142    private final Map<LayoutTrack, LayoutTrackView> trkToView = new HashMap<>();
9143    private final Map<LayoutTrackView, LayoutTrack> viewToTrk = new HashMap<>();
9144
9145    // temporary
9146    @Override
9147    public final LayoutTrackView getLayoutTrackView(LayoutTrack trk) {
9148        LayoutTrackView lv = trkToView.get(trk);
9149        if (lv == null) {
9150            log.warn("No View found for {} class {}", trk, trk.getClass());
9151            throw new IllegalArgumentException("No View found: " + trk.getClass());
9152        }
9153        return lv;
9154    }
9155
9156    // temporary
9157    @Override
9158    public final LevelXingView getLevelXingView(LevelXing xing) {
9159        LayoutTrackView lv = trkToView.get(xing);
9160        if (lv == null) {
9161            log.warn("No View found for {} class {}", xing, xing.getClass());
9162            throw new IllegalArgumentException("No View found: " + xing.getClass());
9163        }
9164        if (lv instanceof LevelXingView) {
9165            return (LevelXingView) lv;
9166        } else {
9167            log.error("wrong type {} {} found {}", xing, xing.getClass(), lv);
9168        }
9169        throw new IllegalArgumentException("Wrong type: " + xing.getClass());
9170    }
9171
9172    // temporary
9173    @Override
9174    public final LayoutTurnoutView getLayoutTurnoutView(LayoutTurnout to) {
9175        LayoutTrackView lv = trkToView.get(to);
9176        if (lv == null) {
9177            log.warn("No View found for {} class {}", to, to.getClass());
9178            throw new IllegalArgumentException("No View found: " + to);
9179        }
9180        if (lv instanceof LayoutTurnoutView) {
9181            return (LayoutTurnoutView) lv;
9182        } else {
9183            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9184        }
9185        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9186    }
9187
9188    // temporary
9189    @Override
9190    public final LayoutTurntableView getLayoutTurntableView(LayoutTurntable to) {
9191        LayoutTrackView lv = trkToView.get(to);
9192        if (lv == null) {
9193            log.warn("No View found for {} class {}", to, to.getClass());
9194            throw new IllegalArgumentException("No matching View found: " + to);
9195        }
9196        if (lv instanceof LayoutTurntableView) {
9197            return (LayoutTurntableView) lv;
9198        } else {
9199            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9200        }
9201        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9202    }
9203
9204    // temporary
9205    @Override
9206    public final LayoutTraverserView getLayoutTraverserView(LayoutTraverser to) {
9207        LayoutTrackView lv = trkToView.get(to);
9208        if (lv == null) {
9209            log.warn("No View found for {} class {}", to, to.getClass());
9210            throw new IllegalArgumentException("No matching View found: " + to);
9211        }
9212        if (lv instanceof LayoutTraverserView) {
9213            return (LayoutTraverserView) lv;
9214        } else {
9215            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9216        }
9217        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9218    }
9219
9220    // temporary
9221    public final LayoutSlipView getLayoutSlipView(LayoutSlip to) {
9222        LayoutTrackView lv = trkToView.get(to);
9223        if (lv == null) {
9224            log.warn("No View found for {} class {}", to, to.getClass());
9225            throw new IllegalArgumentException("No matching View found: " + to);
9226        }
9227        if (lv instanceof LayoutSlipView) {
9228            return (LayoutSlipView) lv;
9229        } else {
9230            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9231        }
9232        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9233    }
9234
9235    // temporary
9236    @Override
9237    public final TrackSegmentView getTrackSegmentView(TrackSegment to) {
9238        LayoutTrackView lv = trkToView.get(to);
9239        if (lv == null) {
9240            log.warn("No View found for {} class {}", to, to.getClass());
9241            throw new IllegalArgumentException("No matching View found: " + to);
9242        }
9243        if (lv instanceof TrackSegmentView) {
9244            return (TrackSegmentView) lv;
9245        } else {
9246            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9247        }
9248        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9249    }
9250
9251    // temporary
9252    @Override
9253    public final PositionablePointView getPositionablePointView(PositionablePoint to) {
9254        LayoutTrackView lv = trkToView.get(to);
9255        if (lv == null) {
9256            log.warn("No View found for {} class {}", to, to.getClass());
9257            throw new IllegalArgumentException("No matching View found: " + to);
9258        }
9259        if (lv instanceof PositionablePointView) {
9260            return (PositionablePointView) lv;
9261        } else {
9262            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9263        }
9264        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9265    }
9266
9267    /**
9268     * Add a LayoutTrack and LayoutTrackView to the list of LayoutTrack family
9269     * objects.
9270     *
9271     * @param trk the layout track to add.
9272     */
9273    @Override
9274    public final void addLayoutTrack(@Nonnull LayoutTrack trk, @Nonnull LayoutTrackView v) {
9275        log.trace("addLayoutTrack {}", trk);
9276        if (layoutTrackList.contains(trk)) {
9277            log.warn("LayoutTrack {} already being maintained", trk.getName());
9278        }
9279
9280        layoutTrackList.add(trk);
9281        layoutTrackViewList.add(v);
9282        trkToView.put(trk, v);
9283        viewToTrk.put(v, trk);
9284
9285        unionToPanelBounds(v.getBounds()); // temporary - this should probably _not_ be in the topological part
9286
9287    }
9288
9289    /**
9290     * If item present, delete from the list of LayoutTracks and force a dirty
9291     * redraw.
9292     *
9293     * @param trk the layout track to remove and redraw.
9294     * @return true is item was deleted and a redraw done.
9295     */
9296    public final boolean removeLayoutTrackAndRedraw(@Nonnull LayoutTrack trk) {
9297        log.trace("removeLayoutTrackAndRedraw {}", trk);
9298        if (layoutTrackList.contains(trk)) {
9299            removeLayoutTrack(trk);
9300            setDirty();
9301            redrawPanel();
9302            log.trace("removeLayoutTrackAndRedraw present {}", trk);
9303            return true;
9304        }
9305        log.trace("removeLayoutTrackAndRedraw absent {}", trk);
9306        return false;
9307    }
9308
9309    /**
9310     * If item present, delete from the list of LayoutTracks and force a dirty
9311     * redraw.
9312     *
9313     * @param trk the layout track to remove.
9314     */
9315    @Override
9316    public final void removeLayoutTrack(@Nonnull LayoutTrack trk) {
9317        log.trace("removeLayoutTrack {}", trk);
9318        layoutTrackList.remove(trk);
9319        LayoutTrackView v = trkToView.get(trk);
9320        layoutTrackViewList.remove(v);
9321        trkToView.remove(trk);
9322        viewToTrk.remove(v);
9323    }
9324
9325    /**
9326     * Clear the list of layout tracks. Not intended for general use.
9327     * <p>
9328     */
9329    private void clearLayoutTracks() {
9330        layoutTrackList.clear();
9331        layoutTrackViewList.clear();
9332        trkToView.clear();
9333        viewToTrk.clear();
9334    }
9335
9336    @Override
9337    public @Nonnull
9338    List<LayoutShape> getLayoutShapes() {
9339        return layoutShapes;
9340    }
9341
9342    public void sortLayoutShapesByLevel() {
9343        layoutShapes.sort((lhs, rhs) -> {
9344            // -1 == less than, 0 == equal, +1 == greater than
9345            return Integer.signum(lhs.getLevel() - rhs.getLevel());
9346        });
9347    }
9348
9349    /**
9350     * {@inheritDoc}
9351     * <p>
9352     * This implementation is temporary, using the on-screen points from the
9353     * LayoutTrackViews via @{link LayoutEditor#getCoords}.
9354     */
9355    @Override
9356    public int computeDirection(LayoutTrack trk1, HitPointType h1, LayoutTrack trk2, HitPointType h2) {
9357        return Path.computeDirection(
9358                getCoords(trk1, h1),
9359                getCoords(trk2, h2)
9360        );
9361    }
9362
9363    @Override
9364    public int computeDirectionToCenter(@Nonnull LayoutTrack trk1, @Nonnull HitPointType h1, @Nonnull PositionablePoint p) {
9365        return Path.computeDirection(
9366                getCoords(trk1, h1),
9367                getPositionablePointView(p).getCoordsCenter()
9368        );
9369    }
9370
9371    @Override
9372    public int computeDirectionFromCenter(@Nonnull PositionablePoint p, @Nonnull LayoutTrack trk1, @Nonnull HitPointType h1) {
9373        return Path.computeDirection(
9374                getPositionablePointView(p).getCoordsCenter(),
9375                getCoords(trk1, h1)
9376        );
9377    }
9378
9379    @Override
9380    public boolean showAlignPopup(@Nonnull Positionable l) {
9381        return false;
9382    }
9383
9384    @Override
9385    public void showToolTip(
9386            @Nonnull Positionable selection,
9387            @Nonnull JmriMouseEvent event) {
9388        ToolTip tip = selection.getToolTip();
9389        tip.setLocation(selection.getX() + selection.getWidth() / 2, selection.getY() + selection.getHeight());
9390        setToolTip(tip);
9391    }
9392
9393    @Override
9394    public void addToPopUpMenu(
9395            @Nonnull NamedBean nb,
9396            @Nonnull JMenuItem item,
9397            int menu) {
9398        if ((nb == null) || (item == null)) {
9399            return;
9400        }
9401
9402        List<?> theList = null;
9403
9404        if (nb instanceof Sensor) {
9405            theList = sensorList;
9406        } else if (nb instanceof Turnout) {
9407            theList = turnoutList;
9408        } else if (nb instanceof SignalHead) {
9409            theList = signalList;
9410        } else if (nb instanceof SignalMast) {
9411            theList = signalMastList;
9412        } else if (nb instanceof Block) {
9413            theList = blockContentsLabelList;
9414        } else if (nb instanceof Memory) {
9415            theList = memoryLabelList;    // Memory Input Icon not supported at this time.
9416        } else if (nb instanceof GlobalVariable) {
9417            theList = globalVariableLabelList;
9418        }
9419        if (theList != null) {
9420            for (Object o : theList) {
9421                PositionableLabel si = (PositionableLabel) o;
9422                if ((si.getNamedBean() == nb) && (si.getPopupUtility() != null)) {
9423                    if (menu != Editor.VIEWPOPUPONLY) {
9424                        si.getPopupUtility().addEditPopUpMenu(item);
9425                    }
9426                    if (menu != Editor.EDITPOPUPONLY) {
9427                        si.getPopupUtility().addViewPopUpMenu(item);
9428                    }
9429                }
9430            }
9431        } else if (nb instanceof Turnout) {
9432            for (LayoutTurnoutView ltv : getLayoutTurnoutAndSlipViews()) {
9433                if (ltv.getTurnout().equals(nb)) {
9434                    if (menu != Editor.VIEWPOPUPONLY) {
9435                        ltv.addEditPopUpMenu(item);
9436                    }
9437                    if (menu != Editor.EDITPOPUPONLY) {
9438                        ltv.addViewPopUpMenu(item);
9439                    }
9440                }
9441            }
9442        }
9443    }
9444
9445    @Override
9446    public @Nonnull
9447    String toString() {
9448        return String.format("LayoutEditor: %s", getLayoutName());
9449    }
9450
9451    @Override
9452    public void vetoableChange(
9453            @Nonnull PropertyChangeEvent evt)
9454            throws PropertyVetoException {
9455        NamedBean nb = (NamedBean) evt.getOldValue();
9456
9457        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
9458            StringBuilder message = new StringBuilder();
9459            message.append(Bundle.getMessage("VetoInUseLayoutEditorHeader", toString())); // NOI18N
9460            message.append("<ul>");
9461            boolean found = false;
9462
9463            if (nb instanceof SignalHead) {
9464                if (containsSignalHead((SignalHead) nb)) {
9465                    found = true;
9466                    message.append("<li>");
9467                    message.append(Bundle.getMessage("VetoSignalHeadIconFound"));
9468                    message.append("</li>");
9469                }
9470                LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
9471
9472                if (lt != null) {
9473                    message.append("<li>");
9474                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToTurnout", lt.getTurnoutName()));
9475                    message.append("</li>");
9476                }
9477                PositionablePoint p = finder.findPositionablePointByBean(nb);
9478
9479                if (p != null) {
9480                    message.append("<li>");
9481                    // Need to expand to get the names of blocks
9482                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToPoint"));
9483                    message.append("</li>");
9484                }
9485                LevelXing lx = finder.findLevelXingByBean(nb);
9486
9487                if (lx != null) {
9488                    message.append("<li>");
9489                    // Need to expand to get the names of blocks
9490                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToLevelXing"));
9491                    message.append("</li>");
9492                }
9493                LayoutSlip ls = finder.findLayoutSlipByBean(nb);
9494
9495                if (ls != null) {
9496                    message.append("<li>");
9497                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToLayoutSlip", ls.getTurnoutName()));
9498                    message.append("</li>");
9499                }
9500            } else if (nb instanceof Turnout) {
9501                LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
9502
9503                if (lt != null) {
9504                    found = true;
9505                    message.append("<li>");
9506                    message.append(Bundle.getMessage("VetoTurnoutIconFound"));
9507                    message.append("</li>");
9508                }
9509
9510                for (LayoutTurnout t : getLayoutTurnouts()) {
9511                    if (t.getLinkedTurnoutName() != null) {
9512                        String uname = nb.getUserName();
9513
9514                        if (nb.getSystemName().equals(t.getLinkedTurnoutName())
9515                                || ((uname != null) && uname.equals(t.getLinkedTurnoutName()))) {
9516                            found = true;
9517                            message.append("<li>");
9518                            message.append(Bundle.getMessage("VetoLinkedTurnout", t.getTurnoutName()));
9519                            message.append("</li>");
9520                        }
9521                    }
9522
9523                    if (nb.equals(t.getSecondTurnout())) {
9524                        found = true;
9525                        message.append("<li>");
9526                        message.append(Bundle.getMessage("VetoSecondTurnout", t.getTurnoutName()));
9527                        message.append("</li>");
9528                    }
9529                }
9530                LayoutSlip ls = finder.findLayoutSlipByBean(nb);
9531
9532                if (ls != null) {
9533                    found = true;
9534                    message.append("<li>");
9535                    message.append(Bundle.getMessage("VetoSlipIconFound", ls.getDisplayName()));
9536                    message.append("</li>");
9537                }
9538
9539                for (LayoutTurntable lx : getLayoutTurntables()) {
9540                    if (lx.isTurnoutControlled()) {
9541                        for (int i = 0; i < lx.getNumberRays(); i++) {
9542                            if (nb.equals(lx.getRayTurnout(i))) {
9543                                found = true;
9544                                message.append("<li>");
9545                                message.append(Bundle.getMessage("VetoRayTurntableControl", lx.getId()));
9546                                message.append("</li>");
9547                                break;
9548                            }
9549                        }
9550                    }
9551                }
9552                for (LayoutTraverser lx : getLayoutTraversers()) {
9553                    if (lx.isTurnoutControlled()) {
9554                        for (int i = 0; i < lx.getNumberSlots(); i++) {
9555                            if (nb.equals(lx.getSlotTurnout(i))) {
9556                                found = true;
9557                                message.append("<li>");
9558                                message.append(Bundle.getMessage("VetoSlotTraverserControl", lx.getId()));
9559                                message.append("</li>");
9560                                break;
9561                            }
9562                        }
9563                    }
9564                }
9565            }
9566
9567            if (nb instanceof SignalMast) {
9568                if (containsSignalMast((SignalMast) nb)) {
9569                    message.append("<li>");
9570                    message.append("As an Icon");
9571                    message.append("</li>");
9572                    found = true;
9573                }
9574                String foundelsewhere = findBeanUsage(nb);
9575
9576                if (foundelsewhere != null) {
9577                    message.append(foundelsewhere);
9578                    found = true;
9579                }
9580            }
9581
9582            if (nb instanceof Sensor) {
9583                int count = 0;
9584
9585                for (SensorIcon si : sensorList) {
9586                    if (nb.equals(si.getNamedBean())) {
9587                        count++;
9588                        found = true;
9589                    }
9590                }
9591
9592                if (count > 0) {
9593                    message.append("<li>");
9594                    message.append(String.format("As an Icon %s times", count));
9595                    message.append("</li>");
9596                }
9597                String foundelsewhere = findBeanUsage(nb);
9598
9599                if (foundelsewhere != null) {
9600                    message.append(foundelsewhere);
9601                    found = true;
9602                }
9603            }
9604
9605            if (nb instanceof Turnout) {
9606                int count = 0;
9607
9608                for (TurnoutIcon si : turnoutList) {
9609                    if (nb.equals(si.getNamedBean())) {
9610                        count++;
9611                        found = true;
9612                    }
9613                }
9614
9615                if (count > 0) {
9616                    message.append("<li>");
9617                    message.append(String.format("As an Icon %s times", count));
9618                    message.append("</li>");
9619                }
9620                String foundelsewhere = findBeanUsage(nb);
9621
9622                if (foundelsewhere != null) {
9623                    message.append(foundelsewhere);
9624                    found = true;
9625                }
9626            }
9627
9628            if (nb instanceof Memory) {
9629                for (MemoryIcon si : memoryLabelList) {
9630                    if (nb.equals(si.getMemory())) {
9631                        found = true;
9632                        message.append("<li>");
9633                        message.append(Bundle.getMessage("VetoMemoryIconFound"));
9634                        message.append("</li>");
9635                    }
9636                }
9637                for (MemoryInputIcon si : memoryInputList) {
9638                    if (nb.equals(si.getMemory())) {
9639                        found = true;
9640                        message.append("<li>");
9641                        message.append(Bundle.getMessage("VetoMemoryIconFound"));
9642                        message.append("</li>");
9643                    }
9644                }
9645            }
9646
9647            if (nb instanceof GlobalVariable) {
9648                for (GlobalVariableIcon si : globalVariableLabelList) {
9649                    if (nb.equals(si.getGlobalVariable())) {
9650                        found = true;
9651                        message.append("<li>");
9652                        message.append(Bundle.getMessage("VetoGlobalVariableIconFound"));
9653                        message.append("</li>");
9654                    }
9655                }
9656            }
9657
9658            if (found) {
9659                message.append("</ul>");
9660                message.append(Bundle.getMessage("VetoReferencesWillBeRemoved")); // NOI18N
9661                throw new PropertyVetoException(message.toString(), evt);
9662            }
9663        } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N
9664            if (nb instanceof SignalHead) {
9665                removeSignalHead((SignalHead) nb);
9666                removeBeanRefs(nb);
9667            }
9668
9669            if (nb instanceof Turnout) {
9670                LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
9671
9672                if (lt != null) {
9673                    lt.setTurnout("");
9674                }
9675
9676                for (LayoutTurnout t : getLayoutTurnouts()) {
9677                    if (t.getLinkedTurnoutName() != null) {
9678                        if (t.getLinkedTurnoutName().equals(nb.getSystemName())
9679                                || ((nb.getUserName() != null) && t.getLinkedTurnoutName().equals(nb.getUserName()))) {
9680                            t.setLinkedTurnoutName("");
9681                        }
9682                    }
9683
9684                    if (nb.equals(t.getSecondTurnout())) {
9685                        t.setSecondTurnout("");
9686                    }
9687                }
9688
9689                for (LayoutSlip sl : getLayoutSlips()) {
9690                    if (nb.equals(sl.getTurnout())) {
9691                        sl.setTurnout("");
9692                    }
9693
9694                    if (nb.equals(sl.getTurnoutB())) {
9695                        sl.setTurnoutB("");
9696                    }
9697                }
9698
9699                for (LayoutTurntable lx : getLayoutTurntables()) {
9700                    if (lx.isTurnoutControlled()) {
9701                        for (int i = 0; i < lx.getNumberRays(); i++) {
9702                            if (nb.equals(lx.getRayTurnout(i))) {
9703                                lx.setRayTurnout(i, null, NamedBean.UNKNOWN);
9704                            }
9705                        }
9706                    }
9707                }
9708
9709                for (LayoutTraverser lx : getLayoutTraversers()) {
9710                    if (lx.isTurnoutControlled()) {
9711                        for (int i = 0; i < lx.getNumberSlots(); i++) {
9712                            if (nb.equals(lx.getSlotTurnout(i))) {
9713                                lx.setSlotTurnout(i, null, NamedBean.UNKNOWN);
9714                            }
9715                        }
9716                    }
9717                }
9718            }
9719
9720            if (nb instanceof SignalMast) {
9721                removeBeanRefs(nb);
9722
9723                if (containsSignalMast((SignalMast) nb)) {
9724                    Iterator<SignalMastIcon> icon = signalMastList.iterator();
9725
9726                    while (icon.hasNext()) {
9727                        SignalMastIcon i = icon.next();
9728
9729                        if (i.getSignalMast().equals(nb)) {
9730                            icon.remove();
9731                            super.removeFromContents(i);
9732                        }
9733                    }
9734                    setDirty();
9735                    redrawPanel();
9736                }
9737            }
9738
9739            if (nb instanceof Sensor) {
9740                removeBeanRefs(nb);
9741                Iterator<SensorIcon> icon = sensorImage.iterator();
9742
9743                while (icon.hasNext()) {
9744                    SensorIcon i = icon.next();
9745
9746                    if (nb.equals(i.getSensor())) {
9747                        icon.remove();
9748                        super.removeFromContents(i);
9749                    }
9750                }
9751                setDirty();
9752                redrawPanel();
9753            }
9754
9755            if (nb instanceof Turnout) {
9756                removeBeanRefs(nb);
9757                Iterator<TurnoutIcon> icon = turnoutImage.iterator();
9758
9759                while (icon.hasNext()) {
9760                    TurnoutIcon i = icon.next();
9761
9762                    if (nb.equals(i.getTurnout())) {
9763                        icon.remove();
9764                        super.removeFromContents(i);
9765                    }
9766                }
9767                setDirty();
9768                redrawPanel();
9769            }
9770
9771            if (nb instanceof Memory) {
9772                Iterator<MemoryIcon> icon = memoryLabelList.iterator();
9773
9774                while (icon.hasNext()) {
9775                    MemoryIcon i = icon.next();
9776
9777                    if (nb.equals(i.getMemory())) {
9778                        icon.remove();
9779                        super.removeFromContents(i);
9780                    }
9781                }
9782
9783                Iterator<MemoryInputIcon> input = memoryInputList.iterator();
9784
9785                while (input.hasNext()) {
9786                    MemoryInputIcon ipt = input.next();
9787
9788                    if (nb.equals(ipt.getMemory())) {
9789                        input.remove();
9790                        super.removeFromContents(ipt);
9791                    }
9792                }
9793            }
9794
9795            if (nb instanceof GlobalVariable) {
9796                Iterator<GlobalVariableIcon> icon = globalVariableLabelList.iterator();
9797
9798                while (icon.hasNext()) {
9799                    GlobalVariableIcon i = icon.next();
9800
9801                    if (nb.equals(i.getGlobalVariable())) {
9802                        icon.remove();
9803                        super.removeFromContents(i);
9804                    }
9805                }
9806            }
9807        }
9808    }
9809
9810    @Override
9811    public void dispose() {
9812        if (leToolBarPanel != null) {
9813            leToolBarPanel.dispose();
9814        }
9815        super.dispose();
9816
9817    }
9818
9819    // package protected
9820    class TurnoutComboBoxPopupMenuListener implements PopupMenuListener {
9821
9822        private final NamedBeanComboBox<Turnout> comboBox;
9823        private final List<Turnout> currentTurnouts;
9824
9825        public TurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) {
9826            this.comboBox = comboBox;
9827            this.currentTurnouts = currentTurnouts;
9828        }
9829
9830        @Override
9831        public void popupMenuWillBecomeVisible(PopupMenuEvent event) {
9832            // This method is called before the popup menu becomes visible.
9833            log.debug("PopupMenuWillBecomeVisible");
9834            Set<Turnout> l = new HashSet<>();
9835            comboBox.getManager().getNamedBeanSet().forEach((turnout) -> {
9836                if (!currentTurnouts.contains(turnout)) {
9837                    if (!validatePhysicalTurnout(turnout.getDisplayName(), null)) {
9838                        l.add(turnout);
9839                    }
9840                }
9841            });
9842            comboBox.setExcludedItems(l);
9843        }
9844
9845        @Override
9846        public void popupMenuWillBecomeInvisible(PopupMenuEvent event) {
9847            // This method is called before the popup menu becomes invisible
9848            log.debug("PopupMenuWillBecomeInvisible");
9849        }
9850
9851        @Override
9852        public void popupMenuCanceled(PopupMenuEvent event) {
9853            // This method is called when the popup menu is canceled
9854            log.debug("PopupMenuCanceled");
9855        }
9856    }
9857
9858    /**
9859     * Create a listener that will exclude turnouts that are present in the
9860     * current panel.
9861     *
9862     * @param comboBox The NamedBeanComboBox that contains the turnout list.
9863     * @return A PopupMenuListener
9864     */
9865    public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox) {
9866        return new TurnoutComboBoxPopupMenuListener(comboBox, new ArrayList<>());
9867    }
9868
9869    /**
9870     * Create a listener that will exclude turnouts that are present in the
9871     * current panel. The list of current turnouts are not excluded.
9872     *
9873     * @param comboBox        The NamedBeanComboBox that contains the turnout
9874     *                        list.
9875     * @param currentTurnouts The turnouts to be left in the turnout list.
9876     * @return A PopupMenuListener
9877     */
9878    public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) {
9879        return new TurnoutComboBoxPopupMenuListener(comboBox, currentTurnouts);
9880    }
9881
9882    List<NamedBeanUsageReport> usageReport;
9883
9884    @Override
9885    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
9886        usageReport = new ArrayList<>();
9887        if (bean != null) {
9888            usageReport = super.getUsageReport(bean);
9889
9890            // LE Specific checks
9891            // Turnouts
9892            findTurnoutUsage(bean);
9893
9894            // Check A, EB, EC for sensors, masts, heads
9895            findPositionalUsage(bean);
9896
9897            // Level Crossings
9898            findXingWhereUsed(bean);
9899
9900            // Track segments
9901            findSegmentWhereUsed(bean);
9902        }
9903        return usageReport;
9904    }
9905
9906    void findTurnoutUsage(NamedBean bean) {
9907        for (LayoutTurnout turnout : getLayoutTurnoutsAndSlips()) {
9908            String data = getUsageData(turnout);
9909
9910            if (bean.equals(turnout.getTurnout())) {
9911                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout", data));
9912            }
9913            if (bean.equals(turnout.getSecondTurnout())) {
9914                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout2", data));
9915            }
9916
9917            if (isLBLockUsed(bean, turnout.getLayoutBlock())) {
9918                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9919            }
9920            if (turnout.hasEnteringDoubleTrack()) {
9921                if (isLBLockUsed(bean, turnout.getLayoutBlockB())) {
9922                    usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9923                }
9924                if (isLBLockUsed(bean, turnout.getLayoutBlockC())) {
9925                    usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9926                }
9927                if (isLBLockUsed(bean, turnout.getLayoutBlockD())) {
9928                    usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9929                }
9930            }
9931
9932            if (bean.equals(turnout.getSensorA())) {
9933                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9934            }
9935            if (bean.equals(turnout.getSensorB())) {
9936                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9937            }
9938            if (bean.equals(turnout.getSensorC())) {
9939                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9940            }
9941            if (bean.equals(turnout.getSensorD())) {
9942                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9943            }
9944
9945            if (bean.equals(turnout.getSignalAMast())) {
9946                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9947            }
9948            if (bean.equals(turnout.getSignalBMast())) {
9949                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9950            }
9951            if (bean.equals(turnout.getSignalCMast())) {
9952                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9953            }
9954            if (bean.equals(turnout.getSignalDMast())) {
9955                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9956            }
9957
9958            if (bean.equals(turnout.getSignalA1())) {
9959                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9960            }
9961            if (bean.equals(turnout.getSignalA2())) {
9962                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9963            }
9964            if (bean.equals(turnout.getSignalA3())) {
9965                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9966            }
9967            if (bean.equals(turnout.getSignalB1())) {
9968                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9969            }
9970            if (bean.equals(turnout.getSignalB2())) {
9971                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9972            }
9973            if (bean.equals(turnout.getSignalC1())) {
9974                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9975            }
9976            if (bean.equals(turnout.getSignalC2())) {
9977                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9978            }
9979            if (bean.equals(turnout.getSignalD1())) {
9980                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9981            }
9982            if (bean.equals(turnout.getSignalD2())) {
9983                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9984            }
9985        }
9986    }
9987
9988    void findPositionalUsage(NamedBean bean) {
9989        for (PositionablePoint point : getPositionablePoints()) {
9990            String data = getUsageData(point);
9991            if (bean.equals(point.getEastBoundSensor())) {
9992                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data));
9993            }
9994            if (bean.equals(point.getWestBoundSensor())) {
9995                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data));
9996            }
9997            if (bean.equals(point.getEastBoundSignalHead())) {
9998                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data));
9999            }
10000            if (bean.equals(point.getWestBoundSignalHead())) {
10001                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data));
10002            }
10003            if (bean.equals(point.getEastBoundSignalMast())) {
10004                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data));
10005            }
10006            if (bean.equals(point.getWestBoundSignalMast())) {
10007                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data));
10008            }
10009        }
10010    }
10011
10012    void findSegmentWhereUsed(NamedBean bean) {
10013        for (TrackSegment segment : getTrackSegments()) {
10014            if (isLBLockUsed(bean, segment.getLayoutBlock())) {
10015                String data = getUsageData(segment);
10016                usageReport.add(new NamedBeanUsageReport("LayoutEditorSegmentBlock", data));
10017            }
10018        }
10019    }
10020
10021    void findXingWhereUsed(NamedBean bean) {
10022        for (LevelXing xing : getLevelXings()) {
10023            String data = getUsageData(xing);
10024            if (isLBLockUsed(bean, xing.getLayoutBlockAC())) {
10025                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data));
10026            }
10027            if (isLBLockUsed(bean, xing.getLayoutBlockBD())) {
10028                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data));
10029            }
10030            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTA)) {
10031                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10032            }
10033            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTB)) {
10034                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10035            }
10036            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTC)) {
10037                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10038            }
10039            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTD)) {
10040                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10041            }
10042        }
10043    }
10044
10045    String getUsageData(LayoutTrack track) {
10046        LayoutTrackView trackView = getLayoutTrackView(track);
10047        Point2D point = trackView.getCoordsCenter();
10048        if (trackView instanceof TrackSegmentView) {
10049            TrackSegmentView segmentView = (TrackSegmentView) trackView;
10050            point = new Point2D.Double(segmentView.getCentreSegX(), segmentView.getCentreSegY());
10051        }
10052        return String.format("%s :: x=%d, y=%d",
10053                track.getClass().getSimpleName(),
10054                Math.round(point.getX()),
10055                Math.round(point.getY()));
10056    }
10057
10058    boolean isLBLockUsed(NamedBean bean, LayoutBlock lblock) {
10059        boolean result = false;
10060        if (lblock != null) {
10061            if (bean.equals(lblock.getBlock())) {
10062                result = true;
10063            }
10064        }
10065        return result;
10066    }
10067
10068    boolean isUsedInXing(NamedBean bean, LevelXing xing, LevelXing.Geometry point) {
10069        boolean result = false;
10070        if (bean.equals(xing.getSensor(point))) {
10071            result = true;
10072        }
10073        if (bean.equals(xing.getSignalHead(point))) {
10074            result = true;
10075        }
10076        if (bean.equals(xing.getSignalMast(point))) {
10077            result = true;
10078        }
10079        return result;
10080    }
10081
10082    // initialize logging
10083    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditor.class);
10084}