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