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