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