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