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