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