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