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        JComponent targetPanel = getTargetPanel();
3076        if (targetPanel != null) {
3077            // repaint only the drawing area, not the whole frame with its
3078            // menu bar, tool bar and scroll bars, none of which this changes
3079            targetPanel.repaint();
3080        } else {
3081            // too early in construction to have a target panel yet
3082            repaint();
3083        }
3084    }
3085
3086    /**
3087     * Allow external set/reset of awaitingIconChange
3088     */
3089    public void setAwaitingIconChange() {
3090        awaitingIconChange = true;
3091    }
3092
3093    public void resetAwaitingIconChange() {
3094        awaitingIconChange = false;
3095    }
3096
3097    /**
3098     * Allow external reset of dirty bit
3099     */
3100    public void resetDirty() {
3101        setDirty(false);
3102        savedEditMode = isEditable();
3103        savedPositionable = allPositionable();
3104        savedControlLayout = allControlling();
3105        savedAnimatingLayout = isAnimating();
3106        savedShowHelpBar = getShowHelpBar();
3107    }
3108
3109    /**
3110     * Allow external set of dirty bit
3111     *
3112     * @param val true/false for panelChanged
3113     */
3114    public void setDirty(boolean val) {
3115        panelChanged = val;
3116    }
3117
3118    @Override
3119    public void setDirty() {
3120        setDirty(true);
3121    }
3122
3123    /**
3124     * Check the dirty state.
3125     *
3126     * @return true if panel has changed
3127     */
3128    @Override
3129    public boolean isDirty() {
3130        return panelChanged;
3131    }
3132
3133    /*
3134    * Get mouse coordinates and adjust for zoom.
3135    * <p>
3136    * Side effects on xLoc, yLoc and dLoc
3137     */
3138    @Nonnull
3139    private Point2D calcLocation(JmriMouseEvent event, int dX, int dY) {
3140        xLoc = (int) ((event.getX() + dX) / getZoom());
3141        yLoc = (int) ((event.getY() + dY) / getZoom());
3142        dLoc = new Point2D.Double(xLoc, yLoc);
3143        return dLoc;
3144    }
3145
3146    private Point2D calcLocation(JmriMouseEvent event) {
3147        return calcLocation(event, 0, 0);
3148    }
3149
3150    /**
3151     * Check for highlighting of cursor position.
3152     *
3153     * If in "highlight" mode, draw a square at the location of the
3154     * event. If there was already a square, just move its location.
3155     * In either case, redraw the panel so the previous square will
3156     * disappear and the new one will appear immediately.
3157     */
3158    private void checkHighlightCursor() {
3159        if (!isEditable() && highlightCursor) {
3160            // rectangle size based on turnout circle size: rectangle should
3161            // be bigger so it can more easily surround turnout on screen
3162            int halfSize = (int)(circleRadius) + 8;
3163            if (_highlightcomponent == null) {
3164                _highlightcomponent = new Rectangle(
3165                        xLoc - halfSize, yLoc - halfSize, halfSize * 2, halfSize * 2);
3166            } else {
3167                _highlightcomponent.setLocation(xLoc - halfSize, yLoc - halfSize);
3168            }
3169            redrawPanel();
3170        }
3171    }
3172
3173    /**
3174     * Check whether an input icon text field is or is becoming active.
3175     * This is based on:
3176     * - The event component is an input icon instance.
3177     * - The mouse event indicates a plain button press.
3178     * @param event The mouse event.
3179     * @return true when active.
3180     */
3181    private boolean isInputTextBox(JmriMouseEvent event) {
3182        if (!(event.getComponent() instanceof PositionableJPanel)) {
3183            return false;
3184        }
3185
3186        if (event.isAltDown() ||
3187                event.isControlDown() ||
3188                event.isMetaDown() ||
3189                event.isPopupTrigger() ||
3190                event.isShiftDown()) {
3191            return false;
3192        }
3193
3194        return true;
3195    }
3196
3197    /**
3198     * Handle a mouse pressed event
3199     * <p>
3200     * Side-effects on _anchorX, _anchorY,_lastX, _lastY, xLoc, yLoc, dLoc,
3201     * selectionActive, xLabel, yLabel
3202     *
3203     * @param event the JmriMouseEvent
3204     */
3205    @Override
3206    public void mousePressed(JmriMouseEvent event) {
3207        if (isInputTextBox(event)) {
3208            return;
3209        }
3210
3211        // initialize cursor position
3212        _anchorX = xLoc;
3213        _anchorY = yLoc;
3214        _lastX = _anchorX;
3215        _lastY = _anchorY;
3216        calcLocation(event);
3217
3218        checkHighlightCursor();
3219
3220        // TODO: Add command-click on nothing to pan view?
3221        if (isEditable()) {
3222            boolean prevSelectionActive = selectionActive;
3223            selectionActive = false;
3224            leToolBarPanel.setLocationText(dLoc);
3225
3226            if (event.isPopupTrigger()) {
3227                if (event.isMetaDown() || event.isAltDown()) {
3228                    // if requesting a popup and it might conflict with moving, delay the request to mouseReleased
3229                    delayedPopupTrigger = true;
3230                } else {
3231                    // no possible conflict with moving, display the popup now
3232                    showEditPopUps(event);
3233                }
3234            }
3235
3236            if (event.isMetaDown() || event.isAltDown()) {
3237                // If dragging an item, identify the item for mouseDragging
3238                selectedObject = null;
3239                selectedHitPointType = HitPointType.NONE;
3240
3241                if (findLayoutTracksHitPoint(dLoc)) {
3242                    selectedObject = foundTrack;
3243                    selectedHitPointType = foundHitPointType;
3244                    startDelta = MathUtil.subtract(foundLocation, dLoc);
3245                    foundTrack = null;
3246                    foundTrackView = null;
3247                } else {
3248                    // Track not hit, find any non LAYOUT_POS_LABEL objects.
3249                    CheckLabel: {
3250                        selectedObject = checkMarkerPopUps(dLoc);
3251                        if (selectedObject != null) {
3252                            selectedHitPointType = HitPointType.MARKER;
3253                            startDelta = MathUtil.subtract(((LocoIcon) selectedObject).getLocation(), dLoc);
3254                            break CheckLabel;
3255                        }
3256
3257                        selectedObject = checkClockPopUps(dLoc);
3258                        if (selectedObject != null) {
3259                            selectedHitPointType = HitPointType.LAYOUT_POS_JCOMP;
3260                            startDelta = MathUtil.subtract(((PositionableJComponent) selectedObject).getLocation(), dLoc);
3261                            break CheckLabel;
3262                        }
3263
3264                        selectedObject = checkMultiSensorPopUps(dLoc);
3265                        if (selectedObject != null) {
3266                            selectedHitPointType = HitPointType.MULTI_SENSOR;
3267                            startDelta = MathUtil.subtract(((MultiSensorIcon) selectedObject).getLocation(), dLoc);
3268                            break CheckLabel;
3269                        }
3270
3271                        selectedObject = checkJPanelPopUps(dLoc);
3272                        if (selectedObject != null) {
3273                            selectedHitPointType = HitPointType.LAYOUT_POS_JPNL;
3274                            startDelta = MathUtil.subtract(((PositionableJPanel) selectedObject).getLocation(), dLoc);
3275                        }
3276                    } // End CheckLabel
3277
3278                    if (selectedObject == null) {
3279                        // Specific objects were not found.
3280                        // The next group are potential LAYOUT_POS_LABEL objects.
3281                        selectedObject = checkSensorIconPopUps(dLoc);
3282                        if (selectedObject == null) {
3283                            selectedObject = checkTurnoutIconPopUps(dLoc);
3284                            if (selectedObject == null) {
3285                                selectedObject = checkSignalHeadIconPopUps(dLoc);
3286                                if (selectedObject == null) {
3287                                    selectedObject = checkLabelImagePopUps(dLoc);
3288                                    if (selectedObject == null) {
3289                                        selectedObject = checkSignalMastIconPopUps(dLoc);
3290                                    }
3291                                }
3292                            }
3293                        }
3294
3295                        // Background objects (level 0 and level 1) are deferred until after the shape objects.
3296                        if (selectedObject != null && !((PositionableLabel) selectedObject).isBackground()) {
3297                            selectedHitPointType = HitPointType.LAYOUT_POS_LABEL;
3298                            startDelta = MathUtil.subtract(((PositionableLabel) selectedObject).getLocation(), dLoc);
3299                            if (selectedObject instanceof MemoryIcon) {
3300                                MemoryIcon pm = (MemoryIcon) selectedObject;
3301
3302                                if (pm.getPopupUtility().getFixedWidth() == 0) {
3303                                    startDelta = new Point2D.Double((pm.getOriginalX() - dLoc.getX()),
3304                                            (pm.getOriginalY() - dLoc.getY()));
3305                                }
3306                            }
3307
3308                            if (selectedObject instanceof GlobalVariableIcon) {
3309                                GlobalVariableIcon pm = (GlobalVariableIcon) selectedObject;
3310
3311                                if (pm.getPopupUtility().getFixedWidth() == 0) {
3312                                    startDelta = new Point2D.Double((pm.getOriginalX() - dLoc.getX()),
3313                                            (pm.getOriginalY() - dLoc.getY()));
3314                                }
3315                            }
3316
3317
3318                        } else {
3319                            // Still nothing found, look for shape objects and then background objects.
3320                            var dragShape = false;
3321
3322                            ListIterator<LayoutShape> listIterator = layoutShapes.listIterator(layoutShapes.size());
3323                            // hit test in front to back order (reverse order of list)
3324                            while (listIterator.hasPrevious()) {
3325                                LayoutShape ls = listIterator.previous();
3326                                selectedHitPointType = ls.findHitPointType(dLoc, true);
3327                                if (LayoutShape.isShapeHitPointType(selectedHitPointType)) {
3328                                    // log.warn("drag selectedObject: ", lt);
3329                                    selectedObject = ls;    // found one!
3330                                    beginLocation = dLoc;
3331                                    currentLocation = beginLocation;
3332                                    startDelta = MathUtil.zeroPoint2D;
3333                                    dragShape = true;
3334                                    break;
3335                                }
3336                            }
3337
3338                            if (!dragShape) {
3339                                // Finally, check for background objects.
3340                                selectedObject = checkBackgroundPopUps(dLoc);
3341
3342                                if (selectedObject != null) {
3343                                    selectedHitPointType = HitPointType.LAYOUT_POS_LABEL;
3344                                    startDelta = MathUtil.subtract(((PositionableLabel) selectedObject).getLocation(), dLoc);
3345                                }
3346                            }
3347                        }
3348                    }
3349                }
3350            } else if (event.isShiftDown() && leToolBarPanel.trackButton.isSelected() && !event.isPopupTrigger()) {
3351                // starting a Track Segment, check for free connection point
3352                selectedObject = null;
3353
3354                if (findLayoutTracksHitPoint(dLoc, true)) {
3355                    // match to a free connection point
3356                    beginTrack = foundTrack;
3357                    beginHitPointType = foundHitPointType;
3358                    beginLocation = foundLocation;
3359                    // BUGFIX: prevents initial drawTrackSegmentInProgress to {0, 0}
3360                    currentLocation = beginLocation;
3361                } else {
3362                    // TODO: auto-add anchor point?
3363                    beginTrack = null;
3364                }
3365            } else if (event.isShiftDown() && leToolBarPanel.shapeButton.isSelected() && !event.isPopupTrigger()) {
3366                // adding or extending a shape
3367                selectedObject = null;  // assume we're adding...
3368                for (LayoutShape ls : layoutShapes) {
3369                    selectedHitPointType = ls.findHitPointType(dLoc, true);
3370                    if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) {
3371                        // log.warn("extend selectedObject: ", lt);
3372                        selectedObject = ls;    // nope, we're extending
3373                        beginLocation = dLoc;
3374                        currentLocation = beginLocation;
3375                        break;
3376                    }
3377                }
3378            } else if (!event.isShiftDown() && !event.isControlDown() && !event.isPopupTrigger()) {
3379                // check if controlling a turnout in edit mode
3380                selectedObject = null;
3381
3382                if (allControlling()) {
3383                    checkControls(false);
3384                }
3385                // initialize starting selection - cancel any previous selection rectangle
3386                selectionActive = true;
3387                selectionX = dLoc.getX();
3388                selectionY = dLoc.getY();
3389                selectionWidth = 0.0;
3390                selectionHeight = 0.0;
3391            }
3392
3393            if (prevSelectionActive) {
3394                redrawPanel();
3395            }
3396
3397        } else if (allControlling()
3398                && !event.isMetaDown() && !event.isPopupTrigger()
3399                && !event.isAltDown() && !event.isShiftDown() && !event.isControlDown()) {
3400            // not in edit mode - check if mouse is on a turnout (using wider search range)
3401            selectedObject = null;
3402            checkControls(true);
3403
3404        } else if ((event.isMetaDown() || event.isAltDown())
3405                && !event.isShiftDown() && !event.isControlDown()) {
3406            // Windows and Linux have meta down on right button press. This prevents isPopTrigger
3407            // reaching the next else-if.
3408
3409            // not in edit mode - check if moving a marker if there are any.  This applies to Windows, Linux and macOS.
3410            selectedObject = checkMarkerPopUps(dLoc);
3411            if (selectedObject != null) {
3412                selectedHitPointType = HitPointType.MARKER;
3413                startDelta = MathUtil.subtract(((LocoIcon) selectedObject).getLocation(), dLoc);
3414                log.debug("mousePressed: ++ MAC/Windows/Linux marker move request");
3415                if (SystemType.isLinux()) {
3416                    // Prepare for a marker popup if the marker move does not occur before mouseReleased.
3417                    // This is only needed for Linux.  Windows handles this in mouseClicked.
3418                    delayedPopupTrigger = true;
3419                    log.debug("mousePressed: ++ Linux marker popup delay");
3420                }
3421            }
3422            if (selectedObject == null) {
3423                selectedObject = checkBlockContentsPopUps(dLoc);
3424                if (selectedObject != null) {
3425                    selectedHitPointType = HitPointType.BLOCKCONTENTSICON;
3426                }
3427            }
3428
3429            // not in edit mode - check if a signal mast popup menu is being requested using Windows or Linux.
3430            var sm = checkSignalMastIconPopUps(dLoc);
3431            if (sm != null) {
3432                delayedPopupTrigger = true;
3433                log.debug("mousePressed: ++ Window/Linux mast popup delay");
3434             }
3435            if (selectedObject == null) {
3436                    selectedObject = checkBlockContentsPopUps(dLoc);
3437                    if (selectedObject != null) {
3438                        selectedHitPointType = HitPointType.BLOCKCONTENTSICON;
3439                    }
3440                }
3441
3442        } else if (event.isPopupTrigger() && !event.isShiftDown()) {
3443
3444            // not in edit mode - check if a marker popup menu is being requested using macOS.
3445            var lo = checkMarkerPopUps(dLoc);
3446            if (lo != null) {
3447                delayedPopupTrigger = true;
3448                log.debug("mousePressed: ++ MAC marker popup delay");
3449            }
3450
3451            // not in edit mode - check if a signal mast popup menu is being requested using macOS.
3452            var sm = checkSignalMastIconPopUps(dLoc);
3453            if (sm != null) {
3454                delayedPopupTrigger = true;
3455                log.debug("mousePressed: ++ MAC mast popup delay");
3456             }
3457
3458        }
3459
3460        if (!event.isPopupTrigger()) {
3461            List<Positionable> selections = getSelectedItems(event);
3462
3463            if (!selections.isEmpty()) {
3464                selections.get(0).doMousePressed(event);
3465            }
3466        }
3467
3468        requestFocusInWindow();
3469
3470    }   // mousePressed
3471
3472// this is a method to iterate over a list of lists of items
3473// calling the predicate tester.test on each one
3474// all matching items are then added to the resulting List
3475// note: currently unused; commented out to avoid findbugs warning
3476// private static List testEachItemInListOfLists(
3477//        @Nonnull List<List> listOfListsOfObjects,
3478//        @Nonnull Predicate<Object> tester) {
3479//    List result = new ArrayList<>();
3480//    for (List<Object> listOfObjects : listOfListsOfObjects) {
3481//        List<Object> l = listOfObjects.stream().filter(o -> tester.test(o)).collect(Collectors.toList());
3482//        result.addAll(l);
3483//    }
3484//    return result;
3485//}
3486// this is a method to iterate over a list of lists of items
3487// calling the predicate tester.test on each one
3488// and return the first one that matches
3489// TODO: make this public? (it is useful! ;-)
3490// note: currently unused; commented out to avoid findbugs warning
3491// private static Object findFirstMatchingItemInListOfLists(
3492//        @Nonnull List<List> listOfListsOfObjects,
3493//        @Nonnull Predicate<Object> tester) {
3494//    Object result = null;
3495//    for (List listOfObjects : listOfListsOfObjects) {
3496//        Optional<Object> opt = listOfObjects.stream().filter(o -> tester.test(o)).findFirst();
3497//        if (opt.isPresent()) {
3498//            result = opt.get();
3499//            break;
3500//        }
3501//    }
3502//    return result;
3503//}
3504    /**
3505     * Called by {@link #mousePressed} to determine if the mouse click was in a
3506     * turnout control location. If so, update selectedHitPointType and
3507     * selectedObject for use by {@link #mouseReleased}.
3508     * <p>
3509     * If there's no match, selectedObject is set to null and
3510     * selectedHitPointType is left referring to the results of the checking the
3511     * last track on the list.
3512     * <p>
3513     * Refers to the current value of {@link #getLayoutTracks()} and
3514     * {@link #dLoc}.
3515     *
3516     * @param useRectangles set true to use rectangle; false for circles.
3517     */
3518    private void checkControls(boolean useRectangles) {
3519        selectedObject = null;  // deliberate side-effect
3520        for (LayoutTrackView theTrackView : getLayoutTrackViews()) {
3521            selectedHitPointType = theTrackView.findHitPointType(dLoc, useRectangles); // deliberate side-effect
3522            if (HitPointType.isControlHitType(selectedHitPointType)) {
3523                selectedObject = theTrackView.getLayoutTrack(); // deliberate side-effect
3524                return;
3525            }
3526        }
3527    }
3528
3529    // This is a geometric search, and should be done with views.
3530    // Hence this form is inevitably temporary.
3531    //
3532    private boolean findLayoutTracksHitPoint(
3533            @Nonnull Point2D loc, boolean requireUnconnected) {
3534        return findLayoutTracksHitPoint(loc, requireUnconnected, null);
3535    }
3536
3537    // This is a geometric search, and should be done with views.
3538    // Hence this form is inevitably temporary.
3539    //
3540    // optional parameter requireUnconnected
3541    private boolean findLayoutTracksHitPoint(@Nonnull Point2D loc) {
3542        return findLayoutTracksHitPoint(loc, false, null);
3543    }
3544
3545    /**
3546     * Internal (private) method to find the track closest to a point, with some
3547     * modifiers to the search. The {@link #foundTrack} and
3548     * {@link #foundHitPointType} members are set from the search.
3549     * <p>
3550     * This is a geometric search, and should be done with views. Hence this
3551     * form is inevitably temporary.
3552     *
3553     * @param loc                Point to search from
3554     * @param requireUnconnected forwarded to {@link #getLayoutTrackView}; if
3555     *                           true, return only free connections
3556     * @param avoid              Don't return this track, keep searching. Note
3557     *                           that {@Link #selectedObject} is also always
3558     *                           avoided automatically
3559     * @returns true if values of {@link #foundTrack} and
3560     * {@link #foundHitPointType} correct; note they may have changed even if
3561     * false is returned.
3562     */
3563    private boolean findLayoutTracksHitPoint(@Nonnull Point2D loc,
3564            boolean requireUnconnected, @CheckForNull LayoutTrack avoid) {
3565        boolean result = false; // assume failure (pessimist!)
3566
3567        foundTrack = null;
3568        foundTrackView = null;
3569        foundHitPointType = HitPointType.NONE;
3570
3571        Optional<LayoutTrack> opt = getLayoutTracks().stream().filter(layoutTrack -> {  // != means can't (yet) loop over Views
3572            if ((layoutTrack != avoid) && (layoutTrack != selectedObject)) {
3573                foundHitPointType = getLayoutTrackView(layoutTrack).findHitPointType(loc, false, requireUnconnected);
3574            }
3575            return (HitPointType.NONE != foundHitPointType);
3576        }).findFirst();
3577
3578        LayoutTrack layoutTrack = null;
3579        if (opt.isPresent()) {
3580            layoutTrack = opt.get();
3581        }
3582
3583        if (layoutTrack != null) {
3584            foundTrack = layoutTrack;
3585            foundTrackView = this.getLayoutTrackView(layoutTrack);
3586
3587            // get screen coordinates
3588            foundLocation = foundTrackView.getCoordsForConnectionType(foundHitPointType);
3589            /// foundNeedsConnect = isDisconnected(foundHitPointType);
3590            result = true;
3591        }
3592        return result;
3593    }
3594
3595    private TrackSegment checkTrackSegmentPopUps(@Nonnull Point2D loc) {
3596        assert loc != null;
3597
3598        TrackSegment result = null;
3599
3600        // NOTE: Rather than calculate all the hit rectangles for all
3601        // the points below and test if this location is in any of those
3602        // rectangles just create a hit rectangle for the location and
3603        // see if any of the points below are in it instead...
3604        Rectangle2D r = layoutEditorControlCircleRectAt(loc);
3605
3606        // check Track Segments, if any
3607        for (TrackSegmentView tsv : getTrackSegmentViews()) {
3608            if (r.contains(tsv.getCentreSeg())) {
3609                result = tsv.getTrackSegment();
3610                break;
3611            }
3612        }
3613        return result;
3614    }
3615
3616    private PositionableLabel checkBackgroundPopUps(@Nonnull Point2D loc) {
3617        assert loc != null;
3618
3619        PositionableLabel result = null;
3620        // check background images, if any
3621        for (int i = backgroundImage.size() - 1; i >= 0; i--) {
3622            PositionableLabel b = backgroundImage.get(i);
3623            Rectangle2D r = b.getBounds();
3624            if (r.contains(loc)) {
3625                result = b;
3626                break;
3627            }
3628        }
3629        return result;
3630    }
3631
3632    private SensorIcon checkSensorIconPopUps(@Nonnull Point2D loc) {
3633        assert loc != null;
3634
3635        SensorIcon result = null;
3636        // check sensor images, if any
3637        for (int i = sensorImage.size() - 1; i >= 0; i--) {
3638            SensorIcon s = sensorImage.get(i);
3639            Rectangle2D r = s.getBounds();
3640            if (r.contains(loc)) {
3641                result = s;
3642            }
3643        }
3644        return result;
3645    }
3646
3647    private TurnoutIcon checkTurnoutIconPopUps(@Nonnull Point2D loc) {
3648        assert loc != null;
3649
3650        TurnoutIcon result = null;
3651        // check turnout images, if any
3652        for (int i = turnoutImage.size() - 1; i >= 0; i--) {
3653            TurnoutIcon s = turnoutImage.get(i);
3654            Rectangle2D r = s.getBounds();
3655            if (r.contains(loc)) {
3656                result = s;
3657            }
3658        }
3659        return result;
3660    }
3661
3662    private SignalHeadIcon checkSignalHeadIconPopUps(@Nonnull Point2D loc) {
3663        assert loc != null;
3664
3665        SignalHeadIcon result = null;
3666        // check signal head images, if any
3667        for (int i = signalHeadImage.size() - 1; i >= 0; i--) {
3668            SignalHeadIcon s = signalHeadImage.get(i);
3669            Rectangle2D r = s.getBounds();
3670            if (r.contains(loc)) {
3671                result = s;
3672                break;
3673            }
3674        }
3675        return result;
3676    }
3677
3678    private SignalMastIcon checkSignalMastIconPopUps(@Nonnull Point2D loc) {
3679        assert loc != null;
3680
3681        SignalMastIcon result = null;
3682        // check signal head images, if any
3683        for (int i = signalMastList.size() - 1; i >= 0; i--) {
3684            SignalMastIcon s = signalMastList.get(i);
3685            Rectangle2D r = s.getBounds();
3686            if (r.contains(loc)) {
3687                result = s;
3688                break;
3689            }
3690        }
3691        return result;
3692    }
3693
3694    private PositionableLabel checkLabelImagePopUps(@Nonnull Point2D loc) {
3695        assert loc != null;
3696
3697        PositionableLabel result = null;
3698        int level = 0;
3699
3700        for (int i = labelImage.size() - 1; i >= 0; i--) {
3701            PositionableLabel s = labelImage.get(i);
3702            double x = s.getX();
3703            double y = s.getY();
3704            double w = 10.0;
3705            double h = 5.0;
3706
3707            if (s.isIcon() || s.isRotated() || s.getPopupUtility().getOrientation() != PositionablePopupUtil.HORIZONTAL) {
3708                w = s.maxWidth();
3709                h = s.maxHeight();
3710            } else if (s.isText()) {
3711                h = s.getFont().getSize();
3712                w = (h * 2 * (s.getText().length())) / 3;
3713            }
3714
3715            Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
3716            if (r.contains(loc)) {
3717                if (s.getDisplayLevel() >= level) {
3718                    // Check to make sure that we are returning the highest level label.
3719                    result = s;
3720                    level = s.getDisplayLevel();
3721                }
3722            }
3723        }
3724        return result;
3725    }
3726
3727    private PositionableJPanel checkJPanelPopUps(@Nonnull Point2D loc) {
3728        assert loc != null;
3729
3730        PositionableJPanel result = null;
3731        int level = 0;
3732
3733        for (int i = memoryInputList.size() - 1; i >= 0; i--) {
3734            PositionableJPanel s = memoryInputList.get(i);
3735            double x = s.getX();
3736            double y = s.getY();
3737            double w = s.getWidth();
3738            double h = s.getHeight();
3739
3740            Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
3741
3742            if (r.contains(loc)) {
3743                if (s.getDisplayLevel() >= level) {
3744                    // Check to make sure that we are returning the highest level label.
3745                    result = s;
3746                    level = s.getDisplayLevel();
3747                }
3748            }
3749        }
3750
3751        if (result == null) {
3752            for (int i = blockContentsInputList.size() - 1; i >= 0; i--) {
3753                PositionableJPanel s = blockContentsInputList.get(i);
3754                double x = s.getX();
3755                double y = s.getY();
3756                double w = s.getWidth();
3757                double h = s.getHeight();
3758
3759                Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
3760
3761                if (r.contains(loc)) {
3762                    if (s.getDisplayLevel() >= level) {
3763                        // Check to make sure that we are returning the highest level label.
3764                        result = s;
3765                        level = s.getDisplayLevel();
3766                    }
3767                }
3768            }
3769        }
3770
3771        return result;
3772    }
3773
3774    private AnalogClock2Display checkClockPopUps(@Nonnull Point2D loc) {
3775        assert loc != null;
3776
3777        AnalogClock2Display result = null;
3778        // check clocks, if any
3779        for (int i = clocks.size() - 1; i >= 0; i--) {
3780            AnalogClock2Display s = clocks.get(i);
3781            Rectangle2D r = s.getBounds();
3782            if (r.contains(loc)) {
3783                result = s;
3784                break;
3785            }
3786        }
3787        return result;
3788    }
3789
3790    private MultiSensorIcon checkMultiSensorPopUps(@Nonnull Point2D loc) {
3791        assert loc != null;
3792
3793        MultiSensorIcon result = null;
3794        // check multi sensor icons, if any
3795        for (int i = multiSensors.size() - 1; i >= 0; i--) {
3796            MultiSensorIcon s = multiSensors.get(i);
3797            Rectangle2D r = s.getBounds();
3798            if (r.contains(loc)) {
3799                result = s;
3800                break;
3801            }
3802        }
3803        return result;
3804    }
3805
3806    private LocoIcon checkMarkerPopUps(@Nonnull Point2D loc) {
3807        assert loc != null;
3808
3809        LocoIcon result = null;
3810        // check marker icons, if any
3811        for (int i = markerImage.size() - 1; i >= 0; i--) {
3812            LocoIcon l = markerImage.get(i);
3813            Rectangle2D r = l.getBounds();
3814            if (r.contains(loc)) {
3815                // mouse was pressed in marker icon
3816                result = l;
3817                break;
3818            }
3819        }
3820        return result;
3821    }
3822
3823    private BlockContentsIcon checkBlockContentsPopUps(@Nonnull Point2D loc) {
3824        assert loc != null;
3825
3826        BlockContentsIcon result = null;
3827        // check marker icons, if any
3828        for (int i = blockContentsLabelList.size() - 1; i >= 0; i--) {
3829            BlockContentsIcon l = blockContentsLabelList.get(i);
3830            Rectangle2D r = l.getBounds();
3831            if (r.contains(loc)) {
3832                // mouse was pressed in marker icon
3833                result = l;
3834                break;
3835            }
3836        }
3837        return result;
3838    }
3839
3840    private LayoutShape checkLayoutShapePopUps(@Nonnull Point2D loc) {
3841        assert loc != null;
3842
3843        LayoutShape result = null;
3844        for (LayoutShape ls : layoutShapes) {
3845            selectedHitPointType = ls.findHitPointType(loc, true);
3846            if (LayoutShape.isShapeHitPointType(selectedHitPointType)) {
3847                result = ls;
3848                break;
3849            }
3850        }
3851        return result;
3852    }
3853
3854    private Positionable checkPositionablePopUps(@Nonnull Point2D loc) {
3855        assert loc != null;
3856
3857        Positionable result = null;
3858        // check factory generated positionables, if any
3859        for (int i = factoryPositionables.size() - 1; i >= 0; i--) {
3860            Positionable s = factoryPositionables.get(i);
3861            Rectangle2D r = s.getBounds();
3862            if (r.contains(loc)) {
3863                result = s;
3864                break;
3865            }
3866        }
3867        return result;
3868    }
3869
3870    /**
3871     * Get the coordinates for the connection type of the specified LayoutTrack
3872     * or subtype.
3873     * <p>
3874     * This uses the current LayoutEditor object to map a LayoutTrack (no
3875     * coordinates) object to _a_ specific LayoutTrackView object in the current
3876     * LayoutEditor i.e. window. This allows the same model object in two
3877     * windows, but not twice in a single window.
3878     * <p>
3879     * This is temporary, and needs to go away as the LayoutTrack doesn't
3880     * logically have position; just the LayoutTrackView does, and multiple
3881     * LayoutTrackViews can refer to one specific LayoutTrack.
3882     *
3883     * @param track          the object (LayoutTrack subclass)
3884     * @param connectionType the type of connection
3885     * @return the coordinates for the connection type of the specified object
3886     */
3887    @Nonnull
3888    public Point2D getCoords(LayoutTrack track, HitPointType connectionType) {
3889        if (track == null) {
3890            log.warn("track is null, HitPointType={}", connectionType);
3891        }
3892        LayoutTrack trk = Objects.requireNonNull(track);
3893        return getCoords(getLayoutTrackView(trk), connectionType);
3894    }
3895
3896    /**
3897     * Get the coordinates for the connection type of the specified
3898     * LayoutTrackView or subtype.
3899     *
3900     * @param trkView        the object (LayoutTrackView subclass)
3901     * @param connectionType the type of connection
3902     * @return the coordinates for the connection type of the specified object
3903     */
3904    @Nonnull
3905    public Point2D getCoords(@Nonnull LayoutTrackView trkView, HitPointType connectionType) {
3906        LayoutTrackView trkv = Objects.requireNonNull(trkView);
3907
3908        return trkv.getCoordsForConnectionType(connectionType);
3909    }
3910
3911    @Override
3912    public void mouseReleased(JmriMouseEvent event) {
3913        super.setToolTip(null);
3914
3915        if (isInputTextBox(event)) {
3916            return;
3917        }
3918
3919        // initialize mouse position
3920        calcLocation(event);
3921
3922        if (!isEditable() && _highlightcomponent != null && highlightCursor) {
3923            _highlightcomponent = null;
3924            // see if we moused up on an object
3925            checkControls(true);
3926            redrawPanel();
3927        }
3928
3929        // if alt modifier is down invert the snap to grid behaviour
3930        snapToGridInvert = event.isAltDown();
3931
3932        if (isEditable()) {
3933            leToolBarPanel.setLocationText(dLoc);
3934
3935            // released the mouse with shift down... see what we're adding
3936            if (!event.isPopupTrigger() && !event.isMetaDown() && event.isShiftDown()) {
3937
3938                currentPoint = new Point2D.Double(xLoc, yLoc);
3939
3940                if (snapToGridOnAdd != snapToGridInvert) {
3941                    // this snaps the current point to the grid
3942                    currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize());
3943                    xLoc = (int) currentPoint.getX();
3944                    yLoc = (int) currentPoint.getY();
3945                    leToolBarPanel.setLocationText(currentPoint);
3946                }
3947
3948                if (leToolBarPanel.turnoutRHButton.isSelected()) {
3949                    addLayoutTurnout(LayoutTurnout.TurnoutType.RH_TURNOUT);
3950                } else if (leToolBarPanel.turnoutLHButton.isSelected()) {
3951                    addLayoutTurnout(LayoutTurnout.TurnoutType.LH_TURNOUT);
3952                } else if (leToolBarPanel.turnoutWYEButton.isSelected()) {
3953                    addLayoutTurnout(LayoutTurnout.TurnoutType.WYE_TURNOUT);
3954                } else if (leToolBarPanel.doubleXoverButton.isSelected()) {
3955                    addLayoutTurnout(LayoutTurnout.TurnoutType.DOUBLE_XOVER);
3956                } else if (leToolBarPanel.rhXoverButton.isSelected()) {
3957                    addLayoutTurnout(LayoutTurnout.TurnoutType.RH_XOVER);
3958                } else if (leToolBarPanel.lhXoverButton.isSelected()) {
3959                    addLayoutTurnout(LayoutTurnout.TurnoutType.LH_XOVER);
3960                } else if (leToolBarPanel.levelXingButton.isSelected()) {
3961                    addLevelXing();
3962                } else if (leToolBarPanel.layoutSingleSlipButton.isSelected()) {
3963                    addLayoutSlip(LayoutSlip.TurnoutType.SINGLE_SLIP);
3964                } else if (leToolBarPanel.layoutDoubleSlipButton.isSelected()) {
3965                    addLayoutSlip(LayoutSlip.TurnoutType.DOUBLE_SLIP);
3966                } else if (leToolBarPanel.endBumperButton.isSelected()) {
3967                    addEndBumper();
3968                } else if (leToolBarPanel.anchorButton.isSelected()) {
3969                    addAnchor();
3970                } else if (leToolBarPanel.edgeButton.isSelected()) {
3971                    addEdgeConnector();
3972                } else if (leToolBarPanel.trackButton.isSelected()) {
3973                    if ((beginTrack != null) && (foundTrack != null)
3974                            && (beginTrack != foundTrack)) {
3975                        addTrackSegment();
3976                        _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
3977                    }
3978                    beginTrack = null;
3979                    foundTrack = null;
3980                    foundTrackView = null;
3981                } else if (leToolBarPanel.multiSensorButton.isSelected()) {
3982                    startMultiSensor();
3983                } else if (leToolBarPanel.sensorButton.isSelected()) {
3984                    addSensor();
3985                } else if (leToolBarPanel.turnoutButton.isSelected()) {
3986                    addTurnout();
3987                } else if (leToolBarPanel.signalButton.isSelected()) {
3988                    addSignalHead();
3989                } else if (leToolBarPanel.textLabelButton.isSelected()) {
3990                    addLabel();
3991                } else if (leToolBarPanel.memoryButton.isSelected()) {
3992                    selectMemoryType();
3993                } else if (leToolBarPanel.globalVariableButton.isSelected()) {
3994                    addGlobalVariable();
3995                } else if (leToolBarPanel.blockContentsButton.isSelected()) {
3996                    selectBlockContentsType();
3997                } else if (leToolBarPanel.iconLabelButton.isSelected()) {
3998                    addIcon();
3999                } else if (leToolBarPanel.logixngButton.isSelected()) {
4000                    addLogixNGIcon();
4001                } else if (leToolBarPanel.audioButton.isSelected()) {
4002                    addAudioIcon();
4003                } else if (leToolBarPanel.shapeButton.isSelected()) {
4004                    LayoutShape ls = (LayoutShape) selectedObject;
4005                    if (ls == null) {
4006                        ls = addLayoutShape(currentPoint);
4007                    } else {
4008                        ls.addPoint(currentPoint, selectedHitPointType.shapePointIndex());
4009                    }
4010                    unionToPanelBounds(ls.getBounds());
4011                    _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
4012                } else if (leToolBarPanel.signalMastButton.isSelected()) {
4013                    addSignalMast();
4014                } else if (leToolBarPanel.turntableButton.isSelected()) {
4015                    addTurntable(currentPoint);
4016                } else if (leToolBarPanel.traverserButton.isSelected()) {
4017                    addTraverser(currentPoint);
4018                } else {
4019                    log.warn("No item selected in panel edit mode");
4020                }
4021                // resizePanelBounds(false);
4022                selectedObject = null;
4023                redrawPanel();
4024            } else if ((event.isPopupTrigger() || delayedPopupTrigger) && !isDragging) {
4025                selectedObject = null;
4026                selectedHitPointType = HitPointType.NONE;
4027                whenReleased = event.getWhen();
4028                showEditPopUps(event);
4029            } else if ((selectedObject != null) && (selectedHitPointType == HitPointType.TURNOUT_CENTER)
4030                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4031                    && !event.isShiftDown() && !event.isControlDown()) {
4032                // controlling turnouts, in edit mode
4033                LayoutTurnout t = (LayoutTurnout) selectedObject;
4034                t.toggleTurnout();
4035            } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.SLIP_LEFT)
4036                    || (selectedHitPointType == HitPointType.SLIP_RIGHT))
4037                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4038                    && !event.isShiftDown() && !event.isControlDown()) {
4039                // controlling slips, in edit mode
4040                LayoutSlip sl = (LayoutSlip) selectedObject;
4041                sl.toggleState(selectedHitPointType);
4042            } else if ((selectedObject != null) && (HitPointType.isTurntableRayHitType(selectedHitPointType))
4043                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4044                    && !event.isShiftDown() && !event.isControlDown()) {
4045                // controlling turntable, in edit mode
4046                LayoutTurntable t = (LayoutTurntable) selectedObject;
4047                t.setPosition(selectedHitPointType.turntableTrackIndex());
4048            } else if ((selectedObject != null) && (HitPointType.isTraverserSlotHitType(selectedHitPointType))
4049                    && allControlling() && (!event.isMetaDown() && !event.isAltDown()) && !event.isPopupTrigger()
4050                    && !event.isShiftDown() && !event.isControlDown()) {
4051                // controlling Traverser, in edit mode
4052                LayoutTraverser t = (LayoutTraverser) selectedObject;
4053                t.setPosition(selectedHitPointType.traverserTrackIndex());
4054            } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.TURNOUT_CENTER)
4055                    || (selectedHitPointType == HitPointType.SLIP_CENTER)
4056                    || (selectedHitPointType == HitPointType.SLIP_LEFT)
4057                    || (selectedHitPointType == HitPointType.SLIP_RIGHT))
4058                    && allControlling() && (event.isMetaDown() && !event.isAltDown())
4059                    && !event.isShiftDown() && !event.isControlDown() && isDragging) {
4060                // We just dropped a turnout (or slip)... see if it will connect to anything
4061                hitPointCheckLayoutTurnouts((LayoutTurnout) selectedObject);
4062            } else if ((selectedObject != null) && (selectedHitPointType == HitPointType.POS_POINT)
4063                    && allControlling() && (event.isMetaDown())
4064                    && !event.isShiftDown() && !event.isControlDown() && isDragging) {
4065                // We just dropped a PositionablePoint... see if it will connect to anything
4066                PositionablePoint p = (PositionablePoint) selectedObject;
4067                if ((p.getConnect1() == null) || (p.getConnect2() == null)) {
4068                    checkPointOfPositionable(p);
4069                }
4070            }
4071
4072            if ((leToolBarPanel.trackButton.isSelected()) && (beginTrack != null) && (foundTrack != null)) {
4073                // user let up shift key before releasing the mouse when creating a track segment
4074                _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
4075                beginTrack = null;
4076                foundTrack = null;
4077                foundTrackView = null;
4078                redrawPanel();
4079            }
4080            createSelectionGroups();
4081        } else if ((selectedObject != null) && (selectedHitPointType == HitPointType.TURNOUT_CENTER)
4082                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4083                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4084            // controlling turnout out of edit mode
4085            LayoutTurnout t = (LayoutTurnout) selectedObject;
4086            if (useDirectTurnoutControl) {
4087                t.setState(Turnout.CLOSED);
4088            } else {
4089                t.toggleTurnout();
4090                if (highlightCursor && !t.isDisabled()) {
4091                    // flash the turnout circle a few times so the user knows it's being toggled
4092                    javax.swing.Timer timer = new javax.swing.Timer(150, null);
4093                    timer.addActionListener(new ActionListener(){
4094                        int count = 1;
4095                        @Override
4096                        public void actionPerformed(ActionEvent ae){
4097                          if(count % 2 != 0) t.setDisabled(true);
4098                          else t.setDisabled(false);
4099                          if(++count > 8) timer.stop();
4100                        }
4101                    });
4102                    timer.start();
4103                }
4104            }
4105        } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.SLIP_LEFT)
4106                || (selectedHitPointType == HitPointType.SLIP_RIGHT))
4107                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4108                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4109            // controlling slip out of edit mode
4110            LayoutSlip sl = (LayoutSlip) selectedObject;
4111            sl.toggleState(selectedHitPointType);
4112        } else if ((selectedObject != null) && (HitPointType.isTurntableRayHitType(selectedHitPointType))
4113                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4114                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4115            // controlling turntable out of edit mode
4116            LayoutTurntable t = (LayoutTurntable) selectedObject;
4117            t.setPosition(selectedHitPointType.turntableTrackIndex());
4118        } else if ((selectedObject != null) && (HitPointType.isTraverserSlotHitType(selectedHitPointType))
4119                && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger()
4120                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4121            // controlling traverser out of edit mode
4122            LayoutTraverser t = (LayoutTraverser) selectedObject;
4123            t.setPosition(selectedHitPointType.traverserTrackIndex());
4124        } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.BLOCKCONTENTSICON))
4125                && allControlling()  && !event.isAltDown() && !event.isPopupTrigger()
4126                && !event.isShiftDown() && (!delayedPopupTrigger)) {
4127            BlockContentsIcon t = (BlockContentsIcon) selectedObject;
4128            if (t != null) {
4129                showPopUp(t, event);
4130            }
4131        } else if ((event.isPopupTrigger() || delayedPopupTrigger) && (!isDragging)) {
4132            // requesting marker popup out of edit mode
4133            LocoIcon lo = checkMarkerPopUps(dLoc);
4134            if (lo != null) {
4135                showPopUp(lo, event);
4136            } else {
4137                if (findLayoutTracksHitPoint(dLoc)) {
4138                    // show popup menu
4139                    switch (foundHitPointType) {
4140                        case TURNOUT_CENTER: {
4141                            if (useDirectTurnoutControl) {
4142                                LayoutTurnout t = (LayoutTurnout) foundTrack;
4143                                t.setState(Turnout.THROWN);
4144                            } else {
4145                                foundTrackView.showPopup(event);
4146                            }
4147                            break;
4148                        }
4149
4150                        case LEVEL_XING_CENTER:
4151                        case SLIP_RIGHT:
4152                        case SLIP_LEFT: {
4153                            foundTrackView.showPopup(event);
4154                            break;
4155                        }
4156
4157                        default: {
4158                            break;
4159                        }
4160                    }
4161                }
4162                AnalogClock2Display c = checkClockPopUps(dLoc);
4163                if (c != null) {
4164                    showPopUp(c, event);
4165                } else {
4166                    SignalMastIcon sm = checkSignalMastIconPopUps(dLoc);
4167                    if (sm != null) {
4168                        showPopUp(sm, event);
4169                    } else {
4170                        PositionableLabel im = checkLabelImagePopUps(dLoc);
4171                        if (im != null) {
4172                            showPopUp(im, event);
4173                        }
4174                    }
4175                }
4176            }
4177        }
4178
4179        if (!event.isPopupTrigger() && !isDragging) {
4180            List<Positionable> selections = getSelectedItems(event);
4181            if (!selections.isEmpty()) {
4182                selections.get(0).doMouseReleased(event);
4183                whenReleased = event.getWhen();
4184            }
4185        }
4186
4187        // train icon needs to know when moved
4188        if (event.isPopupTrigger() && isDragging) {
4189            List<Positionable> selections = getSelectedItems(event);
4190            if (!selections.isEmpty()) {
4191                selections.get(0).doMouseDragged(event);
4192            }
4193        }
4194
4195        if (selectedObject != null) {
4196            // An object was selected, deselect it
4197            prevSelectedObject = selectedObject;
4198            selectedObject = null;
4199        }
4200
4201        // clear these
4202        beginTrack = null;
4203        foundTrack = null;
4204        foundTrackView = null;
4205
4206        delayedPopupTrigger = false;
4207
4208        if (isDragging) {
4209            resizePanelBounds(true);
4210            isDragging = false;
4211        }
4212
4213        requestFocusInWindow();
4214    }   // mouseReleased
4215
4216    public void addPopupItems(@Nonnull JPopupMenu popup, @Nonnull JmriMouseEvent event) {
4217
4218        List<LayoutTrack> tracks = getLayoutTracks().stream().filter(layoutTrack -> {  // != means can't (yet) loop over Views
4219            HitPointType hitPointType = getLayoutTrackView(layoutTrack).findHitPointType(dLoc, false, false);
4220            return (HitPointType.NONE != hitPointType);
4221        }).collect(Collectors.toList());
4222
4223        List<Positionable> selections = getSelectedItems(event);
4224
4225        if ((tracks.size() > 1) || (selections.size() > 1)) {
4226            JMenu iconsBelowMenu = new JMenu(Bundle.getMessage("MenuItemIconsBelow"));
4227
4228            JMenuItem mi = new JMenuItem(Bundle.getMessage("MenuItemIconsBelow_InfoNotInOrder"));
4229            mi.setEnabled(false);
4230            iconsBelowMenu.add(mi);
4231
4232            if (tracks.size() > 1) {
4233                for (int i=0; i < tracks.size(); i++) {
4234                    LayoutTrack t = tracks.get(i);
4235                    iconsBelowMenu.add(new AbstractAction(Bundle.getMessage(
4236                            "LayoutTrackTypeAndName", t.getTypeName(), t.getName())) {
4237                        @Override
4238                        public void actionPerformed(ActionEvent e) {
4239                            LayoutTrackView ltv = getLayoutTrackView(t);
4240                            ltv.showPopup(event);
4241                        }
4242                    });
4243                }
4244            }
4245            if (selections.size() > 1) {
4246                for (int i=0; i < selections.size(); i++) {
4247                    Positionable pos = selections.get(i);
4248                    iconsBelowMenu.add(new AbstractAction(Bundle.getMessage(
4249                            "PositionableTypeAndName", pos.getTypeString(), pos.getNameString())) {
4250                        @Override
4251                        public void actionPerformed(ActionEvent e) {
4252                            showPopUp(pos, event, new ArrayList<>());
4253                        }
4254                    });
4255                }
4256            }
4257            popup.addSeparator();
4258            popup.add(iconsBelowMenu);
4259        }
4260    }
4261
4262    private void showEditPopUps(@Nonnull JmriMouseEvent event) {
4263        if (findLayoutTracksHitPoint(dLoc)) {
4264            if (HitPointType.isBezierHitType(foundHitPointType)) {
4265                getTrackSegmentView((TrackSegment) foundTrack).showBezierPopUp(event, foundHitPointType);
4266            } else if (HitPointType.isTurntableRayHitType(foundHitPointType)) {
4267                LayoutTurntable t = (LayoutTurntable) foundTrack;
4268                if (t.isTurnoutControlled()) {
4269                    LayoutTurntableView ltview = getLayoutTurntableView((LayoutTurntable) foundTrack);
4270                    ltview.showRayPopUp(event, foundHitPointType.turntableTrackIndex());
4271                }
4272            }else if (HitPointType.isTraverserSlotHitType(foundHitPointType)) {
4273                LayoutTraverser t = (LayoutTraverser) foundTrack;
4274                if (t.isTurnoutControlled()) {
4275                    LayoutTraverserView ltview = getLayoutTraverserView((LayoutTraverser) foundTrack);
4276                    ltview.showSlotPopUp(event, foundHitPointType.traverserTrackIndex());
4277                }
4278            } else if (HitPointType.isPopupHitType(foundHitPointType)) {
4279                foundTrackView.showPopup(event);
4280            } else if (HitPointType.isTurnoutHitType(foundHitPointType)) {
4281                // don't curently have edit popup for these
4282            } else {
4283                log.warn("Unknown foundPointType:{}", foundHitPointType);
4284            }
4285        } else {
4286            do {
4287                TrackSegment ts = checkTrackSegmentPopUps(dLoc);
4288                if (ts != null) {
4289                    TrackSegmentView tsv = getTrackSegmentView(ts);
4290                    tsv.showPopup(event);
4291                    break;
4292                }
4293
4294                SensorIcon s = checkSensorIconPopUps(dLoc);
4295                if (s != null) {
4296                    showPopUp(s, event);
4297                    break;
4298                }
4299
4300                TurnoutIcon t = checkTurnoutIconPopUps(dLoc);
4301                if (t != null) {
4302                    showPopUp(t, event);
4303                    break;
4304                }
4305
4306                LocoIcon lo = checkMarkerPopUps(dLoc);
4307                if (lo != null) {
4308                    showPopUp(lo, event);
4309                    break;
4310                }
4311
4312                SignalHeadIcon sh = checkSignalHeadIconPopUps(dLoc);
4313                if (sh != null) {
4314                    showPopUp(sh, event);
4315                    break;
4316                }
4317
4318                AnalogClock2Display c = checkClockPopUps(dLoc);
4319                if (c != null) {
4320                    showPopUp(c, event);
4321                    break;
4322                }
4323
4324                MultiSensorIcon ms = checkMultiSensorPopUps(dLoc);
4325                if (ms != null) {
4326                    showPopUp(ms, event);
4327                    break;
4328                }
4329
4330                LayoutShape ls = checkLayoutShapePopUps(dLoc);
4331                if (ls != null) {
4332                    ls.showShapePopUp(event, selectedHitPointType);
4333                    break;
4334                }
4335
4336                PositionableLabel lb = checkLabelImagePopUps(dLoc);
4337                if (lb != null) {
4338                    showPopUp(lb, event);
4339                    break;
4340                }
4341
4342                PositionableLabel b = checkBackgroundPopUps(dLoc);
4343                if (b != null) {
4344                    showPopUp(b, event);
4345                    break;
4346                }
4347
4348                PositionableJPanel jp = checkJPanelPopUps(dLoc);
4349                if (jp != null) {
4350                    showPopUp(jp, event);
4351                    break;
4352                }
4353
4354                SignalMastIcon sm = checkSignalMastIconPopUps(dLoc);
4355                if (sm != null) {
4356                    showPopUp(sm, event);
4357                    break;
4358                }
4359
4360                Positionable factPos = checkPositionablePopUps(dLoc);
4361                if (factPos != null) {
4362                    showPopUp(factPos, event);
4363                    break;
4364                }
4365
4366            } while (false);
4367        }
4368    }
4369
4370    /**
4371     * Select the menu items to display for the Positionable's popup.
4372     * @param pos   the item containing or requiring the context menu
4373     * @param event the event triggering the menu
4374     */
4375    public void showPopUp(@Nonnull Positionable pos, @Nonnull JmriMouseEvent event) {
4376        Positionable p = Objects.requireNonNull(pos);
4377
4378        if (!((Component) p).isVisible()) {
4379            return; // component must be showing on the screen to determine its location
4380        }
4381        JPopupMenu popup = new JPopupMenu();
4382
4383        if (p.isEditable()) {
4384            JMenuItem jmi;
4385
4386            if (showAlignPopup()) {
4387                setShowAlignmentMenu(popup);
4388                popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) {
4389                    @Override
4390                    public void actionPerformed(ActionEvent event) {
4391                        deleteSelectedItems();
4392                    }
4393                });
4394            } else {
4395                if (p.doViemMenu()) {
4396                    String objectType = p.getClass().getName();
4397                    objectType = objectType.substring(objectType.lastIndexOf('.') + 1);
4398                    jmi = popup.add(objectType);
4399                    jmi.setEnabled(false);
4400
4401                    jmi = popup.add(p.getNameString());
4402                    jmi.setEnabled(false);
4403
4404                    if (p.isPositionable()) {
4405                        setShowCoordinatesMenu(p, popup);
4406                    }
4407                    setDisplayLevelMenu(p, popup);
4408                    setPositionableMenu(p, popup);
4409                }
4410
4411                boolean popupSet = false;
4412                popupSet |= p.setRotateOrthogonalMenu(popup);
4413                popupSet |= p.setRotateMenu(popup);
4414                popupSet |= p.setScaleMenu(popup);
4415                if (popupSet) {
4416                    popup.addSeparator();
4417                    popupSet = false;
4418                }
4419                // Don't show the icon menu item for the MemoryInputIcon
4420                if (!(p instanceof PositionableJPanel)) {
4421                    popupSet |= p.setEditIconMenu(popup);
4422                }
4423                popupSet |= p.setTextEditMenu(popup);
4424
4425                PositionablePopupUtil util = p.getPopupUtility();
4426
4427                if (util != null) {
4428                    util.setFixedTextMenu(popup);
4429                    util.setTextMarginMenu(popup);
4430                    util.setTextBorderMenu(popup);
4431                    util.setTextFontMenu(popup);
4432                    util.setBackgroundMenu(popup);
4433                    util.setTextJustificationMenu(popup);
4434                    util.setTextOrientationMenu(popup);
4435                    popup.addSeparator();
4436                    util.propertyUtil(popup);
4437                    util.setAdditionalEditPopUpMenu(popup);
4438                    popupSet = true;
4439                }
4440
4441                if (popupSet) {
4442                    popup.addSeparator();
4443                    // popupSet = false;
4444                }
4445                p.setDisableControlMenu(popup);
4446                setShowAlignmentMenu(popup);
4447
4448                // for Positionables with unique settings
4449                p.showPopUp(popup);
4450                setShowToolTipMenu(p, popup);
4451
4452                setRemoveMenu(p, popup);
4453
4454                if (p.doViemMenu()) {
4455                    setHiddenMenu(p, popup);
4456                    setEmptyHiddenMenu(p, popup);
4457                    setValueEditDisabledMenu(p, popup);
4458                    setEditIdMenu(p, popup);
4459                    setEditClassesMenu(p, popup);
4460                    popup.addSeparator();
4461                    setLogixNGPositionableMenu(p, popup);
4462                }
4463            }
4464        } else {
4465            p.showPopUp(popup);
4466            PositionablePopupUtil util = p.getPopupUtility();
4467
4468            if (util != null) {
4469                util.setAdditionalViewPopUpMenu(popup);
4470            }
4471        }
4472
4473        addPopupItems(popup, event);
4474
4475        popup.show((Component) p, p.getWidth() / 2 + (int) ((getZoom() - 1.0) * p.getX()),
4476                p.getHeight() / 2 + (int) ((getZoom() - 1.0) * p.getY()));
4477
4478        /*popup.show((Component)pt, event.getX(), event.getY());*/
4479    }
4480
4481    private long whenReleased = 0; // used to identify event that was popup trigger
4482    private boolean awaitingIconChange = false;
4483
4484    @Override
4485    public void mouseClicked(@Nonnull JmriMouseEvent event) {
4486        if (isInputTextBox(event)) {
4487            return;
4488        }
4489
4490        // initialize mouse position
4491        calcLocation(event);
4492
4493        if (!isEditable() && _highlightcomponent != null && highlightCursor) {
4494            _highlightcomponent = null;
4495            redrawPanel();
4496        }
4497
4498        // if alt modifier is down invert the snap to grid behaviour
4499        snapToGridInvert = event.isAltDown();
4500
4501        if (!event.isMetaDown() && !event.isPopupTrigger() && !event.isAltDown()
4502                && !awaitingIconChange && !event.isShiftDown() && !event.isControlDown()) {
4503            List<Positionable> selections = getSelectedItems(event);
4504
4505            if (!selections.isEmpty()) {
4506                selections.get(0).doMouseClicked(event);
4507            }
4508        } else if (event.isPopupTrigger() && (whenReleased != event.getWhen())) {
4509
4510            if (isEditable()) {
4511                selectedObject = null;
4512                selectedHitPointType = HitPointType.NONE;
4513                showEditPopUps(event);
4514            } else {
4515                LocoIcon lo = checkMarkerPopUps(dLoc);
4516
4517                if (lo != null) {
4518                    showPopUp(lo, event);
4519                }
4520            }
4521        }
4522
4523        if (event.isControlDown() && !event.isPopupTrigger()) {
4524            if (findLayoutTracksHitPoint(dLoc)) {
4525                switch (foundHitPointType) {
4526                    case POS_POINT:
4527                    case TURNOUT_CENTER:
4528                    case LEVEL_XING_CENTER:
4529                    case SLIP_LEFT:
4530                    case SLIP_RIGHT:
4531            case TURNTABLE_CENTER:
4532            case TRAVERSER_CENTER: {
4533                        amendSelectionGroup(foundTrack);
4534                        break;
4535                    }
4536
4537                    default: {
4538                        break;
4539                    }
4540                }
4541            } else {
4542                PositionableLabel s = checkSensorIconPopUps(dLoc);
4543                if (s != null) {
4544                    amendSelectionGroup(s);
4545                } else {
4546                    PositionableLabel t = checkTurnoutIconPopUps(dLoc);
4547                    if (t != null) {
4548                        amendSelectionGroup(t);
4549                    } else {
4550                        PositionableLabel sh = checkSignalHeadIconPopUps(dLoc);
4551                        if (sh != null) {
4552                            amendSelectionGroup(sh);
4553                        } else {
4554                            PositionableLabel ms = checkMultiSensorPopUps(dLoc);
4555                            if (ms != null) {
4556                                amendSelectionGroup(ms);
4557                            } else {
4558                                PositionableLabel lb = checkLabelImagePopUps(dLoc);
4559                                if (lb != null) {
4560                                    amendSelectionGroup(lb);
4561                                } else {
4562                                    PositionableLabel b = checkBackgroundPopUps(dLoc);
4563                                    if (b != null) {
4564                                        amendSelectionGroup(b);
4565                                    } else {
4566                                        PositionableLabel sm = checkSignalMastIconPopUps(dLoc);
4567                                        if (sm != null) {
4568                                            amendSelectionGroup(sm);
4569                                        } else {
4570                                            LayoutShape ls = checkLayoutShapePopUps(dLoc);
4571                                            if (ls != null) {
4572                                                amendSelectionGroup(ls);
4573                                            } else {
4574                                                PositionableJPanel jp = checkJPanelPopUps(dLoc);
4575                                                if (jp != null) {
4576                                                    amendSelectionGroup(jp);
4577                                                }
4578                                            }
4579                                        }
4580                                    }
4581                                }
4582                            }
4583                        }
4584                    }
4585                }
4586            }
4587        } else if ((selectionWidth == 0) || (selectionHeight == 0)) {
4588            clearSelectionGroups();
4589        }
4590        requestFocusInWindow();
4591    }
4592
4593    private void checkPointOfPositionable(@Nonnull PositionablePoint p) {
4594        assert p != null;
4595
4596        TrackSegment t = p.getConnect1();
4597
4598        if (t == null) {
4599            t = p.getConnect2();
4600        }
4601
4602        // Nothing connected to this bit of track so ignore
4603        if (t == null) {
4604            return;
4605        }
4606        beginTrack = p;
4607        beginHitPointType = HitPointType.POS_POINT;
4608        PositionablePointView pv = getPositionablePointView(p);
4609        Point2D loc = pv.getCoordsCenter();
4610
4611        if (findLayoutTracksHitPoint(loc, true, p)) {
4612            switch (foundHitPointType) {
4613                case POS_POINT: {
4614                    PositionablePoint p2 = (PositionablePoint) foundTrack;
4615
4616                    if ((p2.getType() == PositionablePoint.PointType.ANCHOR) && p2.setTrackConnection(t)) {
4617                        if (t.getConnect1() == p) {
4618                            t.setNewConnect1(p2, foundHitPointType);
4619                        } else {
4620                            t.setNewConnect2(p2, foundHitPointType);
4621                        }
4622                        p.removeTrackConnection(t);
4623
4624                        if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4625                            removePositionablePoint(p);
4626                        }
4627                    }
4628                    break;
4629                }
4630                case TURNOUT_A:
4631                case TURNOUT_B:
4632                case TURNOUT_C:
4633                case TURNOUT_D:
4634                case SLIP_A:
4635                case SLIP_B:
4636                case SLIP_C:
4637                case SLIP_D:
4638                case LEVEL_XING_A:
4639                case LEVEL_XING_B:
4640                case LEVEL_XING_C:
4641                case LEVEL_XING_D: {
4642                    try {
4643                        if (foundTrack.getConnection(foundHitPointType) == null) {
4644                            foundTrack.setConnection(foundHitPointType, t, HitPointType.TRACK);
4645
4646                            if (t.getConnect1() == p) {
4647                                t.setNewConnect1(foundTrack, foundHitPointType);
4648                            } else {
4649                                t.setNewConnect2(foundTrack, foundHitPointType);
4650                            }
4651                            p.removeTrackConnection(t);
4652
4653                            if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4654                                removePositionablePoint(p);
4655                            }
4656                        }
4657                    } catch (JmriException e) {
4658                        log.debug("Unable to set location");
4659                    }
4660                    break;
4661                }
4662
4663                default: {
4664                    if (HitPointType.isTurntableRayHitType(foundHitPointType)) {
4665                        LayoutTurntable tt = (LayoutTurntable) foundTrack;
4666                        int ray = foundHitPointType.turntableTrackIndex();
4667
4668                        if (tt.getRayConnectIndexed(ray) == null) {
4669                            tt.setRayConnect(t, ray);
4670
4671                            if (t.getConnect1() == p) {
4672                                t.setNewConnect1(tt, foundHitPointType);
4673                            } else {
4674                                t.setNewConnect2(tt, foundHitPointType);
4675                            }
4676                            p.removeTrackConnection(t);
4677
4678                            if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4679                                removePositionablePoint(p);
4680                            }
4681                        }
4682                    } else if (HitPointType.isTraverserSlotHitType(foundHitPointType)) {
4683                        LayoutTraverser tt = (LayoutTraverser) foundTrack;
4684                        int slot = foundHitPointType.traverserTrackIndex();
4685
4686                        if (tt.getSlotConnectIndexed(slot) == null) {
4687                            tt.setSlotConnect(t, slot);
4688
4689                            if (t.getConnect1() == p) {
4690                                t.setNewConnect1(tt, foundHitPointType);
4691                            } else {
4692                                t.setNewConnect2(tt, foundHitPointType);
4693                            }
4694                            p.removeTrackConnection(t);
4695
4696                            if ((p.getConnect1() == null) && (p.getConnect2() == null)) {
4697                                removePositionablePoint(p);
4698                            }
4699                        }
4700                    } else {
4701                        log.debug("No valid point, so will quit");
4702                        return;
4703                    }
4704                    break;
4705                }
4706            }
4707            redrawPanel();
4708
4709            if (t.getLayoutBlock() != null) {
4710                getLEAuxTools().setBlockConnectivityChanged();
4711            }
4712        }
4713        beginTrack = null;
4714    }
4715
4716    // We just dropped a turnout... see if it will connect to anything
4717    private void hitPointCheckLayoutTurnouts(@Nonnull LayoutTurnout lt) {
4718        beginTrack = lt;
4719
4720        LayoutTurnoutView ltv = getLayoutTurnoutView(lt);
4721
4722        if (lt.getConnectA() == null) {
4723            if (lt instanceof LayoutSlip) {
4724                beginHitPointType = HitPointType.SLIP_A;
4725            } else {
4726                beginHitPointType = HitPointType.TURNOUT_A;
4727            }
4728            dLoc = ltv.getCoordsA();
4729            hitPointCheckLayoutTurnoutSubs(dLoc);
4730        }
4731
4732        if (lt.getConnectB() == null) {
4733            if (lt instanceof LayoutSlip) {
4734                beginHitPointType = HitPointType.SLIP_B;
4735            } else {
4736                beginHitPointType = HitPointType.TURNOUT_B;
4737            }
4738            dLoc = ltv.getCoordsB();
4739            hitPointCheckLayoutTurnoutSubs(dLoc);
4740        }
4741
4742        if (lt.getConnectC() == null) {
4743            if (lt instanceof LayoutSlip) {
4744                beginHitPointType = HitPointType.SLIP_C;
4745            } else {
4746                beginHitPointType = HitPointType.TURNOUT_C;
4747            }
4748            dLoc = ltv.getCoordsC();
4749            hitPointCheckLayoutTurnoutSubs(dLoc);
4750        }
4751
4752        if ((lt.getConnectD() == null) && (lt.isTurnoutTypeXover() || lt.isTurnoutTypeSlip())) {
4753            if (lt instanceof LayoutSlip) {
4754                beginHitPointType = HitPointType.SLIP_D;
4755            } else {
4756                beginHitPointType = HitPointType.TURNOUT_D;
4757            }
4758            dLoc = ltv.getCoordsD();
4759            hitPointCheckLayoutTurnoutSubs(dLoc);
4760        }
4761        beginTrack = null;
4762        foundTrack = null;
4763        foundTrackView = null;
4764    }
4765
4766    private void hitPointCheckLayoutTurnoutSubs(@Nonnull Point2D dLoc) {
4767        assert dLoc != null;
4768
4769        if (findLayoutTracksHitPoint(dLoc, true)) {
4770            switch (foundHitPointType) {
4771                case POS_POINT: {
4772                    PositionablePoint p2 = (PositionablePoint) foundTrack;
4773
4774                    if (((p2.getConnect1() == null) && (p2.getConnect2() != null))
4775                            || ((p2.getConnect1() != null) && (p2.getConnect2() == null))) {
4776                        TrackSegment t = p2.getConnect1();
4777
4778                        if (t == null) {
4779                            t = p2.getConnect2();
4780                        }
4781
4782                        if (t == null) {
4783                            return;
4784                        }
4785                        LayoutTurnout lt = (LayoutTurnout) beginTrack;
4786                        try {
4787                            if (lt.getConnection(beginHitPointType) == null) {
4788                                lt.setConnection(beginHitPointType, t, HitPointType.TRACK);
4789                                p2.removeTrackConnection(t);
4790
4791                                if (t.getConnect1() == p2) {
4792                                    t.setNewConnect1(lt, beginHitPointType);
4793                                } else {
4794                                    t.setNewConnect2(lt, beginHitPointType);
4795                                }
4796                                removePositionablePoint(p2);
4797                            }
4798
4799                            if (t.getLayoutBlock() != null) {
4800                                getLEAuxTools().setBlockConnectivityChanged();
4801                            }
4802                        } catch (JmriException e) {
4803                            log.debug("Unable to set location");
4804                        }
4805                    }
4806                    break;
4807                }
4808
4809                case TURNOUT_A:
4810                case TURNOUT_B:
4811                case TURNOUT_C:
4812                case TURNOUT_D:
4813                case SLIP_A:
4814                case SLIP_B:
4815                case SLIP_C:
4816                case SLIP_D: {
4817                    LayoutTurnout ft = (LayoutTurnout) foundTrack;
4818                    addTrackSegment();
4819
4820                    if ((ft.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT) || (ft.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT)) {
4821                        rotateTurnout(ft);
4822                    }
4823
4824                    // Assign a block to the new zero length track segment.
4825                    ((LayoutTurnoutView) foundTrackView).setTrackSegmentBlock(foundHitPointType, true);
4826                    break;
4827                }
4828
4829                default: {
4830                    log.warn("Unexpected foundPointType {} in hitPointCheckLayoutTurnoutSubs", foundHitPointType);
4831                    break;
4832                }
4833            }
4834        }
4835    }
4836
4837    private void rotateTurnout(@Nonnull LayoutTurnout t) {
4838        assert t != null;
4839
4840        LayoutTurnoutView tv = getLayoutTurnoutView(t);
4841
4842        LayoutTurnout be = (LayoutTurnout) beginTrack;
4843        LayoutTurnoutView bev = getLayoutTurnoutView(be);
4844
4845        if (((beginHitPointType == HitPointType.TURNOUT_A) && ((be.getConnectB() != null) || (be.getConnectC() != null)))
4846                || ((beginHitPointType == HitPointType.TURNOUT_B) && ((be.getConnectA() != null) || (be.getConnectC() != null)))
4847                || ((beginHitPointType == HitPointType.TURNOUT_C) && ((be.getConnectB() != null) || (be.getConnectA() != null)))) {
4848            return;
4849        }
4850
4851        if ((be.getTurnoutType() != LayoutTurnout.TurnoutType.RH_TURNOUT) && (be.getTurnoutType() != LayoutTurnout.TurnoutType.LH_TURNOUT)) {
4852            return;
4853        }
4854
4855        Point2D c, diverg, xy2;
4856
4857        if ((foundHitPointType == HitPointType.TURNOUT_C) && (beginHitPointType == HitPointType.TURNOUT_C)) {
4858            c = tv.getCoordsA();
4859            diverg = tv.getCoordsB();
4860            xy2 = MathUtil.subtract(c, diverg);
4861        } else if ((foundHitPointType == HitPointType.TURNOUT_C)
4862                && ((beginHitPointType == HitPointType.TURNOUT_A) || (beginHitPointType == HitPointType.TURNOUT_B))) {
4863
4864            c = tv.getCoordsCenter();
4865            diverg = tv.getCoordsC();
4866
4867            if (beginHitPointType == HitPointType.TURNOUT_A) {
4868                xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
4869            } else {
4870                xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
4871            }
4872        } else if (foundHitPointType == HitPointType.TURNOUT_B) {
4873            c = tv.getCoordsA();
4874            diverg = tv.getCoordsB();
4875
4876            switch (beginHitPointType) {
4877                case TURNOUT_B:
4878                    xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
4879                    break;
4880                case TURNOUT_A:
4881                    xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
4882                    break;
4883                case TURNOUT_C:
4884                default:
4885                    xy2 = MathUtil.subtract(bev.getCoordsCenter(), bev.getCoordsC());
4886                    break;
4887            }
4888        } else if (foundHitPointType == HitPointType.TURNOUT_A) {
4889            c = tv.getCoordsA();
4890            diverg = tv.getCoordsB();
4891
4892            switch (beginHitPointType) {
4893                case TURNOUT_A:
4894                    xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB());
4895                    break;
4896                case TURNOUT_B:
4897                    xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA());
4898                    break;
4899                case TURNOUT_C:
4900                default:
4901                    xy2 = MathUtil.subtract(bev.getCoordsC(), bev.getCoordsCenter());
4902                    break;
4903            }
4904        } else {
4905            return;
4906        }
4907        Point2D xy = MathUtil.subtract(diverg, c);
4908        double radius = Math.toDegrees(Math.atan2(xy.getY(), xy.getX()));
4909        double eRadius = Math.toDegrees(Math.atan2(xy2.getY(), xy2.getX()));
4910        bev.rotateCoords(radius - eRadius);
4911
4912        Point2D conCord = bev.getCoordsA();
4913        Point2D tCord = tv.getCoordsC();
4914
4915        if (foundHitPointType == HitPointType.TURNOUT_B) {
4916            tCord = tv.getCoordsB();
4917        }
4918
4919        if (foundHitPointType == HitPointType.TURNOUT_A) {
4920            tCord = tv.getCoordsA();
4921        }
4922
4923        switch (beginHitPointType) {
4924            case TURNOUT_A:
4925                conCord = bev.getCoordsA();
4926                break;
4927            case TURNOUT_B:
4928                conCord = bev.getCoordsB();
4929                break;
4930            case TURNOUT_C:
4931                conCord = bev.getCoordsC();
4932                break;
4933            default:
4934                break;
4935        }
4936        xy = MathUtil.subtract(conCord, tCord);
4937        Point2D offset = MathUtil.subtract(bev.getCoordsCenter(), xy);
4938        bev.setCoordsCenter(offset);
4939    }
4940
4941    public List<Positionable> _positionableSelection = new ArrayList<>();
4942    public List<LayoutTrack> _layoutTrackSelection = new ArrayList<>();
4943    public List<LayoutShape> _layoutShapeSelection = new ArrayList<>();
4944
4945    @Nonnull
4946    public List<Positionable> getPositionalSelection() {
4947        return _positionableSelection;
4948    }
4949
4950    @Nonnull
4951    public List<LayoutTrack> getLayoutTrackSelection() {
4952        return _layoutTrackSelection;
4953    }
4954
4955    @Nonnull
4956    public List<LayoutShape> getLayoutShapeSelection() {
4957        return _layoutShapeSelection;
4958    }
4959
4960    private void createSelectionGroups() {
4961        Rectangle2D selectionRect = getSelectionRect();
4962
4963        getContents().forEach((o) -> {
4964            if (selectionRect.contains(o.getLocation())) {
4965
4966                log.trace("found item o of class {}", o.getClass());
4967                if (!_positionableSelection.contains(o)) {
4968                    _positionableSelection.add(o);
4969                }
4970            }
4971        });
4972
4973        getLayoutTracks().forEach((lt) -> {
4974            LayoutTrackView ltv = getLayoutTrackView(lt);
4975            Point2D center = ltv.getCoordsCenter();
4976            if (selectionRect.contains(center)) {
4977                if (!_layoutTrackSelection.contains(lt)) {
4978                    _layoutTrackSelection.add(lt);
4979                }
4980            }
4981        });
4982        assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
4983
4984        layoutShapes.forEach((ls) -> {
4985            if (selectionRect.intersects(ls.getBounds())) {
4986                if (!_layoutShapeSelection.contains(ls)) {
4987                    _layoutShapeSelection.add(ls);
4988                }
4989            }
4990        });
4991        redrawPanel();
4992    }
4993
4994    public void clearSelectionGroups() {
4995        selectionActive = false;
4996        _positionableSelection.clear();
4997        _layoutTrackSelection.clear();
4998        assignBlockToSelectionMenuItem.setEnabled(false);
4999        _layoutShapeSelection.clear();
5000    }
5001
5002    private boolean noWarnGlobalDelete = false;
5003
5004    private void deleteSelectedItems() {
5005        if (!noWarnGlobalDelete) {
5006            int selectedValue = JmriJOptionPane.showOptionDialog(this,
5007                    Bundle.getMessage("Question6"), Bundle.getMessage("WarningTitle"),
5008                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
5009                    new Object[]{Bundle.getMessage("ButtonYes"),
5010                        Bundle.getMessage("ButtonNo"),
5011                        Bundle.getMessage("ButtonYesPlus")},
5012                    Bundle.getMessage("ButtonNo"));
5013
5014            // array position 1, ButtonNo or Dialog closed.
5015            if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
5016                return; // return without creating if "No" response
5017            }
5018
5019            if (selectedValue == 2) { // array positio 2, ButtonYesPlus
5020                // Suppress future warnings, and continue
5021                noWarnGlobalDelete = true;
5022            }
5023        }
5024
5025        _positionableSelection.forEach(this::remove);
5026
5027        _layoutTrackSelection.forEach((lt) -> {
5028            if (lt instanceof PositionablePoint) {
5029                boolean oldWarning = noWarnPositionablePoint;
5030                noWarnPositionablePoint = true;
5031                removePositionablePoint((PositionablePoint) lt);
5032                noWarnPositionablePoint = oldWarning;
5033            } else if (lt instanceof LevelXing) {
5034                boolean oldWarning = noWarnLevelXing;
5035                noWarnLevelXing = true;
5036                removeLevelXing((LevelXing) lt);
5037                noWarnLevelXing = oldWarning;
5038            } else if (lt instanceof LayoutSlip) {
5039                boolean oldWarning = noWarnSlip;
5040                noWarnSlip = true;
5041                removeLayoutSlip((LayoutSlip) lt);
5042                noWarnSlip = oldWarning;
5043            } else if (lt instanceof LayoutTurntable) {
5044                boolean oldWarning = noWarnTurntable;
5045                noWarnTurntable = true;
5046                removeTurntable((LayoutTurntable) lt);
5047                noWarnTurntable = oldWarning;
5048            } else if (lt instanceof LayoutTraverser) {
5049                boolean oldWarning = noWarnTraverser;
5050                noWarnTraverser = true;
5051                removeTraverser((LayoutTraverser) lt);
5052                noWarnTraverser = oldWarning;
5053            } else if (lt instanceof LayoutTurnout) {  //<== this includes LayoutSlips
5054                boolean oldWarning = noWarnLayoutTurnout;
5055                noWarnLayoutTurnout = true;
5056                removeLayoutTurnout((LayoutTurnout) lt);
5057                noWarnLayoutTurnout = oldWarning;
5058            }
5059        });
5060
5061        layoutShapes.removeAll(_layoutShapeSelection);
5062
5063        clearSelectionGroups();
5064        redrawPanel();
5065    }
5066
5067    private void amendSelectionGroup(@Nonnull Positionable pos) {
5068        Positionable p = Objects.requireNonNull(pos);
5069
5070        if (_positionableSelection.contains(p)) {
5071            _positionableSelection.remove(p);
5072        } else {
5073            _positionableSelection.add(p);
5074        }
5075        redrawPanel();
5076    }
5077
5078    public void amendSelectionGroup(@Nonnull LayoutTrack track) {
5079        LayoutTrack p = Objects.requireNonNull(track);
5080
5081        if (_layoutTrackSelection.contains(p)) {
5082            _layoutTrackSelection.remove(p);
5083        } else {
5084            _layoutTrackSelection.add(p);
5085        }
5086        assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty());
5087        redrawPanel();
5088    }
5089
5090    public void amendSelectionGroup(@Nonnull LayoutShape shape) {
5091        LayoutShape ls = Objects.requireNonNull(shape);
5092
5093        if (_layoutShapeSelection.contains(ls)) {
5094            _layoutShapeSelection.remove(ls);
5095        } else {
5096            _layoutShapeSelection.add(ls);
5097        }
5098        redrawPanel();
5099    }
5100
5101    public void alignSelection(boolean alignX) {
5102        Point2D minPoint = MathUtil.infinityPoint2D;
5103        Point2D maxPoint = MathUtil.zeroPoint2D;
5104        Point2D sumPoint = MathUtil.zeroPoint2D;
5105        int cnt = 0;
5106
5107        for (Positionable comp : _positionableSelection) {
5108            if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) {
5109                continue;   // skip non-positionables
5110            }
5111            Point2D p = MathUtil.pointToPoint2D(comp.getLocation());
5112            minPoint = MathUtil.min(minPoint, p);
5113            maxPoint = MathUtil.max(maxPoint, p);
5114            sumPoint = MathUtil.add(sumPoint, p);
5115            cnt++;
5116        }
5117
5118        for (LayoutTrack lt : _layoutTrackSelection) {
5119            LayoutTrackView ltv = getLayoutTrackView(lt);
5120            Point2D p = ltv.getCoordsCenter();
5121            minPoint = MathUtil.min(minPoint, p);
5122            maxPoint = MathUtil.max(maxPoint, p);
5123            sumPoint = MathUtil.add(sumPoint, p);
5124            cnt++;
5125        }
5126
5127        for (LayoutShape ls : _layoutShapeSelection) {
5128            Point2D p = ls.getCoordsCenter();
5129            minPoint = MathUtil.min(minPoint, p);
5130            maxPoint = MathUtil.max(maxPoint, p);
5131            sumPoint = MathUtil.add(sumPoint, p);
5132            cnt++;
5133        }
5134
5135        Point2D avePoint = MathUtil.divide(sumPoint, cnt);
5136        int aveX = (int) avePoint.getX();
5137        int aveY = (int) avePoint.getY();
5138
5139        for (Positionable comp : _positionableSelection) {
5140            if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) {
5141                continue;   // skip non-positionables
5142            }
5143
5144            if (alignX) {
5145                comp.setLocation(aveX, comp.getY());
5146            } else {
5147                comp.setLocation(comp.getX(), aveY);
5148            }
5149        }
5150
5151        _layoutTrackSelection.forEach((lt) -> {
5152            LayoutTrackView ltv = getLayoutTrackView(lt);
5153            if (alignX) {
5154                ltv.setCoordsCenter(new Point2D.Double(aveX, ltv.getCoordsCenter().getY()));
5155            } else {
5156                ltv.setCoordsCenter(new Point2D.Double(ltv.getCoordsCenter().getX(), aveY));
5157            }
5158        });
5159
5160        _layoutShapeSelection.forEach((ls) -> {
5161            if (alignX) {
5162                ls.setCoordsCenter(new Point2D.Double(aveX, ls.getCoordsCenter().getY()));
5163            } else {
5164                ls.setCoordsCenter(new Point2D.Double(ls.getCoordsCenter().getX(), aveY));
5165            }
5166        });
5167
5168        redrawPanel();
5169    }
5170
5171    private boolean showAlignPopup() {
5172        return ((!_positionableSelection.isEmpty())
5173                || (!_layoutTrackSelection.isEmpty())
5174                || (!_layoutShapeSelection.isEmpty()));
5175    }
5176
5177    /**
5178     * Offer actions to align the selected Positionable items either
5179     * Horizontally (at average y coord) or Vertically (at average x coord).
5180     *
5181     * @param popup the JPopupMenu to add alignment menu to
5182     * @return true if alignment menu added
5183     */
5184    public boolean setShowAlignmentMenu(@Nonnull JPopupMenu popup) {
5185        if (showAlignPopup()) {
5186            JMenu edit = new JMenu(Bundle.getMessage("EditAlignment"));
5187            edit.add(new AbstractAction(Bundle.getMessage("AlignX")) {
5188                @Override
5189                public void actionPerformed(ActionEvent event) {
5190                    alignSelection(true);
5191                }
5192            });
5193            edit.add(new AbstractAction(Bundle.getMessage("AlignY")) {
5194                @Override
5195                public void actionPerformed(ActionEvent event) {
5196                    alignSelection(false);
5197                }
5198            });
5199            popup.add(edit);
5200
5201            return true;
5202        }
5203        return false;
5204    }
5205
5206    @Override
5207    public void keyPressed(@Nonnull KeyEvent event) {
5208        if (event.getKeyCode() == KeyEvent.VK_DELETE) {
5209            deleteSelectedItems();
5210            return;
5211        }
5212
5213        double deltaX = returnDeltaPositionX(event);
5214        double deltaY = returnDeltaPositionY(event);
5215
5216        if ((deltaX != 0) || (deltaY != 0)) {
5217            selectionX += deltaX;
5218            selectionY += deltaY;
5219
5220            Point2D delta = new Point2D.Double(deltaX, deltaY);
5221            _positionableSelection.forEach((c) -> {
5222                Point2D newPoint = c.getLocation();
5223                if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) {
5224                    MemoryIcon pm = (MemoryIcon) c;
5225                    newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY());
5226                }
5227                newPoint = MathUtil.add(newPoint, delta);
5228                newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
5229                c.setLocation(MathUtil.point2DToPoint(newPoint));
5230            });
5231
5232            _layoutTrackSelection.forEach((lt) -> {
5233                LayoutTrackView ltv = getLayoutTrackView(lt);
5234                Point2D newPoint = MathUtil.add(ltv.getCoordsCenter(), delta);
5235                newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
5236                getLayoutTrackView(lt).setCoordsCenter(newPoint);
5237            });
5238
5239            _layoutShapeSelection.forEach((ls) -> {
5240                Point2D newPoint = MathUtil.add(ls.getCoordsCenter(), delta);
5241                newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint);
5242                ls.setCoordsCenter(newPoint);
5243            });
5244            redrawPanel();
5245            return;
5246        }
5247        getLayoutEditorToolBarPanel().keyPressed(event);
5248    }
5249
5250    private double returnDeltaPositionX(@Nonnull KeyEvent event) {
5251        double result = 0.0;
5252        double amount = event.isShiftDown() ? 5.0 : 1.0;
5253
5254        switch (event.getKeyCode()) {
5255            case KeyEvent.VK_LEFT: {
5256                result = -amount;
5257                break;
5258            }
5259
5260            case KeyEvent.VK_RIGHT: {
5261                result = +amount;
5262                break;
5263            }
5264
5265            default: {
5266                break;
5267            }
5268        }
5269        return result;
5270    }
5271
5272    private double returnDeltaPositionY(@Nonnull KeyEvent event) {
5273        double result = 0.0;
5274        double amount = event.isShiftDown() ? 5.0 : 1.0;
5275
5276        switch (event.getKeyCode()) {
5277            case KeyEvent.VK_UP: {
5278                result = -amount;
5279                break;
5280            }
5281
5282            case KeyEvent.VK_DOWN: {
5283                result = +amount;
5284                break;
5285            }
5286
5287            default: {
5288                break;
5289            }
5290        }
5291        return result;
5292    }
5293
5294    int _prevNumSel = 0;
5295
5296    @Override
5297    public void mouseMoved(@Nonnull JmriMouseEvent event) {
5298        // initialize mouse position
5299        calcLocation(event);
5300
5301        // if alt modifier is down invert the snap to grid behaviour
5302        snapToGridInvert = event.isAltDown();
5303
5304        if (isEditable()) {
5305            leToolBarPanel.setLocationText(dLoc);
5306        }
5307        List<Positionable> selections = getSelectedItems(event);
5308        Positionable selection = null;
5309        int numSel = selections.size();
5310
5311        if (numSel > 0) {
5312            selection = selections.get(0);
5313        }
5314
5315        if ((selection != null) && (selection.getDisplayLevel() > Editor.BKG) && selection.showToolTip()) {
5316            showToolTip(selection, event);
5317        } else if (_targetPanel.getCursor() != Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)) {
5318            super.setToolTip(null);
5319        }
5320
5321        if (numSel != _prevNumSel) {
5322            redrawPanel();
5323            _prevNumSel = numSel;
5324        }
5325
5326        if (findLayoutTracksHitPoint(dLoc)) {
5327            // log.debug("foundTrack: {}", foundTrack);
5328            if (HitPointType.isControlHitType(foundHitPointType)) {
5329                _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
5330                setTurnoutTooltip();
5331            } else {
5332                _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
5333            }
5334            foundTrack = null;
5335            foundHitPointType = HitPointType.NONE;
5336        } else {
5337            _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
5338        }
5339    }   // mouseMoved
5340
5341    private void setTurnoutTooltip() {
5342        if (foundTrackView instanceof LayoutTurnoutView) {
5343            var ltv = (LayoutTurnoutView) foundTrackView;
5344            var lt = ltv.getLayoutTurnout();
5345            if (lt.showToolTip()) {
5346                var tt = lt.getToolTip();
5347                if (tt != null) {
5348                    tt.setText(lt.getNameString());
5349                    var coords = ltv.getCoordsCenter();
5350                    int offsetY = (int) (getTurnoutCircleSize() * SIZE);
5351                    tt.setLocation((int) coords.getX(), (int) coords.getY() + offsetY);
5352                    setToolTip(tt);
5353                }
5354            }
5355        }
5356    }
5357
5358    public void setAllShowLayoutTurnoutToolTip(boolean state) {
5359        log.debug("setAllShowLayoutTurnoutToolTip: {}", state);
5360        for (LayoutTurnout lt : getLayoutTurnoutsAndSlips()) {
5361            lt.setShowToolTip(state);
5362        }
5363    }
5364
5365    private boolean isDragging = false;
5366
5367    @Override
5368    public void mouseDragged(@Nonnull JmriMouseEvent event) {
5369        // initialize mouse position
5370        calcLocation(event);
5371
5372        checkHighlightCursor();
5373
5374        // ignore this event if still at the original point
5375        if ((!isDragging) && (xLoc == getAnchorX()) && (yLoc == getAnchorY())) {
5376            return;
5377        }
5378
5379        // if alt modifier is down invert the snap to grid behaviour
5380        snapToGridInvert = event.isAltDown();
5381
5382        // process this mouse dragged event
5383        if (isEditable()) {
5384            leToolBarPanel.setLocationText(dLoc);
5385        }
5386        currentPoint = MathUtil.add(dLoc, startDelta);
5387        // don't allow negative placement, objects could become unreachable
5388        currentPoint = MathUtil.max(currentPoint, MathUtil.zeroPoint2D);
5389
5390        if ((selectedObject != null) && (event.isMetaDown() || event.isAltDown())
5391                && (selectedHitPointType == HitPointType.MARKER)) {
5392            // marker moves regardless of editMode or positionable
5393            PositionableLabel pl = (PositionableLabel) selectedObject;
5394            pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5395            isDragging = true;
5396            redrawPanel();
5397            return;
5398        }
5399
5400        if (isEditable()) {
5401            if ((selectedObject != null) && event.isMetaDown() && allPositionable()) {
5402                if (snapToGridOnMove != snapToGridInvert) {
5403                    // this snaps currentPoint to the grid
5404                    currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize());
5405                    xLoc = (int) currentPoint.getX();
5406                    yLoc = (int) currentPoint.getY();
5407                    leToolBarPanel.setLocationText(currentPoint);
5408                }
5409
5410                if ((!_positionableSelection.isEmpty())
5411                        || (!_layoutTrackSelection.isEmpty())
5412                        || (!_layoutShapeSelection.isEmpty())) {
5413                    Point2D lastPoint = new Point2D.Double(_lastX, _lastY);
5414                    Point2D offset = MathUtil.subtract(currentPoint, lastPoint);
5415                    Point2D newPoint;
5416
5417                    for (Positionable c : _positionableSelection) {
5418                        if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) {
5419                            MemoryIcon pm = (MemoryIcon) c;
5420                            newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY());
5421                        } else {
5422                            newPoint = c.getLocation();
5423                        }
5424                        newPoint = MathUtil.add(newPoint, offset);
5425                        // don't allow negative placement, objects could become unreachable
5426                        newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
5427                        c.setLocation(MathUtil.point2DToPoint(newPoint));
5428                    }
5429
5430                    for (LayoutTrack lt : _layoutTrackSelection) {
5431                        LayoutTrackView ltv = getLayoutTrackView(lt);
5432                        Point2D center = ltv.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                        getLayoutTrackView(lt).setCoordsCenter(newPoint);
5437                    }
5438
5439                    for (LayoutShape ls : _layoutShapeSelection) {
5440                        Point2D center = ls.getCoordsCenter();
5441                        newPoint = MathUtil.add(center, offset);
5442                        // don't allow negative placement, objects could become unreachable
5443                        newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D);
5444                        ls.setCoordsCenter(newPoint);
5445                    }
5446
5447                    _lastX = xLoc;
5448                    _lastY = yLoc;
5449                } else {
5450                    switch (selectedHitPointType) {
5451                        case POS_POINT:
5452                        case TURNOUT_CENTER:
5453                        case LEVEL_XING_CENTER:
5454                        case SLIP_LEFT:
5455                        case SLIP_RIGHT:
5456                        case TURNTABLE_CENTER:
5457                        case TRAVERSER_CENTER: {
5458                            getLayoutTrackView((LayoutTrack) selectedObject).setCoordsCenter(currentPoint);
5459                            isDragging = true;
5460                            break;
5461                        }
5462
5463                        case TURNOUT_A: {
5464                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsA(currentPoint);
5465                            break;
5466                        }
5467
5468                        case TURNOUT_B: {
5469                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsB(currentPoint);
5470                            break;
5471                        }
5472
5473                        case TURNOUT_C: {
5474                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsC(currentPoint);
5475                            break;
5476                        }
5477
5478                        case TURNOUT_D: {
5479                            getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsD(currentPoint);
5480                            break;
5481                        }
5482
5483                        case LEVEL_XING_A: {
5484                            getLevelXingView((LevelXing) selectedObject).setCoordsA(currentPoint);
5485                            break;
5486                        }
5487
5488                        case LEVEL_XING_B: {
5489                            getLevelXingView((LevelXing) selectedObject).setCoordsB(currentPoint);
5490                            break;
5491                        }
5492
5493                        case LEVEL_XING_C: {
5494                            getLevelXingView((LevelXing) selectedObject).setCoordsC(currentPoint);
5495                            break;
5496                        }
5497
5498                        case LEVEL_XING_D: {
5499                            getLevelXingView((LevelXing) selectedObject).setCoordsD(currentPoint);
5500                            break;
5501                        }
5502
5503                        case SLIP_A: {
5504                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsA(currentPoint);
5505                            break;
5506                        }
5507
5508                        case SLIP_B: {
5509                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsB(currentPoint);
5510                            break;
5511                        }
5512
5513                        case SLIP_C: {
5514                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsC(currentPoint);
5515                            break;
5516                        }
5517
5518                        case SLIP_D: {
5519                            getLayoutSlipView((LayoutSlip) selectedObject).setCoordsD(currentPoint);
5520                            break;
5521                        }
5522
5523                        case LAYOUT_POS_LABEL:
5524                        case MULTI_SENSOR: {
5525                            PositionableLabel pl = (PositionableLabel) selectedObject;
5526                            if (pl.isPositionable()) {
5527                                pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5528                                isDragging = true;
5529                            }
5530                            break;
5531                        }
5532
5533                        case LAYOUT_POS_JCOMP: {
5534                            PositionableJComponent c = (PositionableJComponent) selectedObject;
5535
5536                            if (c.isPositionable()) {
5537                                c.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5538                                isDragging = true;
5539                            }
5540                            break;
5541                        }
5542
5543                        case LAYOUT_POS_JPNL: {
5544                            PositionableJPanel c = (PositionableJPanel) selectedObject;
5545
5546                            if (c.isPositionable()) {
5547                                c.setLocation((int) currentPoint.getX(), (int) currentPoint.getY());
5548                                isDragging = true;
5549                            }
5550                            break;
5551                        }
5552
5553                        case TRACK_CIRCLE_CENTRE: {
5554                            TrackSegmentView tv = getTrackSegmentView((TrackSegment) selectedObject);
5555                            tv.reCalculateTrackSegmentAngle(currentPoint.getX(), currentPoint.getY());
5556                            break;
5557                        }
5558
5559                        default: {
5560                            if (HitPointType.isBezierHitType(foundHitPointType)) {
5561                                int index = selectedHitPointType.bezierPointIndex();
5562                                getTrackSegmentView((TrackSegment) selectedObject).setBezierControlPoint(currentPoint, index);
5563                            } else if ((selectedHitPointType == HitPointType.SHAPE_CENTER)) {
5564                                ((LayoutShape) selectedObject).setCoordsCenter(currentPoint);
5565                            } else if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) {
5566                                int index = selectedHitPointType.shapePointIndex();
5567                                ((LayoutShape) selectedObject).setPoint(index, currentPoint);
5568                            } else if (HitPointType.isTurntableRayHitType(selectedHitPointType)) {
5569                                LayoutTurntable turn = (LayoutTurntable) selectedObject;
5570                                LayoutTurntableView turnView = getLayoutTurntableView(turn);
5571                                turnView.setRayCoordsIndexed(currentPoint.getX(), currentPoint.getY(),
5572                                        selectedHitPointType.turntableTrackIndex());
5573//                             } else if (HitPointType.isTraverserSlotHitType(selectedHitPointType)) {
5574//                                 Placeholder comment:
5575//                                 The ability to drag the slot connection points is disabled.
5576//                                 Connection point locations are relative to the traverser center point.
5577                            }
5578                            break;
5579                        }
5580                    }
5581                }
5582            } else if ((beginTrack != null)
5583                    && event.isShiftDown()
5584                    && leToolBarPanel.trackButton.isSelected()) {
5585                // dragging from first end of Track Segment
5586                currentLocation = new Point2D.Double(xLoc, yLoc);
5587                boolean needResetCursor = (foundTrack != null);
5588
5589                if (findLayoutTracksHitPoint(currentLocation, true)) {
5590                    // have match to free connection point, change cursor
5591                    _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
5592                } else if (needResetCursor) {
5593                    _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
5594                }
5595            } else if (event.isShiftDown()
5596                    && leToolBarPanel.shapeButton.isSelected() && (selectedObject != null)) {
5597                // dragging from end of shape
5598                currentLocation = new Point2D.Double(xLoc, yLoc);
5599            } else if (selectionActive && !event.isShiftDown() && !event.isMetaDown()) {
5600                selectionWidth = xLoc - selectionX;
5601                selectionHeight = yLoc - selectionY;
5602            }
5603            redrawPanel();
5604        } else {
5605            Rectangle r = new Rectangle(event.getX(), event.getY(), 1, 1);
5606            ((JComponent) event.getSource()).scrollRectToVisible(r);
5607        }   // if (isEditable())
5608    }   // mouseDragged
5609
5610    @Override
5611    public void mouseEntered(@Nonnull JmriMouseEvent event) {
5612        _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
5613    }
5614
5615    /**
5616     * Add an Anchor point.
5617     */
5618    public void addAnchor() {
5619        addAnchor(currentPoint);
5620    }
5621
5622    @Nonnull
5623    public PositionablePoint addAnchor(@Nonnull Point2D point) {
5624        Point2D p = Objects.requireNonNull(point);
5625
5626        // get unique name
5627        String name = finder.uniqueName("A", ++numAnchors);
5628
5629        // create object
5630        PositionablePoint o = new PositionablePoint(name,
5631                PositionablePoint.PointType.ANCHOR, this);
5632        PositionablePointView pv = new PositionablePointView(o, p, this);
5633        addLayoutTrack(o, pv);
5634
5635        setDirty();
5636
5637        return o;
5638    }
5639
5640    /**
5641     * Add an End Bumper point.
5642     */
5643    public void addEndBumper() {
5644        // get unique name
5645        String name = finder.uniqueName("EB", ++numEndBumpers);
5646
5647        // create object
5648        PositionablePoint o = new PositionablePoint(name,
5649                PositionablePoint.PointType.END_BUMPER, this);
5650        PositionablePointView pv = new PositionablePointView(o, currentPoint, this);
5651        addLayoutTrack(o, pv);
5652
5653        setDirty();
5654    }
5655
5656    /**
5657     * Add an Edge Connector point.
5658     */
5659    public void addEdgeConnector() {
5660        // get unique name
5661        String name = finder.uniqueName("EC", ++numEdgeConnectors);
5662
5663        // create object
5664        PositionablePoint o = new PositionablePoint(name,
5665                PositionablePoint.PointType.EDGE_CONNECTOR, this);
5666        PositionablePointView pv = new PositionablePointView(o, currentPoint, this);
5667        addLayoutTrack(o, pv);
5668
5669        setDirty();
5670    }
5671
5672    /**
5673     * Add a Track Segment
5674     */
5675    public void addTrackSegment() {
5676        // get unique name
5677        String name = finder.uniqueName("T", ++numTrackSegments);
5678
5679        // create object
5680        newTrack = new TrackSegment(name, beginTrack, beginHitPointType,
5681                foundTrack, foundHitPointType,
5682                leToolBarPanel.mainlineTrack.isSelected(), this);
5683
5684        TrackSegmentView tsv = new TrackSegmentView(
5685                newTrack,
5686                this
5687        );
5688        addLayoutTrack(newTrack, tsv);
5689
5690        setDirty();
5691
5692        // link to connected objects
5693        setLink(beginTrack, beginHitPointType, newTrack, HitPointType.TRACK);
5694        setLink(foundTrack, foundHitPointType, newTrack, HitPointType.TRACK);
5695
5696        // check on layout block
5697        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5698        if (newName == null) {
5699            newName = "";
5700        }
5701        LayoutBlock b = provideLayoutBlock(newName);
5702
5703        if (b != null) {
5704            newTrack.setLayoutBlock(b);
5705            getLEAuxTools().setBlockConnectivityChanged();
5706
5707            // check on occupancy sensor
5708            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5709            if (sensorName == null) {
5710                sensorName = "";
5711            }
5712
5713            if (!sensorName.isEmpty()) {
5714                if (!validateSensor(sensorName, b, this)) {
5715                    b.setOccupancySensorName("");
5716                } else {
5717                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5718                }
5719            }
5720            newTrack.updateBlockInfo();
5721        }
5722    }
5723
5724    /**
5725     * Add a Level Crossing
5726     */
5727    public void addLevelXing() {
5728        // get unique name
5729        String name = finder.uniqueName("X", ++numLevelXings);
5730
5731        // create object
5732        LevelXing o = new LevelXing(name, this);
5733        LevelXingView ov = new LevelXingView(o, currentPoint, this);
5734        addLayoutTrack(o, ov);
5735
5736        setDirty();
5737
5738        // check on layout block
5739        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5740        if (newName == null) {
5741            newName = "";
5742        }
5743        LayoutBlock b = provideLayoutBlock(newName);
5744
5745        if (b != null) {
5746            o.setLayoutBlockAC(b);
5747            o.setLayoutBlockBD(b);
5748
5749            // check on occupancy sensor
5750            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5751            if (sensorName == null) {
5752                sensorName = "";
5753            }
5754
5755            if (!sensorName.isEmpty()) {
5756                if (!validateSensor(sensorName, b, this)) {
5757                    b.setOccupancySensorName("");
5758                } else {
5759                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5760                }
5761            }
5762        }
5763    }
5764
5765    /**
5766     * Add a LayoutSlip
5767     *
5768     * @param type the slip type
5769     */
5770    public void addLayoutSlip(LayoutTurnout.TurnoutType type) {
5771        // get the rotation entry
5772        double rot;
5773        String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim();
5774
5775        if (s.isEmpty()) {
5776            rot = 0.0;
5777        } else {
5778            try {
5779                rot = IntlUtilities.doubleValue(s);
5780            } catch (ParseException e) {
5781                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " "
5782                        + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
5783
5784                return;
5785            }
5786        }
5787
5788        // get unique name
5789        String name = finder.uniqueName("SL", ++numLayoutSlips);
5790
5791        // create object
5792        LayoutSlip o;
5793        LayoutSlipView ov;
5794
5795        switch (type) {
5796            case DOUBLE_SLIP:
5797                LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this);
5798                o = lds;
5799                ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this);
5800                break;
5801            case SINGLE_SLIP:
5802                LayoutSingleSlip lss = new LayoutSingleSlip(name, this);
5803                o = lss;
5804                ov = new LayoutSingleSlipView(lss, currentPoint, rot, this);
5805                break;
5806            default:
5807                log.error("can't create slip {} with type {}", name, type);
5808                return; // without creating
5809        }
5810
5811        addLayoutTrack(o, ov);
5812
5813        setDirty();
5814
5815        // check on layout block
5816        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5817        if (newName == null) {
5818            newName = "";
5819        }
5820        LayoutBlock b = provideLayoutBlock(newName);
5821
5822        if (b != null) {
5823            ov.setLayoutBlock(b);
5824
5825            // check on occupancy sensor
5826            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5827            if (sensorName == null) {
5828                sensorName = "";
5829            }
5830
5831            if (!sensorName.isEmpty()) {
5832                if (!validateSensor(sensorName, b, this)) {
5833                    b.setOccupancySensorName("");
5834                } else {
5835                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5836                }
5837            }
5838        }
5839
5840        String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName();
5841        if (turnoutName == null) {
5842            turnoutName = "";
5843        }
5844
5845        if (validatePhysicalTurnout(turnoutName, this)) {
5846            // turnout is valid and unique.
5847            o.setTurnout(turnoutName);
5848
5849            if (o.getTurnout().getSystemName().equals(turnoutName)) {
5850                leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout());
5851            }
5852        } else {
5853            o.setTurnout("");
5854            leToolBarPanel.turnoutNameComboBox.setSelectedItem(null);
5855            leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1);
5856        }
5857        turnoutName = leToolBarPanel.extraTurnoutNameComboBox.getSelectedItemDisplayName();
5858        if (turnoutName == null) {
5859            turnoutName = "";
5860        }
5861
5862        if (validatePhysicalTurnout(turnoutName, this)) {
5863            // turnout is valid and unique.
5864            o.setTurnoutB(turnoutName);
5865
5866            if (o.getTurnoutB().getSystemName().equals(turnoutName)) {
5867                leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(o.getTurnoutB());
5868            }
5869        } else {
5870            o.setTurnoutB("");
5871            leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(null);
5872            leToolBarPanel.extraTurnoutNameComboBox.setSelectedIndex(-1);
5873        }
5874    }
5875
5876    /**
5877     * Add a Layout Turnout
5878     *
5879     * @param type the turnout type
5880     */
5881    public void addLayoutTurnout(LayoutTurnout.TurnoutType type) {
5882        // get the rotation entry
5883        double rot;
5884        String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim();
5885
5886        if (s.isEmpty()) {
5887            rot = 0.0;
5888        } else {
5889            try {
5890                rot = IntlUtilities.doubleValue(s);
5891            } catch (ParseException e) {
5892                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " "
5893                        + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
5894
5895                return;
5896            }
5897        }
5898
5899        // get unique name
5900        String name = finder.uniqueName("TO", ++numLayoutTurnouts);
5901
5902        // create object - check all types, although not clear all actually reach here
5903        LayoutTurnout o;
5904        LayoutTurnoutView ov;
5905
5906        switch (type) {
5907
5908            case RH_TURNOUT:
5909                LayoutRHTurnout lrht = new LayoutRHTurnout(name, this);
5910                o = lrht;
5911                ov = new LayoutRHTurnoutView(lrht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5912                break;
5913            case LH_TURNOUT:
5914                LayoutLHTurnout llht = new LayoutLHTurnout(name, this);
5915                o = llht;
5916                ov = new LayoutLHTurnoutView(llht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5917                break;
5918            case WYE_TURNOUT:
5919                LayoutWye lw = new LayoutWye(name, this);
5920                o = lw;
5921                ov = new LayoutWyeView(lw, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5922                break;
5923            case DOUBLE_XOVER:
5924                LayoutDoubleXOver ldx = new LayoutDoubleXOver(name, this);
5925                o = ldx;
5926                ov = new LayoutDoubleXOverView(ldx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5927                break;
5928            case RH_XOVER:
5929                LayoutRHXOver lrx = new LayoutRHXOver(name, this);
5930                o = lrx;
5931                ov = new LayoutRHXOverView(lrx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5932                break;
5933            case LH_XOVER:
5934                LayoutLHXOver llx = new LayoutLHXOver(name, this);
5935                o = llx;
5936                ov = new LayoutLHXOverView(llx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this);
5937                break;
5938
5939            case DOUBLE_SLIP:
5940                LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this);
5941                o = lds;
5942                ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this);
5943                log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name);
5944                break;
5945            case SINGLE_SLIP:
5946                LayoutSingleSlip lss = new LayoutSingleSlip(name, this);
5947                o = lss;
5948                ov = new LayoutSingleSlipView(lss, currentPoint, rot, this);
5949                log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name);
5950                break;
5951
5952            default:
5953                log.error("can't create LayoutTrack {} with type {}", name, type);
5954                return; // without creating
5955        }
5956
5957        addLayoutTrack(o, ov);
5958
5959        setDirty();
5960
5961        // check on layout block
5962        String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName();
5963        if (newName == null) {
5964            newName = "";
5965        }
5966        LayoutBlock b = provideLayoutBlock(newName);
5967
5968        if (b != null) {
5969            ov.setLayoutBlock(b);
5970
5971            // check on occupancy sensor
5972            String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName();
5973            if (sensorName == null) {
5974                sensorName = "";
5975            }
5976
5977            if (!sensorName.isEmpty()) {
5978                if (!validateSensor(sensorName, b, this)) {
5979                    b.setOccupancySensorName("");
5980                } else {
5981                    leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor());
5982                }
5983            }
5984        }
5985
5986        // set default continuing route Turnout State
5987        o.setContinuingSense(Turnout.CLOSED);
5988
5989        // check on a physical turnout
5990        String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName();
5991        if (turnoutName == null) {
5992            turnoutName = "";
5993        }
5994
5995        if (validatePhysicalTurnout(turnoutName, this)) {
5996            // turnout is valid and unique.
5997            o.setTurnout(turnoutName);
5998
5999            if (o.getTurnout().getSystemName().equals(turnoutName)) {
6000                leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout());
6001            }
6002        } else {
6003            o.setTurnout("");
6004            leToolBarPanel.turnoutNameComboBox.setSelectedItem(null);
6005            leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1);
6006        }
6007    }
6008
6009    /**
6010     * Validates that a physical turnout exists and is unique among Layout
6011     * Turnouts Returns true if valid turnout was entered, false otherwise
6012     *
6013     * @param inTurnoutName the (system or user) name of the turnout
6014     * @param inOpenPane    the pane over which to show dialogs (null to
6015     *                      suppress dialogs)
6016     * @return true if valid
6017     */
6018    public boolean validatePhysicalTurnout(
6019            @Nonnull String inTurnoutName,
6020            @CheckForNull Component inOpenPane) {
6021        // check if turnout name was entered
6022        if (inTurnoutName.isEmpty()) {
6023            // no turnout entered
6024            return false;
6025        }
6026
6027        // check that the unique turnout name corresponds to a defined physical turnout
6028        Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(inTurnoutName);
6029        if (t == null) {
6030            // There is no turnout corresponding to this name
6031            if (inOpenPane != null) {
6032                JmriJOptionPane.showMessageDialog(inOpenPane,
6033                        MessageFormat.format(Bundle.getMessage("Error8"), inTurnoutName),
6034                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
6035            }
6036            return false;
6037        }
6038
6039        log.debug("validatePhysicalTurnout('{}')", inTurnoutName);
6040        boolean result = true;  // assume success (optimist!)
6041
6042        // ensure that this turnout is unique among Layout Turnouts in this Layout
6043        for (LayoutTurnout lt : getLayoutTurnouts()) {
6044            t = lt.getTurnout();
6045            if (t != null) {
6046                String sname = t.getSystemName();
6047                String uname = t.getUserName();
6048                log.debug("{}: Turnout tested '{}' and '{}'.", lt.getName(), sname, uname);
6049                if ((sname.equals(inTurnoutName))
6050                        || ((uname != null) && (uname.equals(inTurnoutName)))) {
6051                    result = false;
6052                    break;
6053                }
6054            }
6055
6056            // Only check for the second turnout if the type is a double cross over
6057            // otherwise the second turnout is used to throw an additional turnout at
6058            // the same time.
6059            if (lt.isTurnoutTypeXover()) {
6060                t = lt.getSecondTurnout();
6061                if (t != null) {
6062                    String sname = t.getSystemName();
6063                    String uname = t.getUserName();
6064                    log.debug("{}: 2nd Turnout tested '{}' and '{}'.", lt.getName(), sname, uname);
6065                    if ((sname.equals(inTurnoutName))
6066                            || ((uname != null) && (uname.equals(inTurnoutName)))) {
6067                        result = false;
6068                        break;
6069                    }
6070                }
6071            }
6072        }
6073
6074        if (result) {   // only need to test slips if we haven't failed yet...
6075            // ensure that this turnout is unique among Layout slips in this Layout
6076            for (LayoutSlip sl : getLayoutSlips()) {
6077                t = sl.getTurnout();
6078                if (t != null) {
6079                    String sname = t.getSystemName();
6080                    String uname = t.getUserName();
6081                    log.debug("{}: slip Turnout tested '{}' and '{}'.", sl.getName(), sname, uname);
6082                    if ((sname.equals(inTurnoutName))
6083                            || ((uname != null) && (uname.equals(inTurnoutName)))) {
6084                        result = false;
6085                        break;
6086                    }
6087                }
6088
6089                t = sl.getTurnoutB();
6090                if (t != null) {
6091                    String sname = t.getSystemName();
6092                    String uname = t.getUserName();
6093                    log.debug("{}: slip Turnout B tested '{}' and '{}'.", sl.getName(), sname, uname);
6094                    if ((sname.equals(inTurnoutName))
6095                            || ((uname != null) && (uname.equals(inTurnoutName)))) {
6096                        result = false;
6097                        break;
6098                    }
6099                }
6100            }
6101        }
6102
6103        if (result) {   // only need to test Turntable turnouts if we haven't failed yet...
6104            // ensure that this turntable turnout is unique among turnouts in this Layout
6105            for (LayoutTurntable tt : getLayoutTurntables()) {
6106                for (LayoutTurntable.RayTrack ray : tt.getRayTrackList()) {
6107                    t = ray.getTurnout();
6108                    if (t != null) {
6109                        String sname = t.getSystemName();
6110                        String uname = t.getUserName();
6111                        log.debug("{}: Turntable turnout tested '{}' and '{}'.", ray.getTurnoutName(), sname, uname);
6112                        if ((sname.equals(inTurnoutName))
6113                                || ((uname != null) && (uname.equals(inTurnoutName)))) {
6114                            result = false;
6115                            break;
6116                        }
6117                    }
6118                }
6119            }
6120        }
6121
6122        if (result) {   // only need to test Traverser turnouts if we haven't failed yet...
6123            // ensure that this traverser turnout is unique among turnouts in this Layout
6124            for (LayoutTraverser tt : getLayoutTraversers()) {
6125                for (LayoutTraverser.SlotTrack ray : tt.getSlotList()) {
6126                    t = ray.getTurnout();
6127                    if (t != null) {
6128                        String sname = t.getSystemName();
6129                        String uname = t.getUserName();
6130                        log.debug("{}: Traverser turnout tested '{}' and '{}'.", ray.getTurnoutName(), sname, uname);
6131                        if ((sname.equals(inTurnoutName))
6132                                || ((uname != null) && (uname.equals(inTurnoutName)))) {
6133                            result = false;
6134                            break;
6135                        }
6136                    }
6137                }
6138            }
6139        }
6140
6141        if (!result && (inOpenPane != null)) {
6142            JmriJOptionPane.showMessageDialog(inOpenPane,
6143                    MessageFormat.format(Bundle.getMessage("Error4"), inTurnoutName),
6144                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
6145        }
6146        return result;
6147    }
6148
6149    /**
6150     * link the 'from' object and type to the 'to' object and type
6151     *
6152     * @param fromObject    the object to link from
6153     * @param fromPointType the object type to link from
6154     * @param toObject      the object to link to
6155     * @param toPointType   the object type to link to
6156     */
6157    public void setLink(@Nonnull LayoutTrack fromObject, HitPointType fromPointType,
6158            @Nonnull LayoutTrack toObject, HitPointType toPointType) {
6159        switch (fromPointType) {
6160            case POS_POINT: {
6161                if ((toPointType == HitPointType.TRACK) && (fromObject instanceof PositionablePoint)) {
6162                    ((PositionablePoint) fromObject).setTrackConnection((TrackSegment) toObject);
6163                } else {
6164                    log.error("Attempt to link a non-TRACK connection ('{}')to a Positionable Point ('{}')",
6165                            toObject.getName(), fromObject.getName());
6166                }
6167                break;
6168            }
6169
6170            case TURNOUT_A:
6171            case TURNOUT_B:
6172            case TURNOUT_C:
6173            case TURNOUT_D:
6174            case SLIP_A:
6175            case SLIP_B:
6176            case SLIP_C:
6177            case SLIP_D:
6178            case LEVEL_XING_A:
6179            case LEVEL_XING_B:
6180            case LEVEL_XING_C:
6181            case LEVEL_XING_D: {
6182                try {
6183                    fromObject.setConnection(fromPointType, toObject, toPointType);
6184                } catch (JmriException e) {
6185                    // ignore (log.error in setConnection method)
6186                }
6187                break;
6188            }
6189
6190            case TRACK: {
6191                // should never happen, Track Segment links are set in ctor
6192                log.error("Illegal request to set a Track Segment link");
6193                break;
6194            }
6195
6196            default: {
6197                if (HitPointType.isTurntableRayHitType(fromPointType) && (fromObject instanceof LayoutTurntable)) {
6198                    if (toObject instanceof TrackSegment) {
6199                        ((LayoutTurntable) fromObject).setRayConnect((TrackSegment) toObject,
6200                                fromPointType.turntableTrackIndex());
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 if (HitPointType.isTraverserSlotHitType(fromPointType) && (fromObject instanceof LayoutTraverser)) {
6206                    if (toObject instanceof TrackSegment) {
6207                        ((LayoutTraverser) fromObject).setSlotConnect((TrackSegment) toObject,
6208                                fromPointType.traverserTrackIndex());
6209                    } else {
6210                        log.warn("setLink found expected toObject type {} with fromPointType {} fromObject type {}",
6211                                toObject.getClass(), fromPointType, fromObject.getClass(), new Exception("traceback"));
6212                    }
6213                } else {
6214                    log.warn("setLink found expected fromObject type {} with fromPointType {} toObject type {}",
6215                            fromObject.getClass(), fromPointType, toObject.getClass(), new Exception("traceback"));
6216                }
6217                break;
6218            }
6219        }
6220    }
6221
6222    /**
6223     * Return a layout block with the entered name, creating a new one if
6224     * needed. Note that the entered name becomes the user name of the
6225     * LayoutBlock, and a system name is automatically created by
6226     * LayoutBlockManager if needed.
6227     * <p>
6228     * If the block name is a system name, then the user will have to supply a
6229     * user name for the block.
6230     * <p>
6231     * Some, but not all, errors pop a Swing error dialog in addition to
6232     * logging.
6233     *
6234     * @param inBlockName the entered name
6235     * @return the provided LayoutBlock
6236     */
6237    public LayoutBlock provideLayoutBlock(@Nonnull String inBlockName) {
6238        LayoutBlock result = null; // assume failure (pessimist!)
6239        LayoutBlock newBlk = null; // assume failure (pessimist!)
6240
6241        if (inBlockName.isEmpty()) {
6242            // nothing entered, try autoAssign
6243            if (autoAssignBlocks) {
6244                newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock();
6245                if (null == newBlk) {
6246                    log.error("provideLayoutBlock: Failure to auto-assign for empty LayoutBlock name");
6247                }
6248            } else {
6249                log.debug("provideLayoutBlock: no name given and not assigning auto block names");
6250            }
6251        } else {
6252            // check if this Layout Block already exists
6253            result = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(inBlockName);
6254            if (result == null) { //(no)
6255                // The combo box name can be either a block system name or a block user name
6256                Block checkBlock = InstanceManager.getDefault(BlockManager.class).getBlock(inBlockName);
6257                if (checkBlock == null) {
6258                    log.error("provideLayoutBlock: The block name '{}' does not return a block.", inBlockName);
6259                } else {
6260                    String checkUserName = checkBlock.getUserName();
6261                    if (checkUserName != null && checkUserName.equals(inBlockName)) {
6262                        // Go ahead and use the name for the layout block
6263                        newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, inBlockName);
6264                        if (newBlk == null) {
6265                            log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}'.", inBlockName);
6266                        }
6267                    } else {
6268                        // Appears to be a system name, request a user name
6269                        String blkUserName = (String)JmriJOptionPane.showInputDialog(getTargetFrame(),
6270                                Bundle.getMessage("BlkUserNameMsg"),
6271                                Bundle.getMessage("BlkUserNameTitle"),
6272                                JmriJOptionPane.PLAIN_MESSAGE, null, null, "");
6273                        if (blkUserName != null && !blkUserName.isEmpty()) {
6274                            // Verify the user name
6275                            Block checkDuplicate = InstanceManager.getDefault(BlockManager.class).getByUserName(blkUserName);
6276                            if (checkDuplicate != null) {
6277                                JmriJOptionPane.showMessageDialog(getTargetFrame(),
6278                                        Bundle.getMessage("BlkUserNameInUse", blkUserName),
6279                                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
6280                            } else {
6281                                // OK to use as a block user name
6282                                checkBlock.setUserName(blkUserName);
6283                                newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, blkUserName);
6284                                if (newBlk == null) {
6285                                    log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}' with a new user name.", blkUserName);
6286                                }
6287                            }
6288                        }
6289                    }
6290                }
6291            }
6292        }
6293
6294        // if we created a new block
6295        if (newBlk != null) {
6296            // initialize the new block
6297            // log.debug("provideLayoutBlock :: Init new block {}", inBlockName);
6298            newBlk.initializeLayoutBlock();
6299            newBlk.initializeLayoutBlockRouting();
6300            newBlk.setBlockTrackColor(defaultTrackColor);
6301            newBlk.setBlockOccupiedColor(defaultOccupiedTrackColor);
6302            newBlk.setBlockExtraColor(defaultAlternativeTrackColor);
6303            result = newBlk;
6304        }
6305
6306        if (result != null) {
6307            // set both new and previously existing block
6308            result.addLayoutEditor(this);
6309            result.incrementUse();
6310            setDirty();
6311        }
6312        return result;
6313    }
6314
6315    /**
6316     * Validates that the supplied occupancy sensor name corresponds to an
6317     * existing sensor and is unique among all blocks. If valid, returns true
6318     * and sets the block sensor name in the block. Else returns false, and does
6319     * nothing to the block.
6320     *
6321     * @param sensorName the sensor name to validate
6322     * @param blk        the LayoutBlock in which to set it
6323     * @param openFrame  the frame (Component) it is in
6324     * @return true if sensor is valid
6325     */
6326    public boolean validateSensor(
6327            @Nonnull String sensorName,
6328            @Nonnull LayoutBlock blk,
6329            @Nonnull Component openFrame) {
6330        boolean result = false; // assume failure (pessimist!)
6331
6332        // check if anything entered
6333        if (!sensorName.isEmpty()) {
6334            // get a validated sensor corresponding to this name and assigned to block
6335            if (blk.getOccupancySensorName().equals(sensorName)) {
6336                result = true;
6337            } else {
6338                Sensor s = blk.validateSensor(sensorName, openFrame);
6339                result = (s != null); // if sensor returned result is true.
6340            }
6341        }
6342        return result;
6343    }
6344
6345    /**
6346     * Return a layout block with the given name if one exists. Registers this
6347     * LayoutEditor with the layout block. This method is designed to be used
6348     * when a panel is loaded. The calling method must handle whether the use
6349     * count should be incremented.
6350     *
6351     * @param blockID the given name
6352     * @return null if blockID does not already exist
6353     */
6354    public LayoutBlock getLayoutBlock(@Nonnull String blockID) {
6355        // check if this Layout Block already exists
6356        LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(blockID);
6357        if (blk == null) {
6358            log.error("LayoutBlock '{}' not found when panel loaded", blockID);
6359            return null;
6360        }
6361        blk.addLayoutEditor(this);
6362        return blk;
6363    }
6364
6365    /**
6366     * Remove object from all Layout Editor temporary lists of items not part of
6367     * track schematic
6368     *
6369     * @param s the object to remove
6370     * @return true if found
6371     */
6372    private boolean remove(@Nonnull Positionable s) {
6373        boolean found = false;
6374
6375        if (backgroundImage.contains(s)) {
6376            backgroundImage.remove(s);
6377            found = true;
6378        }
6379        if (memoryLabelList.contains(s)) {
6380            memoryLabelList.remove(s);
6381            found = true;
6382        }
6383        if (memoryInputList.contains(s)) {
6384            memoryInputList.remove(s);
6385            found = true;
6386        }
6387        if (globalVariableLabelList.contains(s)) {
6388            globalVariableLabelList.remove(s);
6389            found = true;
6390        }
6391        if (blockContentsLabelList.contains(s)) {
6392            blockContentsLabelList.remove(s);
6393            found = true;
6394        }
6395        if (blockContentsInputList.contains(s)) {
6396            blockContentsInputList.remove(s);
6397            found = true;
6398        }
6399        if (multiSensors.contains(s)) {
6400            multiSensors.remove(s);
6401            found = true;
6402        }
6403        if (clocks.contains(s)) {
6404            clocks.remove(s);
6405            found = true;
6406        }
6407        if (labelImage.contains(s)) {
6408            labelImage.remove(s);
6409            found = true;
6410        }
6411
6412        if (sensorImage.contains(s) || sensorList.contains(s)) {
6413            Sensor sensor = ((SensorIcon) s).getSensor();
6414            if (sensor != null) {
6415                if (removeAttachedBean((sensor))) {
6416                    sensorImage.remove(s);
6417                    sensorList.remove(s);
6418                    found = true;
6419                } else {
6420                    return false;
6421                }
6422            }
6423        }
6424
6425        if (turnoutImage.contains(s) || turnoutList.contains(s)) {
6426            Turnout turnout = ((TurnoutIcon) s).getTurnout();
6427            if (turnout != null) {
6428                if (removeAttachedBean((turnout))) {
6429                    turnoutImage.remove(s);
6430                    turnoutList.remove(s);
6431                    found = true;
6432                } else {
6433                    return false;
6434                }
6435            }
6436        }
6437
6438        if (signalHeadImage.contains(s) || signalList.contains(s)) {
6439            SignalHead head = ((SignalHeadIcon) s).getSignalHead();
6440            if (head != null) {
6441                if (removeAttachedBean((head))) {
6442                    signalHeadImage.remove(s);
6443                    signalList.remove(s);
6444                    found = true;
6445                } else {
6446                    return false;
6447                }
6448            }
6449        }
6450
6451        if (signalMastList.contains(s)) {
6452            SignalMast mast = ((SignalMastIcon) s).getSignalMast();
6453            if (mast != null) {
6454                if (removeAttachedBean((mast))) {
6455                    signalMastList.remove(s);
6456                    found = true;
6457                } else {
6458                    return false;
6459                }
6460            }
6461        }
6462
6463        super.removeFromContents(s);
6464
6465        if (found) {
6466            setDirty();
6467            redrawPanel();
6468        }
6469        return found;
6470    }
6471
6472    @Override
6473    public boolean removeFromContents(@Nonnull Positionable l) {
6474        return remove(l);
6475    }
6476
6477    private String findBeanUsage(@Nonnull NamedBean bean) {
6478        PositionablePoint pe;
6479        PositionablePoint pw;
6480        LayoutTurnout lt;
6481        LevelXing lx;
6482        LayoutSlip ls;
6483        boolean found = false;
6484        StringBuilder sb = new StringBuilder();
6485        String msgKey = "DeleteReference";  // NOI18N
6486        String beanKey = "None";  // NOI18N
6487        String beanValue = bean.getDisplayName();
6488
6489        if (bean instanceof SignalMast) {
6490            beanKey = "BeanNameSignalMast";  // NOI18N
6491
6492            if (InstanceManager.getDefault(SignalMastLogicManager.class).isSignalMastUsed((SignalMast) bean)) {
6493                SignalMastLogic sml = InstanceManager.getDefault(
6494                        SignalMastLogicManager.class).getSignalMastLogic((SignalMast) bean);
6495                if ((sml != null) && sml.useLayoutEditor(sml.getDestinationList().get(0))) {
6496                    msgKey = "DeleteSmlReference";  // NOI18N
6497                }
6498            }
6499        } else if (bean instanceof Sensor) {
6500            beanKey = "BeanNameSensor";  // NOI18N
6501        } else if (bean instanceof Turnout) {
6502            beanKey = "BeanNameTurnout";  // NOI18N
6503        } else if (bean instanceof SignalHead) {
6504            beanKey = "BeanNameSignalHead";  // NOI18N
6505        }
6506        if (!beanKey.equals("None")) {  // NOI18N
6507            sb.append(Bundle.getMessage(msgKey, Bundle.getMessage(beanKey), beanValue));
6508        }
6509
6510        if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) {
6511            TrackSegment t1 = pw.getConnect1();
6512            TrackSegment t2 = pw.getConnect2();
6513            if (t1 != null) {
6514                if (t2 != null) {
6515                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6516                    sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName()));  // NOI18N
6517                } else {
6518                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6519                }
6520            }
6521            found = true;
6522        }
6523
6524        if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) {
6525            TrackSegment t1 = pe.getConnect1();
6526            TrackSegment t2 = pe.getConnect2();
6527
6528            if (t1 != null) {
6529                if (t2 != null) {
6530                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6531                    sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName()));  // NOI18N
6532                } else {
6533                    sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName()));  // NOI18N
6534                }
6535            }
6536            found = true;
6537        }
6538
6539        if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) {
6540            sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("BeanNameTurnout"), lt.getTurnoutName()));   // NOI18N
6541            found = true;
6542        }
6543
6544        if ((lx = finder.findLevelXingByBean(bean)) != null) {
6545            sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("LevelCrossing"), lx.getId()));   // NOI18N
6546            found = true;
6547        }
6548
6549        if ((ls = finder.findLayoutSlipByBean(bean)) != null) {
6550            sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("Slip"), ls.getTurnoutName()));   // NOI18N
6551            found = true;
6552        }
6553
6554        if (!found) {
6555            return null;
6556        }
6557        return sb.toString();
6558    }
6559
6560    /**
6561     * NX Sensors, Signal Heads and Signal Masts can be attached to positional
6562     * points, turnouts and level crossings. If an attachment exists, present an
6563     * option to cancel the remove action, remove the attachement or retain the
6564     * attachment.
6565     *
6566     * @param bean The named bean to be removed.
6567     * @return true if OK to remove the related icon.
6568     */
6569    private boolean removeAttachedBean(@Nonnull NamedBean bean) {
6570        String usage = findBeanUsage(bean);
6571
6572        if (usage != null) {
6573            usage = String.format("<html>%s</html>", usage);
6574            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6575                    usage, Bundle.getMessage("WarningTitle"),
6576                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6577                    new Object[]{Bundle.getMessage("ButtonYes"),
6578                        Bundle.getMessage("ButtonNo"),
6579                        Bundle.getMessage("ButtonCancel")},
6580                    Bundle.getMessage("ButtonYes"));
6581
6582            if (selectedValue == 1 ) { // array pos 1, No
6583                return true; // return leaving the references in place but allow the icon to be deleted.
6584            }
6585            // array pos 2, cancel or Dialog closed
6586            if (selectedValue == 2 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
6587                return false; // do not delete the item
6588            }
6589            if (bean instanceof Sensor) {
6590                // Additional actions for NX sensor pairs
6591                return getLETools().removeSensorAssignment((Sensor) bean);
6592            } else {
6593                removeBeanRefs(bean);
6594            }
6595        }
6596        return true;
6597    }
6598
6599    private void removeBeanRefs(@Nonnull NamedBean bean) {
6600        PositionablePoint pe;
6601        PositionablePoint pw;
6602        LayoutTurnout lt;
6603        LevelXing lx;
6604        LayoutSlip ls;
6605
6606        if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) {
6607            pw.removeBeanReference(bean);
6608        }
6609
6610        if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) {
6611            pe.removeBeanReference(bean);
6612        }
6613
6614        if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) {
6615            lt.removeBeanReference(bean);
6616        }
6617
6618        if ((lx = finder.findLevelXingByBean(bean)) != null) {
6619            lx.removeBeanReference(bean);
6620        }
6621
6622        if ((ls = finder.findLayoutSlipByBean(bean)) != null) {
6623            ls.removeBeanReference(bean);
6624        }
6625    }
6626
6627    private boolean noWarnPositionablePoint = false;
6628
6629    /**
6630     * Remove a PositionablePoint -- an Anchor or an End Bumper.
6631     *
6632     * @param o the PositionablePoint to remove
6633     * @return true if removed
6634     */
6635    public boolean removePositionablePoint(@Nonnull PositionablePoint o) {
6636        // First verify with the user that this is really wanted, only show message if there is a bit of track connected
6637        if ((o.getConnect1() != null) || (o.getConnect2() != null)) {
6638            if (!noWarnPositionablePoint) {
6639                int selectedValue = JmriJOptionPane.showOptionDialog(this,
6640                        Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"),
6641                        JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6642                        new Object[]{Bundle.getMessage("ButtonYes"),
6643                            Bundle.getMessage("ButtonNo"),
6644                            Bundle.getMessage("ButtonYesPlus")},
6645                        Bundle.getMessage("ButtonNo"));
6646
6647                // array position 1, ButtonNo , or Dialog Closed.
6648                if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) {
6649                    return false; // return without creating if "No" response
6650                }
6651
6652                if (selectedValue == 2) { // array position 2, ButtonYesPlus
6653                    // Suppress future warnings, and continue
6654                    noWarnPositionablePoint = true;
6655                }
6656            }
6657
6658            // remove from selection information
6659            if (selectedObject == o) {
6660                selectedObject = null;
6661            }
6662
6663            if (prevSelectedObject == o) {
6664                prevSelectedObject = null;
6665            }
6666
6667            // remove connections if any
6668            TrackSegment t1 = o.getConnect1();
6669            TrackSegment t2 = o.getConnect2();
6670
6671            if (t1 != null) {
6672                removeTrackSegment(t1);
6673            }
6674
6675            if (t2 != null) {
6676                removeTrackSegment(t2);
6677            }
6678
6679            // delete from array
6680        }
6681
6682        return removeLayoutTrackAndRedraw(o);
6683    }
6684
6685    private boolean noWarnLayoutTurnout = false;
6686
6687    /**
6688     * Remove a LayoutTurnout
6689     *
6690     * @param o the LayoutTurnout to remove
6691     * @return true if removed
6692     */
6693    public boolean removeLayoutTurnout(@Nonnull LayoutTurnout o) {
6694        // First verify with the user that this is really wanted
6695        if (!noWarnLayoutTurnout) {
6696            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6697                    Bundle.getMessage("Question1r"), Bundle.getMessage("WarningTitle"),
6698                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6699                    new Object[]{Bundle.getMessage("ButtonYes"),
6700                        Bundle.getMessage("ButtonNo"),
6701                        Bundle.getMessage("ButtonYesPlus")},
6702                    Bundle.getMessage("ButtonNo"));
6703
6704            // return without removing if array position 1 "No" response or Dialog closed
6705            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6706                return false;
6707            }
6708
6709            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
6710                // Suppress future warnings, and continue
6711                noWarnLayoutTurnout = true;
6712            }
6713        }
6714
6715        // remove from selection information
6716        if (selectedObject == o) {
6717            selectedObject = null;
6718        }
6719
6720        if (prevSelectedObject == o) {
6721            prevSelectedObject = null;
6722        }
6723
6724        // remove connections if any
6725        TrackSegment t = (TrackSegment) o.getConnectA();
6726
6727        if (t != null) {
6728            substituteAnchor(getLayoutTurnoutView(o).getCoordsA(), o, t);
6729        }
6730        t = (TrackSegment) o.getConnectB();
6731
6732        if (t != null) {
6733            substituteAnchor(getLayoutTurnoutView(o).getCoordsB(), o, t);
6734        }
6735        t = (TrackSegment) o.getConnectC();
6736
6737        if (t != null) {
6738            substituteAnchor(getLayoutTurnoutView(o).getCoordsC(), o, t);
6739        }
6740        t = (TrackSegment) o.getConnectD();
6741
6742        if (t != null) {
6743            substituteAnchor(getLayoutTurnoutView(o).getCoordsD(), o, t);
6744        }
6745
6746        // decrement Block use count(s)
6747        LayoutBlock b = o.getLayoutBlock();
6748
6749        if (b != null) {
6750            b.decrementUse();
6751        }
6752
6753        if (o.isTurnoutTypeXover() || o.isTurnoutTypeSlip()) {
6754            LayoutBlock b2 = o.getLayoutBlockB();
6755
6756            if ((b2 != null) && (b2 != b)) {
6757                b2.decrementUse();
6758            }
6759            LayoutBlock b3 = o.getLayoutBlockC();
6760
6761            if ((b3 != null) && (b3 != b) && (b3 != b2)) {
6762                b3.decrementUse();
6763            }
6764            LayoutBlock b4 = o.getLayoutBlockD();
6765
6766            if ((b4 != null) && (b4 != b)
6767                    && (b4 != b2) && (b4 != b3)) {
6768                b4.decrementUse();
6769            }
6770        }
6771
6772        return removeLayoutTrackAndRedraw(o);
6773    }
6774
6775    private void substituteAnchor(@Nonnull Point2D loc,
6776            @Nonnull LayoutTrack o, @Nonnull TrackSegment t) {
6777        PositionablePoint p = addAnchor(loc);
6778
6779        if (t.getConnect1() == o) {
6780            t.setNewConnect1(p, HitPointType.POS_POINT);
6781        }
6782
6783        if (t.getConnect2() == o) {
6784            t.setNewConnect2(p, HitPointType.POS_POINT);
6785        }
6786        p.setTrackConnection(t);
6787    }
6788
6789    private boolean noWarnLevelXing = false;
6790
6791    /**
6792     * Remove a Level Crossing
6793     *
6794     * @param o the LevelXing to remove
6795     * @return true if removed
6796     */
6797    public boolean removeLevelXing(@Nonnull LevelXing o) {
6798        // First verify with the user that this is really wanted
6799        if (!noWarnLevelXing) {
6800            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6801                    Bundle.getMessage("Question3r"), Bundle.getMessage("WarningTitle"),
6802                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6803                    new Object[]{Bundle.getMessage("ButtonYes"),
6804                        Bundle.getMessage("ButtonNo"),
6805                        Bundle.getMessage("ButtonYesPlus")},
6806                    Bundle.getMessage("ButtonNo"));
6807
6808             // array position 1 Button No, or Dialog closed.
6809            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6810                return false;
6811            }
6812
6813            if (selectedValue == 2 ) { // array position 2 ButtonYesPlus
6814                // Suppress future warnings, and continue
6815                noWarnLevelXing = true;
6816            }
6817        }
6818
6819        // remove from selection information
6820        if (selectedObject == o) {
6821            selectedObject = null;
6822        }
6823
6824        if (prevSelectedObject == o) {
6825            prevSelectedObject = null;
6826        }
6827
6828        // remove connections if any
6829        LevelXingView ov = getLevelXingView(o);
6830
6831        TrackSegment t = (TrackSegment) o.getConnectA();
6832        if (t != null) {
6833            substituteAnchor(ov.getCoordsA(), o, t);
6834        }
6835        t = (TrackSegment) o.getConnectB();
6836
6837        if (t != null) {
6838            substituteAnchor(ov.getCoordsB(), o, t);
6839        }
6840        t = (TrackSegment) o.getConnectC();
6841
6842        if (t != null) {
6843            substituteAnchor(ov.getCoordsC(), o, t);
6844        }
6845        t = (TrackSegment) o.getConnectD();
6846
6847        if (t != null) {
6848            substituteAnchor(ov.getCoordsD(), o, t);
6849        }
6850
6851        // decrement block use count if any blocks in use
6852        LayoutBlock lb = o.getLayoutBlockAC();
6853
6854        if (lb != null) {
6855            lb.decrementUse();
6856        }
6857        LayoutBlock lbx = o.getLayoutBlockBD();
6858
6859        if ((lbx != null) && (lb != null) && (lbx != lb)) {
6860            lb.decrementUse();
6861        }
6862
6863        return removeLayoutTrackAndRedraw(o);
6864    }
6865
6866    private boolean noWarnSlip = false;
6867
6868    /**
6869     * Remove a slip
6870     *
6871     * @param o the LayoutSlip to remove
6872     * @return true if removed
6873     */
6874    public boolean removeLayoutSlip(@Nonnull LayoutTurnout o) {
6875        if (!(o instanceof LayoutSlip)) {
6876            return false;
6877        }
6878
6879        // First verify with the user that this is really wanted
6880        if (!noWarnSlip) {
6881            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6882                    Bundle.getMessage("Question5r"), Bundle.getMessage("WarningTitle"),
6883                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6884                    new Object[]{Bundle.getMessage("ButtonYes"),
6885                        Bundle.getMessage("ButtonNo"),
6886                        Bundle.getMessage("ButtonYesPlus")},
6887                    Bundle.getMessage("ButtonNo"));
6888
6889             // return without removing if array position 1 "No" response or Dialog closed
6890            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6891                return false;
6892            }
6893
6894            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
6895                // Suppress future warnings, and continue
6896                noWarnSlip = true;
6897            }
6898        }
6899
6900        LayoutTurnoutView ov = getLayoutTurnoutView(o);
6901
6902        // remove from selection information
6903        if (selectedObject == o) {
6904            selectedObject = null;
6905        }
6906
6907        if (prevSelectedObject == o) {
6908            prevSelectedObject = null;
6909        }
6910
6911        // remove connections if any
6912        TrackSegment t = (TrackSegment) o.getConnectA();
6913
6914        if (t != null) {
6915            substituteAnchor(ov.getCoordsA(), o, t);
6916        }
6917        t = (TrackSegment) o.getConnectB();
6918
6919        if (t != null) {
6920            substituteAnchor(ov.getCoordsB(), o, t);
6921        }
6922        t = (TrackSegment) o.getConnectC();
6923
6924        if (t != null) {
6925            substituteAnchor(ov.getCoordsC(), o, t);
6926        }
6927        t = (TrackSegment) o.getConnectD();
6928
6929        if (t != null) {
6930            substituteAnchor(ov.getCoordsD(), o, t);
6931        }
6932
6933        // decrement block use count if any blocks in use
6934        LayoutBlock lb = o.getLayoutBlock();
6935
6936        if (lb != null) {
6937            lb.decrementUse();
6938        }
6939
6940        return removeLayoutTrackAndRedraw(o);
6941    }
6942
6943    private boolean noWarnTurntable = false;
6944    private boolean noWarnTraverser = false;
6945
6946    /**
6947     * Remove a Layout Turntable
6948     *
6949     * @param o the LayoutTurntable to remove
6950     * @return true if removed
6951     */
6952    public boolean removeTurntable(@Nonnull LayoutTurntable o) {
6953        // First verify with the user that this is really wanted
6954        if (!noWarnTurntable) {
6955            int selectedValue = JmriJOptionPane.showOptionDialog(this,
6956                    Bundle.getMessage("Question4r"), Bundle.getMessage("WarningTitle"),
6957                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
6958                    new Object[]{Bundle.getMessage("ButtonYes"),
6959                        Bundle.getMessage("ButtonNo"),
6960                        Bundle.getMessage("ButtonYesPlus")},
6961                    Bundle.getMessage("ButtonNo"));
6962
6963            // return without removing if array position 1 "No" response or Dialog closed
6964            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
6965                return false;
6966            }
6967
6968            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
6969                // Suppress future warnings, and continue
6970                noWarnTurntable = true;
6971            }
6972        }
6973
6974        // Check if removing the turntable will cause errors.
6975        if (!o.isRemoveAllowed()) {
6976            return false;
6977        }
6978
6979        // remove from selection information
6980        if (selectedObject == o) {
6981            selectedObject = null;
6982        }
6983
6984        if (prevSelectedObject == o) {
6985            prevSelectedObject = null;
6986        }
6987
6988        // remove connections if any
6989        LayoutTurntableView ov = getLayoutTurntableView(o);
6990        for (int j = 0; j < o.getNumberRays(); j++) {
6991            TrackSegment t = ov.getRayConnectOrdered(j);
6992
6993            if (t != null) {
6994                substituteAnchor(ov.getRayCoordsIndexed(j), o, t);
6995            }
6996        }
6997
6998        return removeLayoutTrackAndRedraw(o);
6999    }
7000
7001    /**
7002     * Remove a Layout Traverser
7003     *
7004     * @param o the LayoutTraverser to remove
7005     * @return true if removed
7006     */
7007    public boolean removeTraverser(@Nonnull LayoutTraverser o) {
7008        // First verify with the user that this is really wanted
7009        if (!noWarnTraverser) {
7010            int selectedValue = JmriJOptionPane.showOptionDialog(this,
7011                    Bundle.getMessage("Question8r"), Bundle.getMessage("WarningTitle"),
7012                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
7013                    new Object[]{Bundle.getMessage("ButtonYes"),
7014                            Bundle.getMessage("ButtonNo"),
7015                            Bundle.getMessage("ButtonYesPlus")},
7016                    Bundle.getMessage("ButtonNo"));
7017
7018            // return without removing if array position 1 "No" response or Dialog closed
7019            if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) {
7020                return false;
7021            }
7022
7023            if (selectedValue == 2 ) { // ButtonYesPlus in array position 2
7024                // Suppress future warnings, and continue
7025                noWarnTraverser = true;
7026            }
7027        }
7028
7029        // Check if removing the traverser will cause errors.
7030        if (!o.isRemoveAllowed()) {
7031            return false;
7032        }
7033
7034        // remove from selection information
7035        if (selectedObject == o) {
7036            selectedObject = null;
7037        }
7038
7039        if (prevSelectedObject == o) {
7040            prevSelectedObject = null;
7041        }
7042
7043        // remove connections if any
7044        LayoutTraverserView ov = getLayoutTraverserView(o);
7045        for (int j = 0; j < o.getNumberSlots(); j++) {
7046            TrackSegment t = ov.getSlotConnectOrdered(j);
7047            if (t != null) {
7048                substituteAnchor(ov.getSlotCoordsIndexed(j), o, t);
7049            }
7050        }
7051        return removeLayoutTrackAndRedraw(o);
7052    }
7053
7054    /**
7055     * Remove a Track Segment
7056     *
7057     * @param o the TrackSegment to remove
7058     */
7059    public void removeTrackSegment(@Nonnull TrackSegment o) {
7060        // save affected blocks
7061        LayoutBlock block1 = null;
7062        LayoutBlock block2 = null;
7063        LayoutBlock block = o.getLayoutBlock();
7064
7065        // remove any connections
7066        HitPointType type = o.getType1();
7067
7068        if (type == HitPointType.POS_POINT) {
7069            PositionablePoint p = (PositionablePoint) (o.getConnect1());
7070
7071            if (p != null) {
7072                p.removeTrackConnection(o);
7073
7074                if (p.getConnect1() != null) {
7075                    block1 = p.getConnect1().getLayoutBlock();
7076                } else if (p.getConnect2() != null) {
7077                    block1 = p.getConnect2().getLayoutBlock();
7078                }
7079            }
7080        } else {
7081            block1 = getAffectedBlock(o.getConnect1(), type);
7082            disconnect(o.getConnect1(), type);
7083        }
7084        type = o.getType2();
7085
7086        if (type == HitPointType.POS_POINT) {
7087            PositionablePoint p = (PositionablePoint) (o.getConnect2());
7088
7089            if (p != null) {
7090                p.removeTrackConnection(o);
7091
7092                if (p.getConnect1() != null) {
7093                    block2 = p.getConnect1().getLayoutBlock();
7094                } else if (p.getConnect2() != null) {
7095                    block2 = p.getConnect2().getLayoutBlock();
7096                }
7097            }
7098        } else {
7099            block2 = getAffectedBlock(o.getConnect2(), type);
7100            disconnect(o.getConnect2(), type);
7101        }
7102
7103        // delete from array
7104        removeLayoutTrack(o);
7105
7106        // update affected blocks
7107        if (block != null) {
7108            // decrement Block use count
7109            block.decrementUse();
7110            getLEAuxTools().setBlockConnectivityChanged();
7111            block.updatePaths();
7112        }
7113
7114        if ((block1 != null) && (block1 != block)) {
7115            block1.updatePaths();
7116        }
7117
7118        if ((block2 != null) && (block2 != block) && (block2 != block1)) {
7119            block2.updatePaths();
7120        }
7121
7122        //
7123        setDirty();
7124        redrawPanel();
7125    }
7126
7127    private void disconnect(@Nonnull LayoutTrack o, HitPointType type) {
7128        switch (type) {
7129            case TURNOUT_A:
7130            case TURNOUT_B:
7131            case TURNOUT_C:
7132            case TURNOUT_D:
7133            case SLIP_A:
7134            case SLIP_B:
7135            case SLIP_C:
7136            case SLIP_D:
7137            case LEVEL_XING_A:
7138            case LEVEL_XING_B:
7139            case LEVEL_XING_C:
7140            case LEVEL_XING_D: {
7141                try {
7142                    o.setConnection(type, null, HitPointType.NONE);
7143                } catch (JmriException e) {
7144                    // ignore (log.error in setConnection method)
7145                }
7146                break;
7147            }
7148
7149            default: {
7150                if (HitPointType.isTurntableRayHitType(type)) {
7151                    ((LayoutTurntable) o).setRayConnect(null, type.turntableTrackIndex());
7152                }
7153                if (HitPointType.isTraverserSlotHitType(type)) {
7154                    ((LayoutTraverser) o).setSlotConnect(null, type.traverserTrackIndex());
7155                }
7156                break;
7157            }
7158        }
7159    }
7160
7161    /**
7162     * Depending on the given type, and the real class of the given LayoutTrack,
7163     * determine the connected LayoutTrack. This provides a variable-indirect
7164     * form of e.g. trk.getLayoutBlockC() for example. Perhaps "Connected Block"
7165     * captures the idea better, but that method name is being used for
7166     * something else.
7167     *
7168     *
7169     * @param track The track who's connected blocks are being examined
7170     * @param type  This point to check for connected blocks, i.e. TURNOUT_B
7171     * @return The block at a particular point on the track object, or null if
7172     *         none.
7173     */
7174    // Temporary - this should certainly be a LayoutTrack method.
7175    public LayoutBlock getAffectedBlock(@Nonnull LayoutTrack track, HitPointType type) {
7176        LayoutBlock result = null;
7177
7178        switch (type) {
7179            case TURNOUT_A:
7180            case SLIP_A: {
7181                if (track instanceof LayoutTurnout) {
7182                    LayoutTurnout lt = (LayoutTurnout) track;
7183                    result = lt.getLayoutBlock();
7184                }
7185                break;
7186            }
7187
7188            case TURNOUT_B:
7189            case SLIP_B: {
7190                if (track instanceof LayoutTurnout) {
7191                    LayoutTurnout lt = (LayoutTurnout) track;
7192                    result = lt.getLayoutBlockB();
7193                }
7194                break;
7195            }
7196
7197            case TURNOUT_C:
7198            case SLIP_C: {
7199                if (track instanceof LayoutTurnout) {
7200                    LayoutTurnout lt = (LayoutTurnout) track;
7201                    result = lt.getLayoutBlockC();
7202                }
7203                break;
7204            }
7205
7206            case TURNOUT_D:
7207            case SLIP_D: {
7208                if (track instanceof LayoutTurnout) {
7209                    LayoutTurnout lt = (LayoutTurnout) track;
7210                    result = lt.getLayoutBlockD();
7211                }
7212                break;
7213            }
7214
7215            case LEVEL_XING_A:
7216            case LEVEL_XING_C: {
7217                if (track instanceof LevelXing) {
7218                    LevelXing lx = (LevelXing) track;
7219                    result = lx.getLayoutBlockAC();
7220                }
7221                break;
7222            }
7223
7224            case LEVEL_XING_B:
7225            case LEVEL_XING_D: {
7226                if (track instanceof LevelXing) {
7227                    LevelXing lx = (LevelXing) track;
7228                    result = lx.getLayoutBlockBD();
7229                }
7230                break;
7231            }
7232
7233            case TRACK: {
7234                if (track instanceof TrackSegment) {
7235                    TrackSegment ts = (TrackSegment) track;
7236                    result = ts.getLayoutBlock();
7237                }
7238                break;
7239            }
7240            default: {
7241                if (HitPointType.isTurntableRayHitType(type) ||
7242                        HitPointType.isTraverserSlotHitType(type)) {
7243                    break;
7244                }
7245                log.warn("Unhandled track type: {}", type);
7246                break;
7247            }
7248        }
7249        return result;
7250    }
7251
7252    /**
7253     * Add a sensor indicator to the Draw Panel
7254     */
7255    void addSensor() {
7256        String newName = leToolBarPanel.sensorComboBox.getSelectedItemDisplayName();
7257        if (newName == null) {
7258            newName = "";
7259        }
7260
7261        if (newName.isEmpty()) {
7262            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"),
7263                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7264            return;
7265        }
7266        SensorIcon l = new SensorIcon(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
7267                "resources/icons/smallschematics/tracksegments/circuit-error.gif"), this);
7268
7269        l.setIcon("SensorStateActive", leToolBarPanel.sensorIconEditor.getIcon(0));
7270        l.setIcon("SensorStateInactive", leToolBarPanel.sensorIconEditor.getIcon(1));
7271        l.setIcon("BeanStateInconsistent", leToolBarPanel.sensorIconEditor.getIcon(2));
7272        l.setIcon("BeanStateUnknown", leToolBarPanel.sensorIconEditor.getIcon(3));
7273        l.setSensor(newName);
7274        l.setDisplayLevel(Editor.SENSORS);
7275
7276        leToolBarPanel.sensorComboBox.setSelectedItem(l.getSensor());
7277        setNextLocation(l);
7278        try {
7279            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7280        } catch (Positionable.DuplicateIdException e) {
7281            // This should never happen
7282            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7283        }
7284    }
7285
7286    public void putSensor(@Nonnull SensorIcon l) {
7287        l.updateSize();
7288        l.setDisplayLevel(Editor.SENSORS);
7289        try {
7290            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7291        } catch (Positionable.DuplicateIdException e) {
7292            // This should never happen
7293            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7294        }
7295    }
7296
7297    /**
7298     * Add a turnout indicator to the Draw Panel
7299     */
7300    void addTurnout() {
7301        String newName = leToolBarPanel.turnoutComboBox.getSelectedItemDisplayName();
7302        if (newName == null) {
7303            newName = "";
7304        }
7305
7306        if (newName.isEmpty()) {
7307            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"),
7308                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7309            return;
7310        }
7311        TurnoutIcon l = new OutputIndicator(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
7312                "resources/icons/smallschematics/tracksegments/circuit-error.gif"), this);
7313
7314        l.setTurnout(newName);
7315
7316        l.setIcon("TurnoutStateThrown", leToolBarPanel.turnoutIconEditor.getIcon(0));
7317        l.setIcon("TurnoutStateClosed", leToolBarPanel.turnoutIconEditor.getIcon(1));
7318        l.setIcon("BeanStateInconsistent", leToolBarPanel.turnoutIconEditor.getIcon(2));
7319        l.setIcon("BeanStateUnknown", leToolBarPanel.turnoutIconEditor.getIcon(3));
7320        l.setDisplayLevel(Editor.TURNOUTS);
7321
7322        leToolBarPanel.turnoutComboBox.setSelectedItem(l.getTurnout());
7323        setNextLocation(l);
7324        try {
7325            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7326        } catch (Positionable.DuplicateIdException e) {
7327            // This should never happen
7328            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7329        }
7330    }
7331
7332    public void putTurnout(@Nonnull TurnoutIcon l) {
7333        l.updateSize();
7334        l.setDisplayLevel(Editor.TURNOUTS);
7335        try {
7336            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7337        } catch (Positionable.DuplicateIdException e) {
7338            // This should never happen
7339            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7340        }
7341    }
7342
7343    /**
7344     * Add a signal head to the Panel
7345     */
7346    void addSignalHead() {
7347        // check for valid signal head entry
7348        String newName = leToolBarPanel.signalHeadComboBox.getSelectedItemDisplayName();
7349        if (newName == null) {
7350            newName = "";
7351        }
7352        SignalHead mHead = null;
7353
7354        if (!newName.isEmpty()) {
7355            mHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(newName);
7356
7357            /*if (mHead == null)
7358            mHead = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(newName);
7359            else */
7360            leToolBarPanel.signalHeadComboBox.setSelectedItem(mHead);
7361        }
7362
7363        if (mHead == null) {
7364            // There is no signal head corresponding to this name
7365            JmriJOptionPane.showMessageDialog(this,
7366                    MessageFormat.format(Bundle.getMessage("Error9"), newName),
7367                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7368            return;
7369        }
7370
7371        // create and set up signal icon
7372        SignalHeadIcon l = new SignalHeadIcon(this);
7373        l.setSignalHead(newName);
7374        l.setIcon("SignalHeadStateRed", leToolBarPanel.signalIconEditor.getIcon(0));
7375        l.setIcon("SignalHeadStateFlashingRed", leToolBarPanel.signalIconEditor.getIcon(1));
7376        l.setIcon("SignalHeadStateYellow", leToolBarPanel.signalIconEditor.getIcon(2));
7377        l.setIcon("SignalHeadStateFlashingYellow", leToolBarPanel.signalIconEditor.getIcon(3));
7378        l.setIcon("SignalHeadStateGreen", leToolBarPanel.signalIconEditor.getIcon(4));
7379        l.setIcon("SignalHeadStateFlashingGreen", leToolBarPanel.signalIconEditor.getIcon(5));
7380        l.setIcon("SignalHeadStateDark", leToolBarPanel.signalIconEditor.getIcon(6));
7381        l.setIcon("SignalHeadStateHeld", leToolBarPanel.signalIconEditor.getIcon(7));
7382        l.setIcon("SignalHeadStateLunar", leToolBarPanel.signalIconEditor.getIcon(8));
7383        l.setIcon("SignalHeadStateFlashingLunar", leToolBarPanel.signalIconEditor.getIcon(9));
7384        unionToPanelBounds(l.getBounds());
7385        setNextLocation(l);
7386        setDirty();
7387        putSignal(l);
7388    }
7389
7390    public void putSignal(@Nonnull SignalHeadIcon l) {
7391        l.updateSize();
7392        l.setDisplayLevel(Editor.SIGNALS);
7393        try {
7394            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7395        } catch (Positionable.DuplicateIdException e) {
7396            // This should never happen
7397            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7398        }
7399    }
7400
7401    @CheckForNull
7402    SignalHead getSignalHead(@Nonnull String name) {
7403        SignalHead sh = InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(name);
7404
7405        if (sh == null) {
7406            sh = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(name);
7407        }
7408
7409        if (sh == null) {
7410            log.warn("did not find a SignalHead named {}", name);
7411        }
7412        return sh;
7413    }
7414
7415    public boolean containsSignalHead(@CheckForNull SignalHead head) {
7416        if (head != null) {
7417            for (SignalHeadIcon h : signalList) {
7418                if (h.getSignalHead() == head) {
7419                    return true;
7420                }
7421            }
7422        }
7423        return false;
7424    }
7425
7426    public void removeSignalHead(@CheckForNull SignalHead head) {
7427        if (head != null) {
7428            for (SignalHeadIcon h : signalList) {
7429                if (h.getSignalHead() == head) {
7430                    signalList.remove(h);
7431                    h.remove();
7432                    h.dispose();
7433                    setDirty();
7434                    redrawPanel();
7435                    break;
7436                }
7437            }
7438        }
7439    }
7440
7441    void addSignalMast() {
7442        // check for valid signal head entry
7443        String newName = leToolBarPanel.signalMastComboBox.getSelectedItemDisplayName();
7444        if (newName == null) {
7445            newName = "";
7446        }
7447        SignalMast mMast = null;
7448
7449        if (!newName.isEmpty()) {
7450            mMast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(newName);
7451            leToolBarPanel.signalMastComboBox.setSelectedItem(mMast);
7452        }
7453
7454        if (mMast == null) {
7455            // There is no signal head corresponding to this name
7456            JmriJOptionPane.showMessageDialog(this,
7457                    MessageFormat.format(Bundle.getMessage("Error9"), newName),
7458                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7459
7460            return;
7461        }
7462
7463        // create and set up signal icon
7464        SignalMastIcon l = new SignalMastIcon(this);
7465        l.setSignalMast(newName);
7466        unionToPanelBounds(l.getBounds());
7467        setNextLocation(l);
7468        setDirty();
7469        putSignalMast(l);
7470    }
7471
7472    public void putSignalMast(@Nonnull SignalMastIcon l) {
7473        l.updateSize();
7474        l.setDisplayLevel(Editor.SIGNALS);
7475        try {
7476            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7477        } catch (Positionable.DuplicateIdException e) {
7478            // This should never happen
7479            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7480        }
7481    }
7482
7483    SignalMast getSignalMast(@Nonnull String name) {
7484        SignalMast sh = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name);
7485
7486        if (sh == null) {
7487            sh = InstanceManager.getDefault(SignalMastManager.class).getByUserName(name);
7488        }
7489
7490        if (sh == null) {
7491            log.warn("did not find a SignalMast named {}", name);
7492        }
7493        return sh;
7494    }
7495
7496    public boolean containsSignalMast(@Nonnull SignalMast mast) {
7497        for (SignalMastIcon h : signalMastList) {
7498            if (h.getSignalMast() == mast) {
7499                return true;
7500            }
7501        }
7502        return false;
7503    }
7504
7505    /**
7506     * Add a label to the Draw Panel
7507     */
7508    void addLabel() {
7509        String labelText = leToolBarPanel.textLabelTextField.getText();
7510        labelText = (labelText != null) ? labelText.trim() : "";
7511
7512        if (labelText.isEmpty()) {
7513            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11"),
7514                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7515            return;
7516        }
7517        PositionableLabel l = super.addLabel(labelText);
7518        unionToPanelBounds(l.getBounds());
7519        setDirty();
7520        l.setForeground(defaultTextColor);
7521    }
7522
7523    @Override
7524    public void putItem(@Nonnull Positionable l) throws Positionable.DuplicateIdException {
7525        putItem(l, false);
7526    }
7527
7528    @Override
7529    public void putItem(@Nonnull Positionable l, boolean factoryPositionable)
7530            throws Positionable.DuplicateIdException {
7531
7532        super.putItem(l, factoryPositionable);
7533
7534        if (l instanceof SensorIcon) {
7535            sensorImage.add((SensorIcon) l);
7536            sensorList.add((SensorIcon) l);
7537        } else if (l instanceof TurnoutIcon) {
7538            turnoutImage.add((TurnoutIcon) l);
7539            turnoutList.add((TurnoutIcon) l);
7540        } else if (l instanceof LocoIcon) {
7541            markerImage.add((LocoIcon) l);
7542        } else if (l instanceof SignalHeadIcon) {
7543            signalHeadImage.add((SignalHeadIcon) l);
7544            signalList.add((SignalHeadIcon) l);
7545        } else if (l instanceof SignalMastIcon) {
7546            signalMastList.add((SignalMastIcon) l);
7547        } else if (l instanceof BlockContentsIcon) {
7548            blockContentsLabelList.add((BlockContentsIcon) l);
7549        } else if (l instanceof MemoryIcon) {
7550            memoryLabelList.add((MemoryIcon) l);
7551        } else if (l instanceof BlockContentsInputIcon) {
7552            blockContentsInputList.add((BlockContentsInputIcon) l);
7553        } else if (l instanceof MemoryInputIcon) {
7554            memoryInputList.add((MemoryInputIcon) l);
7555        } else if (l instanceof GlobalVariableIcon) {
7556            globalVariableLabelList.add((GlobalVariableIcon) l);
7557        } else if (l instanceof AnalogClock2Display) {
7558            clocks.add((AnalogClock2Display) l);
7559        } else if (l instanceof MultiSensorIcon) {
7560            multiSensors.add((MultiSensorIcon) l);
7561        } else if (factoryPositionable) {
7562            factoryPositionables.add(l);
7563        }
7564
7565        if (l instanceof PositionableLabel) {
7566            if (((PositionableLabel) l).isBackground()) {
7567                backgroundImage.add((PositionableLabel) l);
7568            } else {
7569                labelImage.add((PositionableLabel) l);
7570            }
7571        }
7572        unionToPanelBounds(l.getBounds(new Rectangle()));
7573        setDirty();
7574    }
7575
7576    /**
7577     * When adding a memory variable, provide an option to create the normal label
7578     * or create an input text field.  The label requires a pop-up dialog to change the value
7579     * while the text field makes it possible to change the value on the panel.  This also makes
7580     * it possible to change the value using the web server.
7581     */
7582    void selectMemoryType() {
7583        int response = JmriJOptionPane.showConfirmDialog(null,
7584            Bundle.getMessage("MemorySelectType"),
7585            Bundle.getMessage("MemorySelectTitle"),
7586            JmriJOptionPane.YES_NO_OPTION);
7587
7588        if (response == JmriJOptionPane.YES_OPTION) {
7589            addMemory();
7590            return;
7591        }
7592
7593        var length = JmriJOptionPane.showInputDialog(null,
7594            Bundle.getMessage("MemorySelectSize"),
7595            "5");
7596
7597        int textLength;
7598        try {
7599           textLength = Integer.parseInt(length);
7600        }
7601        catch (NumberFormatException e) {
7602           textLength = 5;
7603        }
7604
7605        addInputMemory(textLength);
7606    }
7607
7608    /**
7609     * Add a memory label to the Draw Panel
7610     */
7611    void addMemory() {
7612        String memoryName = leToolBarPanel.textMemoryComboBox.getSelectedItemDisplayName();
7613        if (memoryName == null) {
7614            memoryName = "";
7615        }
7616
7617        if (memoryName.isEmpty()) {
7618            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11a"),
7619                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7620            return;
7621        }
7622        MemoryIcon l = new MemoryIcon(" ", this);
7623        l.setMemory(memoryName);
7624        setNextLocation(l);
7625        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7626        l.setDisplayLevel(Editor.LABELS);
7627        l.setForeground(defaultTextColor);
7628        unionToPanelBounds(l.getBounds());
7629        try {
7630            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7631        } catch (Positionable.DuplicateIdException e) {
7632            // This should never happen
7633            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7634        }
7635    }
7636
7637    void addInputMemory(int textFieldLength) {
7638        String memoryName = leToolBarPanel.textMemoryComboBox.getSelectedItemDisplayName();
7639        if (memoryName == null) {
7640            memoryName = "";
7641        }
7642
7643        if (memoryName.isEmpty()) {
7644            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11a"),
7645                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7646            return;
7647        }
7648
7649        MemoryInputIcon l = new MemoryInputIcon(textFieldLength, this);
7650        l.setMemory(memoryName);
7651        setNextLocation(l);
7652        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7653        l.setDisplayLevel(Editor.MEMORIES);
7654        l.setForeground(defaultTextColor);
7655        unionToPanelBounds(l.getBounds());
7656        try {
7657            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7658        } catch (Positionable.DuplicateIdException e) {
7659            // This should never happen
7660            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7661        }
7662    }
7663
7664
7665    void addGlobalVariable() {
7666        String globalVariableName = leToolBarPanel.textGlobalVariableComboBox.getSelectedItemDisplayName();
7667        if (globalVariableName == null) {
7668            globalVariableName = "";
7669        }
7670
7671        if (globalVariableName.isEmpty()) {
7672            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11c"),
7673                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7674            return;
7675        }
7676        GlobalVariableIcon l = new GlobalVariableIcon(" ", this);
7677        l.setGlobalVariable(globalVariableName);
7678        GlobalVariable xGlobalVariable = l.getGlobalVariable();
7679
7680        if (xGlobalVariable != null) {
7681            String uname = xGlobalVariable.getDisplayName();
7682            if (!uname.equals(globalVariableName)) {
7683                // put the system name in the memory field
7684                leToolBarPanel.textGlobalVariableComboBox.setSelectedItem(xGlobalVariable);
7685            }
7686        }
7687        setNextLocation(l);
7688        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7689        l.setDisplayLevel(Editor.LABELS);
7690        l.setForeground(defaultTextColor);
7691        unionToPanelBounds(l.getBounds());
7692        try {
7693            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7694        } catch (Positionable.DuplicateIdException e) {
7695            // This should never happen
7696            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7697        }
7698    }
7699
7700    /**
7701     * When adding a blopck contents object, provide an option to create the normal label
7702     * or create an input text field.  The label requires a pop-up dialog to change the value
7703     * while the text field makes it possible to change the value on the panel.  This also makes
7704     * it possible to change the value using the web server.
7705     */
7706    void selectBlockContentsType() {
7707        int response = JmriJOptionPane.showConfirmDialog(null,
7708            Bundle.getMessage("BlockSelectType"),
7709            Bundle.getMessage("BlockSelectTitle"),
7710            JmriJOptionPane.YES_NO_OPTION);
7711
7712        if (response == JmriJOptionPane.YES_OPTION) {
7713            addBlockContents();
7714            return;
7715        }
7716
7717        var length = JmriJOptionPane.showInputDialog(null,
7718            Bundle.getMessage("BlockSelectSize"),
7719            "5");
7720
7721        int textLength;
7722        try {
7723           textLength = Integer.parseInt(length);
7724        }
7725        catch (NumberFormatException e) {
7726           textLength = 5;
7727        }
7728
7729        addInputBlockContents(textLength);
7730    }
7731
7732    void addBlockContents() {
7733        String newName = leToolBarPanel.blockContentsComboBox.getSelectedItemDisplayName();
7734        if (newName == null) {
7735            newName = "";
7736        }
7737
7738        if (newName.isEmpty()) {
7739            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11b"),
7740                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7741            return;
7742        }
7743        BlockContentsIcon l = new BlockContentsIcon(" ", this);
7744        l.setBlock(newName);
7745        Block xMemory = l.getBlock();
7746
7747        if (xMemory != null) {
7748            String uname = xMemory.getDisplayName();
7749            if (!uname.equals(newName)) {
7750                // put the system name in the memory field
7751                leToolBarPanel.blockContentsComboBox.setSelectedItem(xMemory);
7752            }
7753        }
7754        setNextLocation(l);
7755        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7756        l.setDisplayLevel(Editor.LABELS);
7757        l.setForeground(defaultTextColor);
7758        try {
7759            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7760        } catch (Positionable.DuplicateIdException e) {
7761            // This should never happen
7762            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7763        }
7764    }
7765
7766    void addInputBlockContents(int textFieldLength) {
7767        String newName = leToolBarPanel.blockContentsComboBox.getSelectedItemDisplayName();
7768        if (newName == null) {
7769            newName = "";
7770        }
7771
7772        if (newName.isEmpty()) {
7773            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11b"),
7774                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7775            return;
7776        }
7777        BlockContentsInputIcon l = new BlockContentsInputIcon(textFieldLength, this);
7778        l.setBlock(newName);
7779        setNextLocation(l);
7780        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7781        l.setDisplayLevel(Editor.MEMORIES);
7782        l.setForeground(defaultTextColor);
7783        try {
7784            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7785        } catch (Positionable.DuplicateIdException e) {
7786            // This should never happen
7787            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7788        }
7789    }
7790
7791    /**
7792     * Add a Reporter Icon to the panel.
7793     *
7794     * @param reporter the reporter icon to add.
7795     * @param xx       the horizontal location.
7796     * @param yy       the vertical location.
7797     */
7798    public void addReporter(@Nonnull Reporter reporter, int xx, int yy) {
7799        ReporterIcon l = new ReporterIcon(this);
7800        l.setReporter(reporter);
7801        l.setLocation(xx, yy);
7802        l.setSize(l.getPreferredSize().width, l.getPreferredSize().height);
7803        l.setDisplayLevel(Editor.LABELS);
7804        unionToPanelBounds(l.getBounds());
7805        try {
7806            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7807        } catch (Positionable.DuplicateIdException e) {
7808            // This should never happen
7809            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7810        }
7811    }
7812
7813    /**
7814     * Add an icon to the target
7815     */
7816    void addIcon() {
7817        PositionableLabel l = new PositionableLabel(leToolBarPanel.iconEditor.getIcon(0), this);
7818        setNextLocation(l);
7819        l.setDisplayLevel(Editor.ICONS);
7820        unionToPanelBounds(l.getBounds());
7821        l.updateSize();
7822        try {
7823            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7824        } catch (Positionable.DuplicateIdException e) {
7825            // This should never happen
7826            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7827        }
7828    }
7829
7830    /**
7831     * Add a LogixNG icon to the target
7832     */
7833    void addLogixNGIcon() {
7834        LogixNGIcon l = new LogixNGIcon(leToolBarPanel.logixngEditor.getIcon(0), this);
7835        setNextLocation(l);
7836        l.setDisplayLevel(Editor.ICONS);
7837        unionToPanelBounds(l.getBounds());
7838        l.updateSize();
7839        try {
7840            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7841        } catch (Positionable.DuplicateIdException e) {
7842            // This should never happen
7843            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7844        }
7845    }
7846
7847    /**
7848     * Add a LogixNG icon to the target
7849     */
7850    void addAudioIcon() {
7851        String audioName = leToolBarPanel.textAudioComboBox.getSelectedItemDisplayName();
7852        if (audioName == null) {
7853            audioName = "";
7854        }
7855
7856        if (audioName.isEmpty()) {
7857            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11d"),
7858                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
7859            return;
7860        }
7861
7862        AudioIcon l = new AudioIcon(leToolBarPanel.audioEditor.getIcon(0), this);
7863        l.setAudio(audioName);
7864        Audio xAudio = l.getAudio();
7865
7866        if (xAudio != null) {
7867            String uname = xAudio.getDisplayName();
7868            if (!uname.equals(audioName)) {
7869                // put the system name in the memory field
7870                leToolBarPanel.textAudioComboBox.setSelectedItem(xAudio);
7871            }
7872        }
7873
7874        setNextLocation(l);
7875        l.setDisplayLevel(Editor.ICONS);
7876        unionToPanelBounds(l.getBounds());
7877        l.updateSize();
7878        try {
7879            putItem(l); // note: this calls unionToPanelBounds & setDirty()
7880        } catch (Positionable.DuplicateIdException e) {
7881            // This should never happen
7882            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
7883        }
7884    }
7885
7886    /**
7887     * Add a loco marker to the target
7888     */
7889    @Override
7890    public LocoIcon addLocoIcon(@Nonnull String name) {
7891        LocoIcon l = new LocoIcon(this);
7892        Point2D pt = windowCenter();
7893        if (selectionActive) {
7894            pt = MathUtil.midPoint(getSelectionRect());
7895        }
7896        l.setLocation((int) pt.getX(), (int) pt.getY());
7897        putLocoIcon(l, name);
7898        l.setPositionable(true);
7899        unionToPanelBounds(l.getBounds());
7900        return l;
7901    }
7902
7903    @Override
7904    public void putLocoIcon(@Nonnull LocoIcon l, @Nonnull String name) {
7905        super.putLocoIcon(l, name);
7906        markerImage.add(l);
7907        unionToPanelBounds(l.getBounds());
7908    }
7909
7910    private JFileChooser inputFileChooser = null;
7911
7912    /**
7913     * Add a background image
7914     */
7915    public void addBackground() {
7916        if (inputFileChooser == null) {
7917            inputFileChooser = new jmri.util.swing.JmriJFileChooser(
7918                    String.format("%s%sresources%sicons",
7919                            System.getProperty("user.dir"),
7920                            File.separator,
7921                            File.separator));
7922
7923            inputFileChooser.setFileFilter(new FileNameExtensionFilter("Graphics Files", "gif", "jpg", "png"));
7924        }
7925        inputFileChooser.rescanCurrentDirectory();
7926
7927        int retVal = inputFileChooser.showOpenDialog(this);
7928
7929        if (retVal != JFileChooser.APPROVE_OPTION) {
7930            return; // give up if no file selected
7931        }
7932
7933        // NamedIcon icon = new NamedIcon(inputFileChooser.getSelectedFile().getPath(),
7934        // inputFileChooser.getSelectedFile().getPath());
7935        String name = inputFileChooser.getSelectedFile().getPath();
7936
7937        // convert to portable path
7938        name = FileUtil.getPortableFilename(name);
7939
7940        // setup icon
7941        PositionableLabel o = super.setUpBackground(name);
7942        backgroundImage.add(o);
7943        unionToPanelBounds(o.getBounds());
7944        setDirty();
7945    }
7946
7947    // there is no way to call this; could that
7948    //    private boolean remove(@Nonnull Object s)
7949    // is being used instead.
7950    //
7951    ///**
7952    // * Remove a background image from the list of background images
7953    // *
7954    // * @param b PositionableLabel to remove
7955    // */
7956    //private void removeBackground(@Nonnull PositionableLabel b) {
7957    //    if (backgroundImage.contains(b)) {
7958    //        backgroundImage.remove(b);
7959    //        setDirty();
7960    //    }
7961    //}
7962    /**
7963     * add a layout shape to the list of layout shapes
7964     *
7965     * @param p Point2D where the shape should be
7966     * @return the LayoutShape
7967     */
7968    @Nonnull
7969    private LayoutShape addLayoutShape(@Nonnull Point2D p) {
7970        // get unique name
7971        String name = finder.uniqueName("S", getLayoutShapes().size() + 1);
7972
7973        // create object
7974        LayoutShape o = new LayoutShape(name, p, this);
7975        layoutShapes.add(o);
7976        unionToPanelBounds(o.getBounds());
7977        setDirty();
7978        return o;
7979    }
7980
7981    /**
7982     * Remove a layout shape from the list of layout shapes
7983     *
7984     * @param s the LayoutShape to add
7985     * @return true if added
7986     */
7987    public boolean removeLayoutShape(@Nonnull LayoutShape s) {
7988        boolean result = false;
7989        if (layoutShapes.contains(s)) {
7990            layoutShapes.remove(s);
7991            setDirty();
7992            result = true;
7993            redrawPanel();
7994        }
7995        return result;
7996    }
7997
7998    /**
7999     * Invoke a window to allow you to add a MultiSensor indicator to the target
8000     */
8001    private int multiLocX;
8002    private int multiLocY;
8003
8004    void startMultiSensor() {
8005        multiLocX = xLoc;
8006        multiLocY = yLoc;
8007
8008        if (leToolBarPanel.multiSensorFrame == null) {
8009            // create a common edit frame
8010            leToolBarPanel.multiSensorFrame = new MultiSensorIconFrame(this);
8011            leToolBarPanel.multiSensorFrame.initComponents();
8012            leToolBarPanel.multiSensorFrame.pack();
8013        }
8014        leToolBarPanel.multiSensorFrame.setVisible(true);
8015    }
8016
8017    // Invoked when window has new multi-sensor ready
8018    public void addMultiSensor(@Nonnull MultiSensorIcon l) {
8019        l.setLocation(multiLocX, multiLocY);
8020        try {
8021            putItem(l); // note: this calls unionToPanelBounds & setDirty()
8022        } catch (Positionable.DuplicateIdException e) {
8023            // This should never happen
8024            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
8025        }
8026        leToolBarPanel.multiSensorFrame.dispose();
8027        leToolBarPanel.multiSensorFrame = null;
8028    }
8029
8030    /**
8031     * Set object location and size for icon and label object as it is created.
8032     * Size comes from the preferredSize; location comes from the fields where
8033     * the user can spec it.
8034     *
8035     * @param obj the positionable object.
8036     */
8037    @Override
8038    public void setNextLocation(@Nonnull Positionable obj) {
8039        obj.setLocation(xLoc, yLoc);
8040    }
8041
8042    //
8043    // singleton (one per-LayoutEditor) accessors
8044    //
8045    private ConnectivityUtil conTools = null;
8046
8047    @Nonnull
8048    public ConnectivityUtil getConnectivityUtil() {
8049        if (conTools == null) {
8050            conTools = new ConnectivityUtil(this);
8051        }
8052        return conTools;
8053    }
8054
8055    private LayoutEditorTools tools = null;
8056
8057    @Nonnull
8058    public LayoutEditorTools getLETools() {
8059        if (tools == null) {
8060            tools = new LayoutEditorTools(this);
8061        }
8062        return tools;
8063    }
8064
8065    private LayoutEditorAuxTools auxTools = null;
8066
8067    @Override
8068    @Nonnull
8069    public LayoutEditorAuxTools getLEAuxTools() {
8070        if (auxTools == null) {
8071            auxTools = new LayoutEditorAuxTools(this);
8072        }
8073        return auxTools;
8074    }
8075
8076    private LayoutEditorChecks layoutEditorChecks = null;
8077
8078    @Nonnull
8079    public LayoutEditorChecks getLEChecks() {
8080        if (layoutEditorChecks == null) {
8081            layoutEditorChecks = new LayoutEditorChecks(this);
8082        }
8083        return layoutEditorChecks;
8084    }
8085
8086    /**
8087     * Invoked by DeletePanel menu item Validate user intent before deleting
8088     */
8089    @Override
8090    public boolean deletePanel() {
8091        if (canDeletePanel()) {
8092            // verify deletion
8093            if (!super.deletePanel()) {
8094                return false; // return without deleting if "No" response
8095            }
8096            clearLayoutTracks();
8097            return true;
8098        }
8099        return false;
8100    }
8101
8102    /**
8103     * Check for conditions that prevent a delete.
8104     * <ul>
8105     * <li>The panel has active edge connector links</li>
8106     * <li>The panel is used by EntryExit</li>
8107     * </ul>
8108     * @return true if ok to delete
8109     */
8110    public boolean canDeletePanel() {
8111        var messages = new ArrayList<String>();
8112
8113        var points = getPositionablePoints();
8114        for (PositionablePoint point : points) {
8115            if (point.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) {
8116                var panelName = point.getLinkedEditorName();
8117                if (!panelName.isEmpty()) {
8118                    messages.add(Bundle.getMessage("ActiveEdgeConnector", point.getId(), point.getLinkedEditorName()));
8119                }
8120            }
8121        }
8122
8123        var entryExitPairs = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class);
8124        if (!entryExitPairs.getNxSource(this).isEmpty()) {
8125            messages.add(Bundle.getMessage("ActiveEntryExit"));
8126        }
8127
8128        if (!messages.isEmpty()) {
8129            StringBuilder msg = new StringBuilder(Bundle.getMessage("PanelRelationshipsError"));
8130            for (String message : messages) {
8131                msg.append(message);
8132            }
8133            JmriJOptionPane.showMessageDialog(null,
8134                    msg.toString(),
8135                    Bundle.getMessage("ErrorTitle"), // NOI18N
8136                    JmriJOptionPane.ERROR_MESSAGE);
8137        }
8138
8139        return messages.isEmpty();
8140    }
8141
8142    /**
8143     * Control whether target panel items are editable. Does this by invoking
8144     * the {@link Editor#setAllEditable} function of the parent class. This also
8145     * controls the relevant pop-up menu items (which are the primary way that
8146     * items are edited).
8147     *
8148     * @param editable true for editable.
8149     */
8150    @Override
8151    public void setAllEditable(boolean editable) {
8152        int restoreScroll = _scrollState;
8153
8154        super.setAllEditable(editable);
8155
8156        if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
8157            if (editable) {
8158                createfloatingEditToolBoxFrame();
8159                createFloatingHelpPanel();
8160            } else {
8161                deletefloatingEditToolBoxFrame();
8162            }
8163        } else {
8164            editToolBarContainerPanel.setVisible(editable);
8165        }
8166        setShowHidden(editable);
8167
8168        if (editable) {
8169            setScroll(Editor.SCROLL_BOTH);
8170            _scrollState = restoreScroll;
8171        } else {
8172            setScroll(_scrollState);
8173        }
8174
8175        // these may not be set up yet...
8176        if (helpBarPanel != null) {
8177            if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
8178                if (floatEditHelpPanel != null) {
8179                    floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar());
8180                }
8181            } else {
8182                helpBarPanel.setVisible(editable && getShowHelpBar());
8183            }
8184        }
8185        awaitingIconChange = false;
8186        editModeCheckBoxMenuItem.setSelected(editable);
8187        redrawPanel();
8188    }
8189
8190    /**
8191     * Control whether panel items are positionable. Markers are always
8192     * positionable.
8193     *
8194     * @param state true for positionable.
8195     */
8196    @Override
8197    public void setAllPositionable(boolean state) {
8198        super.setAllPositionable(state);
8199
8200        markerImage.forEach((p) -> p.setPositionable(true));
8201    }
8202
8203    /**
8204     * Control whether target panel items are controlling layout items. Does
8205     * this by invoke the {@link Positionable#setControlling} function of each
8206     * item on the target panel. This also controls the relevant pop-up menu
8207     * items.
8208     *
8209     * @param state true for controlling.
8210     */
8211    public void setTurnoutAnimation(boolean state) {
8212        if (animationCheckBoxMenuItem.isSelected() != state) {
8213            animationCheckBoxMenuItem.setSelected(state);
8214        }
8215
8216        if (animatingLayout != state) {
8217            animatingLayout = state;
8218            redrawPanel();
8219        }
8220    }
8221
8222    public boolean isAnimating() {
8223        return animatingLayout;
8224    }
8225
8226    public boolean getScroll() {
8227        // deprecated but kept to allow opening files
8228        // on version 2.5.1 and earlier
8229        return _scrollState != Editor.SCROLL_NONE;
8230    }
8231
8232//    public Color getDefaultBackgroundColor() {
8233//        return defaultBackgroundColor;
8234//    }
8235    public String getDefaultTrackColor() {
8236        return ColorUtil.colorToColorName(defaultTrackColor);
8237    }
8238
8239    /**
8240     *
8241     * Getter defaultTrackColor.
8242     *
8243     * @return block default color as Color
8244     */
8245    @Nonnull
8246    public Color getDefaultTrackColorColor() {
8247        return defaultTrackColor;
8248    }
8249
8250    @Nonnull
8251    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8252    public String getDefaultOccupiedTrackColor() {
8253        return ColorUtil.colorToColorName(defaultOccupiedTrackColor);
8254    }
8255
8256    /**
8257     *
8258     * Getter defaultOccupiedTrackColor.
8259     *
8260     * @return block default occupied color as Color
8261     */
8262    @Nonnull
8263    public Color getDefaultOccupiedTrackColorColor() {
8264        return defaultOccupiedTrackColor;
8265    }
8266
8267    @Nonnull
8268    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8269    public String getDefaultAlternativeTrackColor() {
8270        return ColorUtil.colorToColorName(defaultAlternativeTrackColor);
8271    }
8272
8273    /**
8274     *
8275     * Getter defaultAlternativeTrackColor.
8276     *
8277     * @return block default alternative color as Color
8278     */
8279    @Nonnull
8280    public Color getDefaultAlternativeTrackColorColor() {
8281        return defaultAlternativeTrackColor;
8282    }
8283
8284    @Nonnull
8285    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8286    public String getDefaultTextColor() {
8287        return ColorUtil.colorToColorName(defaultTextColor);
8288    }
8289
8290    @Nonnull
8291    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8292    public String getTurnoutCircleColor() {
8293        return ColorUtil.colorToColorName(turnoutCircleColor);
8294    }
8295
8296    @Nonnull
8297    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it")
8298    public String getTurnoutCircleThrownColor() {
8299        return ColorUtil.colorToColorName(turnoutCircleThrownColor);
8300    }
8301
8302    public boolean isTurnoutFillControlCircles() {
8303        return turnoutFillControlCircles;
8304    }
8305
8306    public int getTurnoutCircleSize() {
8307        return turnoutCircleSize;
8308    }
8309
8310    public boolean isTurnoutDrawUnselectedLeg() {
8311        return turnoutDrawUnselectedLeg;
8312    }
8313
8314    public boolean isHighlightCursor() {
8315        return highlightCursor;
8316    }
8317
8318    public String getLayoutName() {
8319        return layoutName;
8320    }
8321
8322    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8323    public boolean getShowHelpBar() {
8324        return showHelpBar;
8325    }
8326
8327    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8328    public boolean getDrawGrid() {
8329        return drawGrid;
8330    }
8331
8332    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8333    public boolean getSnapOnAdd() {
8334        return snapToGridOnAdd;
8335    }
8336
8337    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8338    public boolean getSnapOnMove() {
8339        return snapToGridOnMove;
8340    }
8341
8342    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8343    public boolean getAntialiasingOn() {
8344        return antialiasingOn;
8345    }
8346
8347    public boolean isDrawLayoutTracksLabel() {
8348        return drawLayoutTracksLabel;
8349    }
8350
8351    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8352    public boolean getHighlightSelectedBlock() {
8353        return highlightSelectedBlockFlag;
8354    }
8355
8356    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8357    public boolean getTurnoutCircles() {
8358        return turnoutCirclesWithoutEditMode;
8359    }
8360
8361    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8362    public boolean getTooltipsNotEdit() {
8363        return tooltipsWithoutEditMode;
8364    }
8365
8366    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8367    public boolean getTooltipsInEdit() {
8368        return tooltipsInEditMode;
8369    }
8370
8371    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8372    public boolean getAutoBlockAssignment() {
8373        return autoAssignBlocks;
8374    }
8375
8376    public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight) {
8377        setLayoutDimensions(windowWidth, windowHeight, windowX, windowY, panelWidth, panelHeight, false);
8378    }
8379
8380    public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight, boolean merge) {
8381
8382        gContext.setUpperLeftX(windowX);
8383        gContext.setUpperLeftY(windowY);
8384        setLocation(gContext.getUpperLeftX(), gContext.getUpperLeftY());
8385
8386        gContext.setWindowWidth(windowWidth);
8387        gContext.setWindowHeight(windowHeight);
8388        setSize(windowWidth, windowHeight);
8389
8390        Rectangle2D panelBounds = new Rectangle2D.Double(0.0, 0.0, panelWidth, panelHeight);
8391
8392        if (merge) {
8393            panelBounds.add(calculateMinimumLayoutBounds());
8394        }
8395        setPanelBounds(panelBounds);
8396    }
8397
8398    @Nonnull
8399    public Rectangle2D getPanelBounds() {
8400        return new Rectangle2D.Double(0.0, 0.0, gContext.getLayoutWidth(), gContext.getLayoutHeight());
8401    }
8402
8403    public void setPanelBounds(@Nonnull Rectangle2D newBounds) {
8404        // don't let origin go negative
8405        newBounds = newBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8406
8407        if (!getPanelBounds().equals(newBounds)) {
8408            gContext.setLayoutWidth((int) newBounds.getWidth());
8409            gContext.setLayoutHeight((int) newBounds.getHeight());
8410            resetTargetSize();
8411        }
8412        log.debug("setPanelBounds(({})", newBounds);
8413    }
8414
8415    private void resetTargetSize() {
8416        int newTargetWidth = (int) (gContext.getLayoutWidth() * getZoom());
8417        int newTargetHeight = (int) (gContext.getLayoutHeight() * getZoom());
8418
8419        Dimension targetPanelSize = getTargetPanelSize();
8420        int oldTargetWidth = (int) targetPanelSize.getWidth();
8421        int oldTargetHeight = (int) targetPanelSize.getHeight();
8422
8423        if ((newTargetWidth != oldTargetWidth) || (newTargetHeight != oldTargetHeight)) {
8424            setTargetPanelSize(newTargetWidth, newTargetHeight);
8425            adjustScrollBars();
8426        }
8427    }
8428
8429    // this will grow the panel bounds based on items added to the layout
8430    @Nonnull
8431    public Rectangle2D unionToPanelBounds(@Nonnull Rectangle2D bounds) {
8432        Rectangle2D result = getPanelBounds();
8433
8434        // make room to expand
8435        Rectangle2D b = MathUtil.inset(bounds, gContext.getGridSize() * gContext.getGridSize2nd() / -2.0);
8436
8437        // don't let origin go negative
8438        b = b.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8439
8440        result.add(b);
8441
8442        setPanelBounds(result);
8443        return result;
8444    }
8445
8446    /**
8447     * @param color value to set the default track color to.
8448     */
8449    public void setDefaultTrackColor(@Nonnull Color color) {
8450        defaultTrackColor = color;
8451        JmriColorChooser.addRecentColor(color);
8452    }
8453
8454    /**
8455     * @param color value to set the default occupied track color to.
8456     */
8457    public void setDefaultOccupiedTrackColor(@Nonnull Color color) {
8458        defaultOccupiedTrackColor = color;
8459        JmriColorChooser.addRecentColor(color);
8460    }
8461
8462    /**
8463     * @param color value to set the default alternate track color to.
8464     */
8465    public void setDefaultAlternativeTrackColor(@Nonnull Color color) {
8466        defaultAlternativeTrackColor = color;
8467        JmriColorChooser.addRecentColor(color);
8468    }
8469
8470    /**
8471     * @param color new color for turnout circle.
8472     */
8473    public void setTurnoutCircleColor(@CheckForNull Color color) {
8474        if (color == null) {
8475            turnoutCircleColor = getDefaultTrackColorColor();
8476        } else {
8477            turnoutCircleColor = color;
8478            JmriColorChooser.addRecentColor(color);
8479        }
8480    }
8481
8482    /**
8483     * @param color new color for turnout circle.
8484     */
8485    public void setTurnoutCircleThrownColor(@CheckForNull Color color) {
8486        if (color == null) {
8487            turnoutCircleThrownColor = getDefaultTrackColorColor();
8488        } else {
8489            turnoutCircleThrownColor = color;
8490            JmriColorChooser.addRecentColor(color);
8491        }
8492    }
8493
8494    /**
8495     * Should only be invoked on the GUI (Swing) thread.
8496     *
8497     * @param state true to fill in turnout control circles, else false.
8498     */
8499    @InvokeOnGuiThread
8500    public void setTurnoutFillControlCircles(boolean state) {
8501        if (turnoutFillControlCircles != state) {
8502            turnoutFillControlCircles = state;
8503            turnoutFillControlCirclesCheckBoxMenuItem.setSelected(turnoutFillControlCircles);
8504        }
8505    }
8506
8507    public void setTurnoutCircleSize(int size) {
8508        // this is an int
8509        turnoutCircleSize = size;
8510
8511        // these are doubles
8512        circleRadius = SIZE * size;
8513        circleDiameter = 2.0 * circleRadius;
8514
8515        setOptionMenuTurnoutCircleSize();
8516    }
8517
8518    /**
8519     * Should only be invoked on the GUI (Swing) thread.
8520     *
8521     * @param state true to draw unselected legs, else false.
8522     */
8523    @InvokeOnGuiThread
8524    public void setTurnoutDrawUnselectedLeg(boolean state) {
8525        if (turnoutDrawUnselectedLeg != state) {
8526            turnoutDrawUnselectedLeg = state;
8527            turnoutDrawUnselectedLegCheckBoxMenuItem.setSelected(turnoutDrawUnselectedLeg);
8528        }
8529    }
8530
8531    /**
8532     * Should only be invoked on the GUI (Swing) thread.
8533     *
8534     * @param state true to enable highlighting the cursor (mouse/finger press/drag)
8535     */
8536    @InvokeOnGuiThread
8537    public void setHighlightCursor(boolean state) {
8538        if (highlightCursor != state) {
8539            highlightCursor = state;
8540            highlightCursorCheckBoxMenuItem.setSelected(highlightCursor);
8541        }
8542    }
8543
8544    /**
8545     * @param color value to set the default text color to.
8546     */
8547    public void setDefaultTextColor(@Nonnull Color color) {
8548        defaultTextColor = color;
8549        JmriColorChooser.addRecentColor(color);
8550    }
8551
8552    /**
8553     * @param color value to set the panel background to.
8554     */
8555    public void setDefaultBackgroundColor(@Nonnull Color color) {
8556        defaultBackgroundColor = color;
8557        JmriColorChooser.addRecentColor(color);
8558    }
8559
8560    public void setLayoutName(@Nonnull String name) {
8561        layoutName = name;
8562    }
8563
8564    /**
8565     * Should only be invoked on the GUI (Swing) thread.
8566     *
8567     * @param state true to show the help bar, else false.
8568     */
8569    @InvokeOnGuiThread  // due to the setSelected call on a possibly-visible item
8570    public void setShowHelpBar(boolean state) {
8571        if (showHelpBar != state) {
8572            showHelpBar = state;
8573
8574            // these may not be set up yet...
8575            if (showHelpCheckBoxMenuItem != null) {
8576                showHelpCheckBoxMenuItem.setSelected(showHelpBar);
8577            }
8578
8579            if (toolBarSide.equals(ToolBarSide.eFLOAT)) {
8580                if (floatEditHelpPanel != null) {
8581                    floatEditHelpPanel.setVisible(isEditable() && showHelpBar);
8582                }
8583            } else {
8584                if (helpBarPanel != null) {
8585                    helpBarPanel.setVisible(isEditable() && showHelpBar);
8586
8587                }
8588            }
8589            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".showHelpBar", showHelpBar));
8590        }
8591    }
8592
8593    /**
8594     * Should only be invoked on the GUI (Swing) thread.
8595     *
8596     * @param state true to show the draw grid, else false.
8597     */
8598    @InvokeOnGuiThread
8599    public void setDrawGrid(boolean state) {
8600        if (drawGrid != state) {
8601            drawGrid = state;
8602            showGridCheckBoxMenuItem.setSelected(drawGrid);
8603        }
8604    }
8605
8606    /**
8607     * Should only be invoked on the GUI (Swing) thread.
8608     *
8609     * @param state true to set snap to grid on add, else false.
8610     */
8611    @InvokeOnGuiThread
8612    public void setSnapOnAdd(boolean state) {
8613        if (snapToGridOnAdd != state) {
8614            snapToGridOnAdd = state;
8615            snapToGridOnAddCheckBoxMenuItem.setSelected(snapToGridOnAdd);
8616        }
8617    }
8618
8619    /**
8620     * Should only be invoked on the GUI (Swing) thread.
8621     *
8622     * @param state true to set snap on move, else false.
8623     */
8624    @InvokeOnGuiThread
8625    public void setSnapOnMove(boolean state) {
8626        if (snapToGridOnMove != state) {
8627            snapToGridOnMove = state;
8628            snapToGridOnMoveCheckBoxMenuItem.setSelected(snapToGridOnMove);
8629        }
8630    }
8631
8632    /**
8633     * Should only be invoked on the GUI (Swing) thread.
8634     *
8635     * @param state true to set anti-aliasing flag on, else false.
8636     */
8637    @InvokeOnGuiThread
8638    public void setAntialiasingOn(boolean state) {
8639        if (antialiasingOn != state) {
8640            antialiasingOn = state;
8641
8642            // this may not be set up yet...
8643            if (antialiasingOnCheckBoxMenuItem != null) {
8644                antialiasingOnCheckBoxMenuItem.setSelected(antialiasingOn);
8645
8646            }
8647            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".antialiasingOn", antialiasingOn));
8648        }
8649    }
8650
8651    /**
8652     *
8653     * @param state true to set anti-aliasing flag on, else false.
8654     */
8655    public void setDrawLayoutTracksLabel(boolean state) {
8656        if (drawLayoutTracksLabel != state) {
8657            drawLayoutTracksLabel = state;
8658
8659            // this may not be set up yet...
8660            if (drawLayoutTracksLabelCheckBoxMenuItem != null) {
8661                drawLayoutTracksLabelCheckBoxMenuItem.setSelected(drawLayoutTracksLabel);
8662
8663            }
8664            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".drawLayoutTracksLabel", drawLayoutTracksLabel));
8665        }
8666    }
8667
8668    // enable/disable using the "Extra" color to highlight the selected block
8669    public void setHighlightSelectedBlock(boolean state) {
8670        if (highlightSelectedBlockFlag != state) {
8671            highlightSelectedBlockFlag = state;
8672
8673            // this may not be set up yet...
8674            if (leToolBarPanel.highlightBlockCheckBox != null) {
8675                leToolBarPanel.highlightBlockCheckBox.setSelected(highlightSelectedBlockFlag);
8676
8677            }
8678
8679            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".highlightSelectedBlock", highlightSelectedBlockFlag));
8680
8681            // thread this so it won't break the AppVeyor checks
8682            ThreadingUtil.newThread(() -> {
8683                if (highlightSelectedBlockFlag) {
8684                    // use the "Extra" color to highlight the selected block
8685                    if (!highlightBlockInComboBox(leToolBarPanel.blockIDComboBox)) {
8686                        highlightBlockInComboBox(leToolBarPanel.blockContentsComboBox);
8687                    }
8688                } else {
8689                    // undo using the "Extra" color to highlight the selected block
8690                    Block block = leToolBarPanel.blockIDComboBox.getSelectedItem();
8691                    highlightBlock(null);
8692                    leToolBarPanel.blockIDComboBox.setSelectedItem(block);
8693                }
8694            }).start();
8695        }
8696    }
8697
8698    //
8699    // highlight the block selected by the specified combo Box
8700    //
8701    public boolean highlightBlockInComboBox(@Nonnull NamedBeanComboBox<Block> inComboBox) {
8702        return highlightBlock(inComboBox.getSelectedItem());
8703    }
8704
8705    /**
8706     * highlight the specified block
8707     *
8708     * @param inBlock the block
8709     * @return true if block was highlighted
8710     */
8711    public boolean highlightBlock(@CheckForNull Block inBlock) {
8712        boolean result = false; // assume failure (pessimist!)
8713
8714        if (leToolBarPanel.blockIDComboBox.getSelectedItem() != inBlock) {
8715            leToolBarPanel.blockIDComboBox.setSelectedItem(inBlock);
8716        }
8717
8718        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class
8719        );
8720        Set<Block> l = leToolBarPanel.blockIDComboBox.getManager().getNamedBeanSet();
8721        for (Block b : l) {
8722            LayoutBlock lb = lbm.getLayoutBlock(b);
8723            if (lb != null) {
8724                boolean enable = ((inBlock != null) && b.equals(inBlock));
8725                lb.setUseExtraColor(enable);
8726                result |= enable;
8727            }
8728        }
8729        return result;
8730    }
8731
8732    /**
8733     * highlight the specified layout block
8734     *
8735     * @param inLayoutBlock the layout block
8736     * @return true if layout block was highlighted
8737     */
8738    public boolean highlightLayoutBlock(@Nonnull LayoutBlock inLayoutBlock) {
8739        return highlightBlock(inLayoutBlock.getBlock());
8740    }
8741
8742    public void setTurnoutCircles(boolean state) {
8743        if (turnoutCirclesWithoutEditMode != state) {
8744            turnoutCirclesWithoutEditMode = state;
8745            if (turnoutCirclesOnCheckBoxMenuItem != null) {
8746                turnoutCirclesOnCheckBoxMenuItem.setSelected(turnoutCirclesWithoutEditMode);
8747            }
8748        }
8749    }
8750
8751    public void setAutoBlockAssignment(boolean boo) {
8752        if (autoAssignBlocks != boo) {
8753            autoAssignBlocks = boo;
8754            if (autoAssignBlocksCheckBoxMenuItem != null) {
8755                autoAssignBlocksCheckBoxMenuItem.setSelected(autoAssignBlocks);
8756            }
8757        }
8758    }
8759
8760    public void setTooltipsNotEdit(boolean state) {
8761        if (tooltipsWithoutEditMode != state) {
8762            tooltipsWithoutEditMode = state;
8763            setTooltipSubMenu();
8764            setTooltipsAlwaysOrNever();
8765        }
8766    }
8767
8768    public void setTooltipsInEdit(boolean state) {
8769        if (tooltipsInEditMode != state) {
8770            tooltipsInEditMode = state;
8771            setTooltipSubMenu();
8772            setTooltipsAlwaysOrNever();
8773        }
8774    }
8775
8776    private void setTooltipsAlwaysOrNever() {
8777        tooltipsAlwaysOrNever = ((tooltipsInEditMode && tooltipsWithoutEditMode) ||
8778                    (!tooltipsInEditMode && !tooltipsWithoutEditMode));
8779    }
8780
8781    private void setTooltipSubMenu() {
8782        if (tooltipNoneMenuItem != null) {
8783            tooltipNoneMenuItem.setSelected((!tooltipsInEditMode) && (!tooltipsWithoutEditMode));
8784            tooltipAlwaysMenuItem.setSelected((tooltipsInEditMode) && (tooltipsWithoutEditMode));
8785            tooltipInEditMenuItem.setSelected((tooltipsInEditMode) && (!tooltipsWithoutEditMode));
8786            tooltipNotInEditMenuItem.setSelected((!tooltipsInEditMode) && (tooltipsWithoutEditMode));
8787        }
8788    }
8789
8790    // accessor routines for turnout size parameters
8791    public void setTurnoutBX(double bx) {
8792        turnoutBX = bx;
8793        setDirty();
8794    }
8795
8796    public double getTurnoutBX() {
8797        return turnoutBX;
8798    }
8799
8800    public void setTurnoutCX(double cx) {
8801        turnoutCX = cx;
8802        setDirty();
8803    }
8804
8805    public double getTurnoutCX() {
8806        return turnoutCX;
8807    }
8808
8809    public void setTurnoutWid(double wid) {
8810        turnoutWid = wid;
8811        setDirty();
8812    }
8813
8814    public double getTurnoutWid() {
8815        return turnoutWid;
8816    }
8817
8818    public void setXOverLong(double lg) {
8819        xOverLong = lg;
8820        setDirty();
8821    }
8822
8823    public double getXOverLong() {
8824        return xOverLong;
8825    }
8826
8827    public void setXOverHWid(double hwid) {
8828        xOverHWid = hwid;
8829        setDirty();
8830    }
8831
8832    public double getXOverHWid() {
8833        return xOverHWid;
8834    }
8835
8836    public void setXOverShort(double sh) {
8837        xOverShort = sh;
8838        setDirty();
8839    }
8840
8841    public double getXOverShort() {
8842        return xOverShort;
8843    }
8844
8845    // reset turnout sizes to program defaults
8846    private void resetTurnoutSize() {
8847        turnoutBX = LayoutTurnout.turnoutBXDefault;
8848        turnoutCX = LayoutTurnout.turnoutCXDefault;
8849        turnoutWid = LayoutTurnout.turnoutWidDefault;
8850        xOverLong = LayoutTurnout.xOverLongDefault;
8851        xOverHWid = LayoutTurnout.xOverHWidDefault;
8852        xOverShort = LayoutTurnout.xOverShortDefault;
8853        setDirty();
8854    }
8855
8856    public void setDirectTurnoutControl(boolean boo) {
8857        useDirectTurnoutControl = boo;
8858        useDirectTurnoutControlCheckBoxMenuItem.setSelected(useDirectTurnoutControl);
8859    }
8860
8861    // TODO: Java standard pattern for boolean getters is "isShowHelpBar()"
8862    public boolean getDirectTurnoutControl() {
8863        return useDirectTurnoutControl;
8864    }
8865
8866    // final initialization routine for loading a LayoutEditor
8867    public void setConnections() {
8868        getLayoutTracks().forEach((lt) -> lt.setObjects(this));
8869        getLEAuxTools().initializeBlockConnectivity();
8870        log.debug("Initializing Block Connectivity for {}", getLayoutName());
8871
8872        // reset the panel changed bit
8873        resetDirty();
8874    }
8875
8876    // these are convenience methods to return rectangles
8877    // to use when (hit point-in-rect testing
8878    //
8879    // compute the control point rect at inPoint
8880    public @Nonnull
8881    Rectangle2D layoutEditorControlRectAt(@Nonnull Point2D inPoint) {
8882        return new Rectangle2D.Double(inPoint.getX() - SIZE,
8883                inPoint.getY() - SIZE, SIZE2, SIZE2);
8884    }
8885
8886    // compute the turnout circle control rect at inPoint
8887    public @Nonnull
8888    Rectangle2D layoutEditorControlCircleRectAt(@Nonnull Point2D inPoint) {
8889        return new Rectangle2D.Double(inPoint.getX() - circleRadius,
8890                inPoint.getY() - circleRadius, circleDiameter, circleDiameter);
8891    }
8892
8893    /**
8894     * Special internal class to allow drawing of layout to a JLayeredPane This
8895     * is the 'target' pane where the layout is displayed
8896     */
8897    @Override
8898    public void paintTargetPanel(@Nonnull Graphics g) {
8899        // Nothing to do here
8900        // All drawing has been moved into LayoutEditorComponent
8901        // which calls draw.
8902        // This is so the layout is drawn at level three
8903        // (above or below the Positionables)
8904    }
8905
8906    // get selection rectangle
8907    @Nonnull
8908    public Rectangle2D getSelectionRect() {
8909        double selX = Math.min(selectionX, selectionX + selectionWidth);
8910        double selY = Math.min(selectionY, selectionY + selectionHeight);
8911        return new Rectangle2D.Double(selX, selY,
8912                Math.abs(selectionWidth), Math.abs(selectionHeight));
8913    }
8914
8915    // set selection rectangle
8916    public void setSelectionRect(@Nonnull Rectangle2D selectionRect) {
8917        // selectionRect = selectionRect.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8918        selectionX = selectionRect.getX();
8919        selectionY = selectionRect.getY();
8920        selectionWidth = selectionRect.getWidth();
8921        selectionHeight = selectionRect.getHeight();
8922
8923        // There's already code in the super class (Editor) to draw
8924        // the selection rect... We just have to set _selectRect
8925        _selectRect = MathUtil.rectangle2DToRectangle(selectionRect);
8926
8927        selectionRect = MathUtil.scale(selectionRect, getZoom());
8928
8929        JComponent targetPanel = getTargetPanel();
8930        Rectangle targetRect = targetPanel.getVisibleRect();
8931        // this will make it the size of the targetRect
8932        // (effectively centering it onscreen)
8933        Rectangle2D selRect2D = MathUtil.inset(selectionRect,
8934                (selectionRect.getWidth() - targetRect.getWidth()) / 2.0,
8935                (selectionRect.getHeight() - targetRect.getHeight()) / 2.0);
8936        // don't let the origin go negative
8937        selRect2D = selRect2D.createIntersection(MathUtil.zeroToInfinityRectangle2D);
8938        Rectangle selRect = MathUtil.rectangle2DToRectangle(selRect2D);
8939        if (!targetRect.contains(selRect)) {
8940            targetPanel.scrollRectToVisible(selRect);
8941        }
8942
8943        clearSelectionGroups();
8944        selectionActive = true;
8945        createSelectionGroups();
8946        // redrawPanel(); // createSelectionGroups already calls this
8947    }
8948
8949    public void setSelectRect(Rectangle rectangle) {
8950        _selectRect = rectangle;
8951    }
8952
8953    /*
8954    // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class
8955    public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<LayoutTrack> layoutTrackClass) {
8956    return getLayoutTracks().stream()
8957    .filter(item -> item instanceof PositionablePoint)
8958    .filter(layoutTrackClass::isInstance)
8959    //.map(layoutTrackClass::cast)  // TODO: Do we need this? if not dead-code-strip
8960    .collect(Collectors.toList());
8961    }
8962
8963    // TODO: This compiles but I can't get the syntax correct to pass the array of (sub-)classes
8964    public List<LayoutTrack> getLayoutTracksOfClasses(@Nonnull List<Class<? extends LayoutTrack>> layoutTrackClasses) {
8965    return getLayoutTracks().stream()
8966    .filter(o -> layoutTrackClasses.contains(o.getClass()))
8967    .collect(Collectors.toList());
8968    }
8969
8970    // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class
8971    public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<? extends LayoutTrack> layoutTrackClass) {
8972    return getLayoutTracksOfClasses(new ArrayList<>(Arrays.asList(layoutTrackClass)));
8973    }
8974
8975    public List<PositionablePoint> getPositionablePoints() {
8976    return getLayoutTracksOfClass(PositionablePoint);
8977    }
8978     */
8979    @Override
8980    public @Nonnull
8981    Stream<LayoutTrack> getLayoutTracksOfClass(Class<? extends LayoutTrack> layoutTrackClass) {
8982        return getLayoutTracks().stream()
8983                .filter(layoutTrackClass::isInstance)
8984                .map(layoutTrackClass::cast);
8985    }
8986
8987    @Override
8988    public @Nonnull
8989    Stream<LayoutTrackView> getLayoutTrackViewsOfClass(Class<? extends LayoutTrackView> layoutTrackViewClass) {
8990        return getLayoutTrackViews().stream()
8991                .filter(layoutTrackViewClass::isInstance)
8992                .map(layoutTrackViewClass::cast);
8993    }
8994
8995    @Override
8996    public @Nonnull
8997    List<PositionablePointView> getPositionablePointViews() {
8998        return getLayoutTrackViewsOfClass(PositionablePointView.class)
8999                .map(PositionablePointView.class::cast)
9000                .collect(Collectors.toCollection(ArrayList::new));
9001    }
9002
9003    @Override
9004    public @Nonnull
9005    List<PositionablePoint> getPositionablePoints() {
9006        return getLayoutTracksOfClass(PositionablePoint.class)
9007                .map(PositionablePoint.class::cast)
9008                .collect(Collectors.toCollection(ArrayList::new));
9009    }
9010
9011    public @Nonnull
9012    List<LayoutSlipView> getLayoutSlipViews() {
9013        return getLayoutTrackViewsOfClass(LayoutSlipView.class)
9014                .map(LayoutSlipView.class::cast)
9015                .collect(Collectors.toCollection(ArrayList::new));
9016    }
9017
9018    @Override
9019    public @Nonnull
9020    List<LayoutSlip> getLayoutSlips() {
9021        return getLayoutTracksOfClass(LayoutSlip.class)
9022                .map(LayoutSlip.class::cast)
9023                .collect(Collectors.toCollection(ArrayList::new));
9024    }
9025
9026    @Override
9027    public @Nonnull
9028    List<TrackSegmentView> getTrackSegmentViews() {
9029        return getLayoutTrackViewsOfClass(TrackSegmentView.class)
9030                .map(TrackSegmentView.class::cast)
9031                .collect(Collectors.toCollection(ArrayList::new));
9032    }
9033
9034    @Override
9035    public @Nonnull
9036    List<TrackSegment> getTrackSegments() {
9037        return getLayoutTracksOfClass(TrackSegment.class)
9038                .map(TrackSegment.class::cast)
9039                .collect(Collectors.toCollection(ArrayList::new));
9040    }
9041
9042    public @Nonnull
9043    List<LayoutTurnoutView> getLayoutTurnoutViews() { // this specifically does not include slips
9044        return getLayoutTrackViews().stream() // next line excludes LayoutSlips
9045                .filter((o) -> (!(o instanceof LayoutSlipView) && (o instanceof LayoutTurnoutView)))
9046                .map(LayoutTurnoutView.class::cast)
9047                .collect(Collectors.toCollection(ArrayList::new));
9048    }
9049
9050    @Override
9051    public @Nonnull
9052    List<LayoutTurnout> getLayoutTurnouts() { // this specifically does not include slips
9053        return getLayoutTracks().stream() // next line excludes LayoutSlips
9054                .filter((o) -> (!(o instanceof LayoutSlip) && (o instanceof LayoutTurnout)))
9055                .map(LayoutTurnout.class::cast)
9056                .collect(Collectors.toCollection(ArrayList::new));
9057    }
9058
9059    @Override
9060    public @Nonnull
9061    List<LayoutTurntable> getLayoutTurntables() {
9062        return getLayoutTracksOfClass(LayoutTurntable.class)
9063                .map(LayoutTurntable.class::cast)
9064                .collect(Collectors.toCollection(ArrayList::new));
9065    }
9066
9067    public @Nonnull
9068    List<LayoutTurntableView> getLayoutTurntableViews() {
9069        return getLayoutTrackViewsOfClass(LayoutTurntableView.class)
9070                .map(LayoutTurntableView.class::cast)
9071                .collect(Collectors.toCollection(ArrayList::new));
9072    }
9073
9074    @Override
9075    public @Nonnull
9076    List<LayoutTraverser> getLayoutTraversers() {
9077        return getLayoutTracksOfClass(LayoutTraverser.class)
9078                .map(LayoutTraverser.class::cast)
9079                .collect(Collectors.toCollection(ArrayList::new));
9080    }
9081
9082    public @Nonnull
9083    List<LayoutTraverserView> getLayoutTraverserViews() {
9084        return getLayoutTrackViewsOfClass(LayoutTraverserView.class)
9085                .map(LayoutTraverserView.class::cast)
9086                .collect(Collectors.toCollection(ArrayList::new));
9087    }
9088
9089    @Override
9090    public @Nonnull
9091    List<LevelXing> getLevelXings() {
9092        return getLayoutTracksOfClass(LevelXing.class)
9093                .map(LevelXing.class::cast)
9094                .collect(Collectors.toCollection(ArrayList::new));
9095    }
9096
9097    @Override
9098    public @Nonnull
9099    List<LevelXingView> getLevelXingViews() {
9100        return getLayoutTrackViewsOfClass(LevelXingView.class)
9101                .map(LevelXingView.class::cast)
9102                .collect(Collectors.toCollection(ArrayList::new));
9103    }
9104
9105    /**
9106     * Read-only access to the list of LayoutTrack family objects. The returned
9107     * list will throw UnsupportedOperationException if you attempt to modify
9108     * it.
9109     *
9110     * @return unmodifiable copy of layout track list.
9111     */
9112    @Override
9113    @Nonnull
9114    public final List<LayoutTrack> getLayoutTracks() {
9115        return Collections.unmodifiableList(layoutTrackList);
9116    }
9117
9118    public @Nonnull
9119    List<LayoutTurnoutView> getLayoutTurnoutAndSlipViews() {
9120        return getLayoutTrackViewsOfClass(LayoutTurnoutView.class
9121        )
9122                .map(LayoutTurnoutView.class::cast)
9123                .collect(Collectors.toCollection(ArrayList::new));
9124    }
9125
9126    @Override
9127    public @Nonnull
9128    List<LayoutTurnout> getLayoutTurnoutsAndSlips() {
9129        return getLayoutTracksOfClass(LayoutTurnout.class
9130        )
9131                .map(LayoutTurnout.class::cast)
9132                .collect(Collectors.toCollection(ArrayList::new));
9133    }
9134
9135    /**
9136     * Read-only access to the list of LayoutTrackView family objects. The
9137     * returned list will throw UnsupportedOperationException if you attempt to
9138     * modify it.
9139     *
9140     * @return unmodifiable copy of track views.
9141     */
9142    @Override
9143    @Nonnull
9144    public final List<LayoutTrackView> getLayoutTrackViews() {
9145        return Collections.unmodifiableList(layoutTrackViewList);
9146    }
9147
9148    private final List<LayoutTrack> layoutTrackList = new ArrayList<>();
9149    private final List<LayoutTrackView> layoutTrackViewList = new ArrayList<>();
9150    private final Map<LayoutTrack, LayoutTrackView> trkToView = new HashMap<>();
9151    private final Map<LayoutTrackView, LayoutTrack> viewToTrk = new HashMap<>();
9152
9153    // temporary
9154    @Override
9155    public final LayoutTrackView getLayoutTrackView(LayoutTrack trk) {
9156        LayoutTrackView lv = trkToView.get(trk);
9157        if (lv == null) {
9158            log.warn("No View found for {} class {}", trk, trk.getClass());
9159            throw new IllegalArgumentException("No View found: " + trk.getClass());
9160        }
9161        return lv;
9162    }
9163
9164    // temporary
9165    @Override
9166    public final LevelXingView getLevelXingView(LevelXing xing) {
9167        LayoutTrackView lv = trkToView.get(xing);
9168        if (lv == null) {
9169            log.warn("No View found for {} class {}", xing, xing.getClass());
9170            throw new IllegalArgumentException("No View found: " + xing.getClass());
9171        }
9172        if (lv instanceof LevelXingView) {
9173            return (LevelXingView) lv;
9174        } else {
9175            log.error("wrong type {} {} found {}", xing, xing.getClass(), lv);
9176        }
9177        throw new IllegalArgumentException("Wrong type: " + xing.getClass());
9178    }
9179
9180    // temporary
9181    @Override
9182    public final LayoutTurnoutView getLayoutTurnoutView(LayoutTurnout to) {
9183        LayoutTrackView lv = trkToView.get(to);
9184        if (lv == null) {
9185            log.warn("No View found for {} class {}", to, to.getClass());
9186            throw new IllegalArgumentException("No View found: " + to);
9187        }
9188        if (lv instanceof LayoutTurnoutView) {
9189            return (LayoutTurnoutView) lv;
9190        } else {
9191            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9192        }
9193        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9194    }
9195
9196    // temporary
9197    @Override
9198    public final LayoutTurntableView getLayoutTurntableView(LayoutTurntable to) {
9199        LayoutTrackView lv = trkToView.get(to);
9200        if (lv == null) {
9201            log.warn("No View found for {} class {}", to, to.getClass());
9202            throw new IllegalArgumentException("No matching View found: " + to);
9203        }
9204        if (lv instanceof LayoutTurntableView) {
9205            return (LayoutTurntableView) lv;
9206        } else {
9207            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9208        }
9209        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9210    }
9211
9212    // temporary
9213    @Override
9214    public final LayoutTraverserView getLayoutTraverserView(LayoutTraverser to) {
9215        LayoutTrackView lv = trkToView.get(to);
9216        if (lv == null) {
9217            log.warn("No View found for {} class {}", to, to.getClass());
9218            throw new IllegalArgumentException("No matching View found: " + to);
9219        }
9220        if (lv instanceof LayoutTraverserView) {
9221            return (LayoutTraverserView) lv;
9222        } else {
9223            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9224        }
9225        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9226    }
9227
9228    // temporary
9229    public final LayoutSlipView getLayoutSlipView(LayoutSlip to) {
9230        LayoutTrackView lv = trkToView.get(to);
9231        if (lv == null) {
9232            log.warn("No View found for {} class {}", to, to.getClass());
9233            throw new IllegalArgumentException("No matching View found: " + to);
9234        }
9235        if (lv instanceof LayoutSlipView) {
9236            return (LayoutSlipView) lv;
9237        } else {
9238            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9239        }
9240        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9241    }
9242
9243    // temporary
9244    @Override
9245    public final TrackSegmentView getTrackSegmentView(TrackSegment to) {
9246        LayoutTrackView lv = trkToView.get(to);
9247        if (lv == null) {
9248            log.warn("No View found for {} class {}", to, to.getClass());
9249            throw new IllegalArgumentException("No matching View found: " + to);
9250        }
9251        if (lv instanceof TrackSegmentView) {
9252            return (TrackSegmentView) lv;
9253        } else {
9254            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9255        }
9256        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9257    }
9258
9259    // temporary
9260    @Override
9261    public final PositionablePointView getPositionablePointView(PositionablePoint to) {
9262        LayoutTrackView lv = trkToView.get(to);
9263        if (lv == null) {
9264            log.warn("No View found for {} class {}", to, to.getClass());
9265            throw new IllegalArgumentException("No matching View found: " + to);
9266        }
9267        if (lv instanceof PositionablePointView) {
9268            return (PositionablePointView) lv;
9269        } else {
9270            log.error("wrong type {} {} found {}", to, to.getClass(), lv);
9271        }
9272        throw new IllegalArgumentException("Wrong type: " + to.getClass());
9273    }
9274
9275    /**
9276     * Add a LayoutTrack and LayoutTrackView to the list of LayoutTrack family
9277     * objects.
9278     *
9279     * @param trk the layout track to add.
9280     */
9281    @Override
9282    public final void addLayoutTrack(@Nonnull LayoutTrack trk, @Nonnull LayoutTrackView v) {
9283        log.trace("addLayoutTrack {}", trk);
9284        if (layoutTrackList.contains(trk)) {
9285            log.warn("LayoutTrack {} already being maintained", trk.getName());
9286        }
9287
9288        layoutTrackList.add(trk);
9289        layoutTrackViewList.add(v);
9290        trkToView.put(trk, v);
9291        viewToTrk.put(v, trk);
9292
9293        unionToPanelBounds(v.getBounds()); // temporary - this should probably _not_ be in the topological part
9294
9295    }
9296
9297    /**
9298     * If item present, delete from the list of LayoutTracks and force a dirty
9299     * redraw.
9300     *
9301     * @param trk the layout track to remove and redraw.
9302     * @return true is item was deleted and a redraw done.
9303     */
9304    public final boolean removeLayoutTrackAndRedraw(@Nonnull LayoutTrack trk) {
9305        log.trace("removeLayoutTrackAndRedraw {}", trk);
9306        if (layoutTrackList.contains(trk)) {
9307            removeLayoutTrack(trk);
9308            setDirty();
9309            redrawPanel();
9310            log.trace("removeLayoutTrackAndRedraw present {}", trk);
9311            return true;
9312        }
9313        log.trace("removeLayoutTrackAndRedraw absent {}", trk);
9314        return false;
9315    }
9316
9317    /**
9318     * If item present, delete from the list of LayoutTracks and force a dirty
9319     * redraw.
9320     *
9321     * @param trk the layout track to remove.
9322     */
9323    @Override
9324    public final void removeLayoutTrack(@Nonnull LayoutTrack trk) {
9325        log.trace("removeLayoutTrack {}", trk);
9326        layoutTrackList.remove(trk);
9327        LayoutTrackView v = trkToView.get(trk);
9328        layoutTrackViewList.remove(v);
9329        trkToView.remove(trk);
9330        viewToTrk.remove(v);
9331    }
9332
9333    /**
9334     * Clear the list of layout tracks. Not intended for general use.
9335     * <p>
9336     */
9337    private void clearLayoutTracks() {
9338        layoutTrackList.clear();
9339        layoutTrackViewList.clear();
9340        trkToView.clear();
9341        viewToTrk.clear();
9342    }
9343
9344    @Override
9345    public @Nonnull
9346    List<LayoutShape> getLayoutShapes() {
9347        return layoutShapes;
9348    }
9349
9350    public void sortLayoutShapesByLevel() {
9351        layoutShapes.sort((lhs, rhs) -> {
9352            // -1 == less than, 0 == equal, +1 == greater than
9353            return Integer.signum(lhs.getLevel() - rhs.getLevel());
9354        });
9355    }
9356
9357    /**
9358     * {@inheritDoc}
9359     * <p>
9360     * This implementation is temporary, using the on-screen points from the
9361     * LayoutTrackViews via {@link LayoutEditor#getCoords}.
9362     */
9363    @Override
9364    public int computeDirection(LayoutTrack trk1, HitPointType h1, LayoutTrack trk2, HitPointType h2) {
9365        return Path.computeDirection(
9366                getCoords(trk1, h1),
9367                getCoords(trk2, h2)
9368        );
9369    }
9370
9371    @Override
9372    public int computeDirectionToCenter(@Nonnull LayoutTrack trk1, @Nonnull HitPointType h1, @Nonnull PositionablePoint p) {
9373        return Path.computeDirection(
9374                getCoords(trk1, h1),
9375                getPositionablePointView(p).getCoordsCenter()
9376        );
9377    }
9378
9379    @Override
9380    public int computeDirectionFromCenter(@Nonnull PositionablePoint p, @Nonnull LayoutTrack trk1, @Nonnull HitPointType h1) {
9381        return Path.computeDirection(
9382                getPositionablePointView(p).getCoordsCenter(),
9383                getCoords(trk1, h1)
9384        );
9385    }
9386
9387    @Override
9388    public boolean showAlignPopup(@Nonnull Positionable l) {
9389        return false;
9390    }
9391
9392    @Override
9393    public void showToolTip(
9394            @Nonnull Positionable selection,
9395            @Nonnull JmriMouseEvent event) {
9396        ToolTip tip = selection.getToolTip();
9397        tip.setLocation(selection.getX() + selection.getWidth() / 2, selection.getY() + selection.getHeight());
9398        setToolTip(tip);
9399    }
9400
9401    @Override
9402    public void addToPopUpMenu(
9403            @Nonnull NamedBean nb,
9404            @Nonnull JMenuItem item,
9405            int menu) {
9406        if ((nb == null) || (item == null)) {
9407            return;
9408        }
9409
9410        List<?> theList = null;
9411
9412        if (nb instanceof Sensor) {
9413            theList = sensorList;
9414        } else if (nb instanceof Turnout) {
9415            theList = turnoutList;
9416        } else if (nb instanceof SignalHead) {
9417            theList = signalList;
9418        } else if (nb instanceof SignalMast) {
9419            theList = signalMastList;
9420        } else if (nb instanceof Block) {
9421            theList = blockContentsLabelList;
9422        } else if (nb instanceof Memory) {
9423            theList = memoryLabelList;    // Memory Input Icon not supported at this time.
9424        } else if (nb instanceof GlobalVariable) {
9425            theList = globalVariableLabelList;
9426        }
9427        if (theList != null) {
9428            for (Object o : theList) {
9429                PositionableLabel si = (PositionableLabel) o;
9430                if ((si.getNamedBean() == nb) && (si.getPopupUtility() != null)) {
9431                    if (menu != Editor.VIEWPOPUPONLY) {
9432                        si.getPopupUtility().addEditPopUpMenu(item);
9433                    }
9434                    if (menu != Editor.EDITPOPUPONLY) {
9435                        si.getPopupUtility().addViewPopUpMenu(item);
9436                    }
9437                }
9438            }
9439        } else if (nb instanceof Turnout) {
9440            for (LayoutTurnoutView ltv : getLayoutTurnoutAndSlipViews()) {
9441                if (ltv.getTurnout().equals(nb)) {
9442                    if (menu != Editor.VIEWPOPUPONLY) {
9443                        ltv.addEditPopUpMenu(item);
9444                    }
9445                    if (menu != Editor.EDITPOPUPONLY) {
9446                        ltv.addViewPopUpMenu(item);
9447                    }
9448                }
9449            }
9450        }
9451    }
9452
9453    @Override
9454    public @Nonnull
9455    String toString() {
9456        return String.format("LayoutEditor: %s", getLayoutName());
9457    }
9458
9459    @Override
9460    public void vetoableChange(
9461            @Nonnull PropertyChangeEvent evt)
9462            throws PropertyVetoException {
9463        NamedBean nb = (NamedBean) evt.getOldValue();
9464
9465        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
9466            StringBuilder message = new StringBuilder();
9467            message.append(Bundle.getMessage("VetoInUseLayoutEditorHeader", toString())); // NOI18N
9468            message.append("<ul>");
9469            boolean found = false;
9470
9471            if (nb instanceof SignalHead) {
9472                if (containsSignalHead((SignalHead) nb)) {
9473                    found = true;
9474                    message.append("<li>");
9475                    message.append(Bundle.getMessage("VetoSignalHeadIconFound"));
9476                    message.append("</li>");
9477                }
9478                LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
9479
9480                if (lt != null) {
9481                    message.append("<li>");
9482                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToTurnout", lt.getTurnoutName()));
9483                    message.append("</li>");
9484                }
9485                PositionablePoint p = finder.findPositionablePointByBean(nb);
9486
9487                if (p != null) {
9488                    message.append("<li>");
9489                    // Need to expand to get the names of blocks
9490                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToPoint"));
9491                    message.append("</li>");
9492                }
9493                LevelXing lx = finder.findLevelXingByBean(nb);
9494
9495                if (lx != null) {
9496                    message.append("<li>");
9497                    // Need to expand to get the names of blocks
9498                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToLevelXing"));
9499                    message.append("</li>");
9500                }
9501                LayoutSlip ls = finder.findLayoutSlipByBean(nb);
9502
9503                if (ls != null) {
9504                    message.append("<li>");
9505                    message.append(Bundle.getMessage("VetoSignalHeadAssignedToLayoutSlip", ls.getTurnoutName()));
9506                    message.append("</li>");
9507                }
9508            } else if (nb instanceof Turnout) {
9509                LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
9510
9511                if (lt != null) {
9512                    found = true;
9513                    message.append("<li>");
9514                    message.append(Bundle.getMessage("VetoTurnoutIconFound"));
9515                    message.append("</li>");
9516                }
9517
9518                for (LayoutTurnout t : getLayoutTurnouts()) {
9519                    if (t.getLinkedTurnoutName() != null) {
9520                        String uname = nb.getUserName();
9521
9522                        if (nb.getSystemName().equals(t.getLinkedTurnoutName())
9523                                || ((uname != null) && uname.equals(t.getLinkedTurnoutName()))) {
9524                            found = true;
9525                            message.append("<li>");
9526                            message.append(Bundle.getMessage("VetoLinkedTurnout", t.getTurnoutName()));
9527                            message.append("</li>");
9528                        }
9529                    }
9530
9531                    if (nb.equals(t.getSecondTurnout())) {
9532                        found = true;
9533                        message.append("<li>");
9534                        message.append(Bundle.getMessage("VetoSecondTurnout", t.getTurnoutName()));
9535                        message.append("</li>");
9536                    }
9537                }
9538                LayoutSlip ls = finder.findLayoutSlipByBean(nb);
9539
9540                if (ls != null) {
9541                    found = true;
9542                    message.append("<li>");
9543                    message.append(Bundle.getMessage("VetoSlipIconFound", ls.getDisplayName()));
9544                    message.append("</li>");
9545                }
9546
9547                for (LayoutTurntable lx : getLayoutTurntables()) {
9548                    if (lx.isTurnoutControlled()) {
9549                        for (int i = 0; i < lx.getNumberRays(); i++) {
9550                            if (nb.equals(lx.getRayTurnout(i))) {
9551                                found = true;
9552                                message.append("<li>");
9553                                message.append(Bundle.getMessage("VetoRayTurntableControl", lx.getId()));
9554                                message.append("</li>");
9555                                break;
9556                            }
9557                        }
9558                    }
9559                }
9560                for (LayoutTraverser lx : getLayoutTraversers()) {
9561                    if (lx.isTurnoutControlled()) {
9562                        for (int i = 0; i < lx.getNumberSlots(); i++) {
9563                            if (nb.equals(lx.getSlotTurnout(i))) {
9564                                found = true;
9565                                message.append("<li>");
9566                                message.append(Bundle.getMessage("VetoSlotTraverserControl", lx.getId()));
9567                                message.append("</li>");
9568                                break;
9569                            }
9570                        }
9571                    }
9572                }
9573            }
9574
9575            if (nb instanceof SignalMast) {
9576                if (containsSignalMast((SignalMast) nb)) {
9577                    message.append("<li>");
9578                    message.append("As an Icon");
9579                    message.append("</li>");
9580                    found = true;
9581                }
9582                String foundelsewhere = findBeanUsage(nb);
9583
9584                if (foundelsewhere != null) {
9585                    message.append(foundelsewhere);
9586                    found = true;
9587                }
9588            }
9589
9590            if (nb instanceof Sensor) {
9591                int count = 0;
9592
9593                for (SensorIcon si : sensorList) {
9594                    if (nb.equals(si.getNamedBean())) {
9595                        count++;
9596                        found = true;
9597                    }
9598                }
9599
9600                if (count > 0) {
9601                    message.append("<li>");
9602                    message.append(String.format("As an Icon %s times", count));
9603                    message.append("</li>");
9604                }
9605                String foundelsewhere = findBeanUsage(nb);
9606
9607                if (foundelsewhere != null) {
9608                    message.append(foundelsewhere);
9609                    found = true;
9610                }
9611            }
9612
9613            if (nb instanceof Turnout) {
9614                int count = 0;
9615
9616                for (TurnoutIcon si : turnoutList) {
9617                    if (nb.equals(si.getNamedBean())) {
9618                        count++;
9619                        found = true;
9620                    }
9621                }
9622
9623                if (count > 0) {
9624                    message.append("<li>");
9625                    message.append(String.format("As an Icon %s times", count));
9626                    message.append("</li>");
9627                }
9628                String foundelsewhere = findBeanUsage(nb);
9629
9630                if (foundelsewhere != null) {
9631                    message.append(foundelsewhere);
9632                    found = true;
9633                }
9634            }
9635
9636            if (nb instanceof Memory) {
9637                for (MemoryIcon si : memoryLabelList) {
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                for (MemoryInputIcon si : memoryInputList) {
9646                    if (nb.equals(si.getMemory())) {
9647                        found = true;
9648                        message.append("<li>");
9649                        message.append(Bundle.getMessage("VetoMemoryIconFound"));
9650                        message.append("</li>");
9651                    }
9652                }
9653            }
9654
9655            if (nb instanceof GlobalVariable) {
9656                for (GlobalVariableIcon si : globalVariableLabelList) {
9657                    if (nb.equals(si.getGlobalVariable())) {
9658                        found = true;
9659                        message.append("<li>");
9660                        message.append(Bundle.getMessage("VetoGlobalVariableIconFound"));
9661                        message.append("</li>");
9662                    }
9663                }
9664            }
9665
9666            if (found) {
9667                message.append("</ul>");
9668                message.append(Bundle.getMessage("VetoReferencesWillBeRemoved")); // NOI18N
9669                throw new PropertyVetoException(message.toString(), evt);
9670            }
9671        } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N
9672            if (nb instanceof SignalHead) {
9673                removeSignalHead((SignalHead) nb);
9674                removeBeanRefs(nb);
9675            }
9676
9677            if (nb instanceof Turnout) {
9678                LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb);
9679
9680                if (lt != null) {
9681                    lt.setTurnout("");
9682                }
9683
9684                for (LayoutTurnout t : getLayoutTurnouts()) {
9685                    if (t.getLinkedTurnoutName() != null) {
9686                        if (t.getLinkedTurnoutName().equals(nb.getSystemName())
9687                                || ((nb.getUserName() != null) && t.getLinkedTurnoutName().equals(nb.getUserName()))) {
9688                            t.setLinkedTurnoutName("");
9689                        }
9690                    }
9691
9692                    if (nb.equals(t.getSecondTurnout())) {
9693                        t.setSecondTurnout("");
9694                    }
9695                }
9696
9697                for (LayoutSlip sl : getLayoutSlips()) {
9698                    if (nb.equals(sl.getTurnout())) {
9699                        sl.setTurnout("");
9700                    }
9701
9702                    if (nb.equals(sl.getTurnoutB())) {
9703                        sl.setTurnoutB("");
9704                    }
9705                }
9706
9707                for (LayoutTurntable lx : getLayoutTurntables()) {
9708                    if (lx.isTurnoutControlled()) {
9709                        for (int i = 0; i < lx.getNumberRays(); i++) {
9710                            if (nb.equals(lx.getRayTurnout(i))) {
9711                                lx.setRayTurnout(i, null, NamedBean.UNKNOWN);
9712                            }
9713                        }
9714                    }
9715                }
9716
9717                for (LayoutTraverser lx : getLayoutTraversers()) {
9718                    if (lx.isTurnoutControlled()) {
9719                        for (int i = 0; i < lx.getNumberSlots(); i++) {
9720                            if (nb.equals(lx.getSlotTurnout(i))) {
9721                                lx.setSlotTurnout(i, null, NamedBean.UNKNOWN);
9722                            }
9723                        }
9724                    }
9725                }
9726            }
9727
9728            if (nb instanceof SignalMast) {
9729                removeBeanRefs(nb);
9730
9731                if (containsSignalMast((SignalMast) nb)) {
9732                    Iterator<SignalMastIcon> icon = signalMastList.iterator();
9733
9734                    while (icon.hasNext()) {
9735                        SignalMastIcon i = icon.next();
9736
9737                        if (i.getSignalMast().equals(nb)) {
9738                            icon.remove();
9739                            super.removeFromContents(i);
9740                        }
9741                    }
9742                    setDirty();
9743                    redrawPanel();
9744                }
9745            }
9746
9747            if (nb instanceof Sensor) {
9748                removeBeanRefs(nb);
9749                Iterator<SensorIcon> icon = sensorImage.iterator();
9750
9751                while (icon.hasNext()) {
9752                    SensorIcon i = icon.next();
9753
9754                    if (nb.equals(i.getSensor())) {
9755                        icon.remove();
9756                        super.removeFromContents(i);
9757                    }
9758                }
9759                setDirty();
9760                redrawPanel();
9761            }
9762
9763            if (nb instanceof Turnout) {
9764                removeBeanRefs(nb);
9765                Iterator<TurnoutIcon> icon = turnoutImage.iterator();
9766
9767                while (icon.hasNext()) {
9768                    TurnoutIcon i = icon.next();
9769
9770                    if (nb.equals(i.getTurnout())) {
9771                        icon.remove();
9772                        super.removeFromContents(i);
9773                    }
9774                }
9775                setDirty();
9776                redrawPanel();
9777            }
9778
9779            if (nb instanceof Memory) {
9780                Iterator<MemoryIcon> icon = memoryLabelList.iterator();
9781
9782                while (icon.hasNext()) {
9783                    MemoryIcon i = icon.next();
9784
9785                    if (nb.equals(i.getMemory())) {
9786                        icon.remove();
9787                        super.removeFromContents(i);
9788                    }
9789                }
9790
9791                Iterator<MemoryInputIcon> input = memoryInputList.iterator();
9792
9793                while (input.hasNext()) {
9794                    MemoryInputIcon ipt = input.next();
9795
9796                    if (nb.equals(ipt.getMemory())) {
9797                        input.remove();
9798                        super.removeFromContents(ipt);
9799                    }
9800                }
9801            }
9802
9803            if (nb instanceof GlobalVariable) {
9804                Iterator<GlobalVariableIcon> icon = globalVariableLabelList.iterator();
9805
9806                while (icon.hasNext()) {
9807                    GlobalVariableIcon i = icon.next();
9808
9809                    if (nb.equals(i.getGlobalVariable())) {
9810                        icon.remove();
9811                        super.removeFromContents(i);
9812                    }
9813                }
9814            }
9815        }
9816    }
9817
9818    @Override
9819    public void dispose() {
9820        if (leToolBarPanel != null) {
9821            leToolBarPanel.dispose();
9822        }
9823        super.dispose();
9824
9825    }
9826
9827    // package protected
9828    class TurnoutComboBoxPopupMenuListener implements PopupMenuListener {
9829
9830        private final NamedBeanComboBox<Turnout> comboBox;
9831        private final List<Turnout> currentTurnouts;
9832
9833        public TurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) {
9834            this.comboBox = comboBox;
9835            this.currentTurnouts = currentTurnouts;
9836        }
9837
9838        @Override
9839        public void popupMenuWillBecomeVisible(PopupMenuEvent event) {
9840            // This method is called before the popup menu becomes visible.
9841            log.debug("PopupMenuWillBecomeVisible");
9842            Set<Turnout> l = new HashSet<>();
9843            comboBox.getManager().getNamedBeanSet().forEach((turnout) -> {
9844                if (!currentTurnouts.contains(turnout)) {
9845                    if (!validatePhysicalTurnout(turnout.getDisplayName(), null)) {
9846                        l.add(turnout);
9847                    }
9848                }
9849            });
9850            comboBox.setExcludedItems(l);
9851        }
9852
9853        @Override
9854        public void popupMenuWillBecomeInvisible(PopupMenuEvent event) {
9855            // This method is called before the popup menu becomes invisible
9856            log.debug("PopupMenuWillBecomeInvisible");
9857        }
9858
9859        @Override
9860        public void popupMenuCanceled(PopupMenuEvent event) {
9861            // This method is called when the popup menu is canceled
9862            log.debug("PopupMenuCanceled");
9863        }
9864    }
9865
9866    /**
9867     * Create a listener that will exclude turnouts that are present in the
9868     * current panel.
9869     *
9870     * @param comboBox The NamedBeanComboBox that contains the turnout list.
9871     * @return A PopupMenuListener
9872     */
9873    public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox) {
9874        return new TurnoutComboBoxPopupMenuListener(comboBox, new ArrayList<>());
9875    }
9876
9877    /**
9878     * Create a listener that will exclude turnouts that are present in the
9879     * current panel. The list of current turnouts are not excluded.
9880     *
9881     * @param comboBox        The NamedBeanComboBox that contains the turnout
9882     *                        list.
9883     * @param currentTurnouts The turnouts to be left in the turnout list.
9884     * @return A PopupMenuListener
9885     */
9886    public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) {
9887        return new TurnoutComboBoxPopupMenuListener(comboBox, currentTurnouts);
9888    }
9889
9890    List<NamedBeanUsageReport> usageReport;
9891
9892    @Override
9893    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
9894        usageReport = new ArrayList<>();
9895        if (bean != null) {
9896            usageReport = super.getUsageReport(bean);
9897
9898            // LE Specific checks
9899            // Turnouts
9900            findTurnoutUsage(bean);
9901
9902            // Check A, EB, EC for sensors, masts, heads
9903            findPositionalUsage(bean);
9904
9905            // Level Crossings
9906            findXingWhereUsed(bean);
9907
9908            // Track segments
9909            findSegmentWhereUsed(bean);
9910        }
9911        return usageReport;
9912    }
9913
9914    void findTurnoutUsage(NamedBean bean) {
9915        for (LayoutTurnout turnout : getLayoutTurnoutsAndSlips()) {
9916            String data = getUsageData(turnout);
9917
9918            if (bean.equals(turnout.getTurnout())) {
9919                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout", data));
9920            }
9921            if (bean.equals(turnout.getSecondTurnout())) {
9922                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout2", data));
9923            }
9924
9925            if (isLBLockUsed(bean, turnout.getLayoutBlock())) {
9926                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9927            }
9928            if (turnout.hasEnteringDoubleTrack()) {
9929                if (isLBLockUsed(bean, turnout.getLayoutBlockB())) {
9930                    usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9931                }
9932                if (isLBLockUsed(bean, turnout.getLayoutBlockC())) {
9933                    usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9934                }
9935                if (isLBLockUsed(bean, turnout.getLayoutBlockD())) {
9936                    usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data));
9937                }
9938            }
9939
9940            if (bean.equals(turnout.getSensorA())) {
9941                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9942            }
9943            if (bean.equals(turnout.getSensorB())) {
9944                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9945            }
9946            if (bean.equals(turnout.getSensorC())) {
9947                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9948            }
9949            if (bean.equals(turnout.getSensorD())) {
9950                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data));
9951            }
9952
9953            if (bean.equals(turnout.getSignalAMast())) {
9954                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9955            }
9956            if (bean.equals(turnout.getSignalBMast())) {
9957                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9958            }
9959            if (bean.equals(turnout.getSignalCMast())) {
9960                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9961            }
9962            if (bean.equals(turnout.getSignalDMast())) {
9963                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data));
9964            }
9965
9966            if (bean.equals(turnout.getSignalA1())) {
9967                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9968            }
9969            if (bean.equals(turnout.getSignalA2())) {
9970                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9971            }
9972            if (bean.equals(turnout.getSignalA3())) {
9973                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9974            }
9975            if (bean.equals(turnout.getSignalB1())) {
9976                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9977            }
9978            if (bean.equals(turnout.getSignalB2())) {
9979                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9980            }
9981            if (bean.equals(turnout.getSignalC1())) {
9982                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9983            }
9984            if (bean.equals(turnout.getSignalC2())) {
9985                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9986            }
9987            if (bean.equals(turnout.getSignalD1())) {
9988                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9989            }
9990            if (bean.equals(turnout.getSignalD2())) {
9991                usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data));
9992            }
9993        }
9994    }
9995
9996    void findPositionalUsage(NamedBean bean) {
9997        for (PositionablePoint point : getPositionablePoints()) {
9998            String data = getUsageData(point);
9999            if (bean.equals(point.getEastBoundSensor())) {
10000                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data));
10001            }
10002            if (bean.equals(point.getWestBoundSensor())) {
10003                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data));
10004            }
10005            if (bean.equals(point.getEastBoundSignalHead())) {
10006                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data));
10007            }
10008            if (bean.equals(point.getWestBoundSignalHead())) {
10009                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data));
10010            }
10011            if (bean.equals(point.getEastBoundSignalMast())) {
10012                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data));
10013            }
10014            if (bean.equals(point.getWestBoundSignalMast())) {
10015                usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data));
10016            }
10017        }
10018    }
10019
10020    void findSegmentWhereUsed(NamedBean bean) {
10021        for (TrackSegment segment : getTrackSegments()) {
10022            if (isLBLockUsed(bean, segment.getLayoutBlock())) {
10023                String data = getUsageData(segment);
10024                usageReport.add(new NamedBeanUsageReport("LayoutEditorSegmentBlock", data));
10025            }
10026        }
10027    }
10028
10029    void findXingWhereUsed(NamedBean bean) {
10030        for (LevelXing xing : getLevelXings()) {
10031            String data = getUsageData(xing);
10032            if (isLBLockUsed(bean, xing.getLayoutBlockAC())) {
10033                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data));
10034            }
10035            if (isLBLockUsed(bean, xing.getLayoutBlockBD())) {
10036                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data));
10037            }
10038            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTA)) {
10039                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10040            }
10041            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTB)) {
10042                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10043            }
10044            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTC)) {
10045                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10046            }
10047            if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTD)) {
10048                usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data));
10049            }
10050        }
10051    }
10052
10053    String getUsageData(LayoutTrack track) {
10054        LayoutTrackView trackView = getLayoutTrackView(track);
10055        Point2D point = trackView.getCoordsCenter();
10056        if (trackView instanceof TrackSegmentView) {
10057            TrackSegmentView segmentView = (TrackSegmentView) trackView;
10058            point = new Point2D.Double(segmentView.getCentreSegX(), segmentView.getCentreSegY());
10059        }
10060        return String.format("%s :: x=%d, y=%d",
10061                track.getClass().getSimpleName(),
10062                Math.round(point.getX()),
10063                Math.round(point.getY()));
10064    }
10065
10066    boolean isLBLockUsed(NamedBean bean, LayoutBlock lblock) {
10067        boolean result = false;
10068        if (lblock != null) {
10069            if (bean.equals(lblock.getBlock())) {
10070                result = true;
10071            }
10072        }
10073        return result;
10074    }
10075
10076    boolean isUsedInXing(NamedBean bean, LevelXing xing, LevelXing.Geometry point) {
10077        boolean result = false;
10078        if (bean.equals(xing.getSensor(point))) {
10079            result = true;
10080        }
10081        if (bean.equals(xing.getSignalHead(point))) {
10082            result = true;
10083        }
10084        if (bean.equals(xing.getSignalMast(point))) {
10085            result = true;
10086        }
10087        return result;
10088    }
10089
10090    // initialize logging
10091    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditor.class);
10092}