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