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 public void actionPerformed(ActionEvent ae){ 3741 if(count % 2 != 0) t.setDisabled(true); 3742 else t.setDisabled(false); 3743 if(++count > 8) timer.stop(); 3744 } 3745 }); 3746 timer.start(); 3747 } 3748 } 3749 } else if ((selectedObject != null) && ((selectedHitPointType == HitPointType.SLIP_LEFT) 3750 || (selectedHitPointType == HitPointType.SLIP_RIGHT)) 3751 && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger() 3752 && !event.isShiftDown() && (!delayedPopupTrigger)) { 3753 // controlling slip out of edit mode 3754 LayoutSlip sl = (LayoutSlip) selectedObject; 3755 sl.toggleState(selectedHitPointType); 3756 } else if ((selectedObject != null) && (HitPointType.isTurntableRayHitType(selectedHitPointType)) 3757 && allControlling() && !event.isMetaDown() && !event.isAltDown() && !event.isPopupTrigger() 3758 && !event.isShiftDown() && (!delayedPopupTrigger)) { 3759 // controlling turntable out of edit mode 3760 LayoutTurntable t = (LayoutTurntable) selectedObject; 3761 t.setPosition(selectedHitPointType.turntableTrackIndex()); 3762 } else if ((event.isPopupTrigger() || delayedPopupTrigger) && (!isDragging)) { 3763 // requesting marker popup out of edit mode 3764 LocoIcon lo = checkMarkerPopUps(dLoc); 3765 if (lo != null) { 3766 showPopUp(lo, event); 3767 } else { 3768 if (findLayoutTracksHitPoint(dLoc)) { 3769 // show popup menu 3770 switch (foundHitPointType) { 3771 case TURNOUT_CENTER: { 3772 if (useDirectTurnoutControl) { 3773 LayoutTurnout t = (LayoutTurnout) foundTrack; 3774 t.setState(Turnout.THROWN); 3775 } else { 3776 foundTrackView.showPopup(event); 3777 } 3778 break; 3779 } 3780 3781 case LEVEL_XING_CENTER: 3782 case SLIP_RIGHT: 3783 case SLIP_LEFT: { 3784 foundTrackView.showPopup(event); 3785 break; 3786 } 3787 3788 default: { 3789 break; 3790 } 3791 } 3792 } 3793 AnalogClock2Display c = checkClockPopUps(dLoc); 3794 if (c != null) { 3795 showPopUp(c, event); 3796 } else { 3797 SignalMastIcon sm = checkSignalMastIconPopUps(dLoc); 3798 if (sm != null) { 3799 showPopUp(sm, event); 3800 } else { 3801 PositionableLabel im = checkLabelImagePopUps(dLoc); 3802 if (im != null) { 3803 showPopUp(im, event); 3804 } 3805 } 3806 } 3807 } 3808 } 3809 3810 if (!event.isPopupTrigger() && !isDragging) { 3811 List<Positionable> selections = getSelectedItems(event); 3812 if (!selections.isEmpty()) { 3813 selections.get(0).doMouseReleased(event); 3814 whenReleased = event.getWhen(); 3815 } 3816 } 3817 3818 // train icon needs to know when moved 3819 if (event.isPopupTrigger() && isDragging) { 3820 List<Positionable> selections = getSelectedItems(event); 3821 if (!selections.isEmpty()) { 3822 selections.get(0).doMouseDragged(event); 3823 } 3824 } 3825 3826 if (selectedObject != null) { 3827 // An object was selected, deselect it 3828 prevSelectedObject = selectedObject; 3829 selectedObject = null; 3830 } 3831 3832 // clear these 3833 beginTrack = null; 3834 foundTrack = null; 3835 foundTrackView = null; 3836 3837 delayedPopupTrigger = false; 3838 3839 if (isDragging) { 3840 resizePanelBounds(true); 3841 isDragging = false; 3842 } 3843 3844 requestFocusInWindow(); 3845 } // mouseReleased 3846 3847 public void addPopupItems(@Nonnull JPopupMenu popup, @Nonnull JmriMouseEvent event) { 3848 3849 List<LayoutTrack> tracks = getLayoutTracks().stream().filter(layoutTrack -> { // != means can't (yet) loop over Views 3850 HitPointType hitPointType = getLayoutTrackView(layoutTrack).findHitPointType(dLoc, false, false); 3851 return (HitPointType.NONE != hitPointType); 3852 }).collect(Collectors.toList()); 3853 3854 List<Positionable> selections = getSelectedItems(event); 3855 3856 if ((tracks.size() > 1) || (selections.size() > 1)) { 3857 JMenu iconsBelowMenu = new JMenu(Bundle.getMessage("MenuItemIconsBelow")); 3858 3859 JMenuItem mi = new JMenuItem(Bundle.getMessage("MenuItemIconsBelow_InfoNotInOrder")); 3860 mi.setEnabled(false); 3861 iconsBelowMenu.add(mi); 3862 3863 if (tracks.size() > 1) { 3864 for (int i=0; i < tracks.size(); i++) { 3865 LayoutTrack t = tracks.get(i); 3866 iconsBelowMenu.add(new AbstractAction(Bundle.getMessage( 3867 "LayoutTrackTypeAndName", t.getTypeName(), t.getName())) { 3868 @Override 3869 public void actionPerformed(ActionEvent e) { 3870 LayoutTrackView ltv = getLayoutTrackView(t); 3871 ltv.showPopup(event); 3872 } 3873 }); 3874 } 3875 } 3876 if (selections.size() > 1) { 3877 for (int i=0; i < selections.size(); i++) { 3878 Positionable pos = selections.get(i); 3879 iconsBelowMenu.add(new AbstractAction(Bundle.getMessage( 3880 "PositionableTypeAndName", pos.getTypeString(), pos.getNameString())) { 3881 @Override 3882 public void actionPerformed(ActionEvent e) { 3883 showPopUp(pos, event, new ArrayList<>()); 3884 } 3885 }); 3886 } 3887 } 3888 popup.addSeparator(); 3889 popup.add(iconsBelowMenu); 3890 } 3891 } 3892 3893 private void showEditPopUps(@Nonnull JmriMouseEvent event) { 3894 if (findLayoutTracksHitPoint(dLoc)) { 3895 if (HitPointType.isBezierHitType(foundHitPointType)) { 3896 getTrackSegmentView((TrackSegment) foundTrack).showBezierPopUp(event, foundHitPointType); 3897 } else if (HitPointType.isTurntableRayHitType(foundHitPointType)) { 3898 LayoutTurntable t = (LayoutTurntable) foundTrack; 3899 if (t.isTurnoutControlled()) { 3900 LayoutTurntableView ltview = getLayoutTurntableView((LayoutTurntable) foundTrack); 3901 ltview.showRayPopUp(event, foundHitPointType.turntableTrackIndex()); 3902 } 3903 } else if (HitPointType.isPopupHitType(foundHitPointType)) { 3904 foundTrackView.showPopup(event); 3905 } else if (HitPointType.isTurnoutHitType(foundHitPointType)) { 3906 // don't curently have edit popup for these 3907 } else { 3908 log.warn("Unknown foundPointType:{}", foundHitPointType); 3909 } 3910 } else { 3911 do { 3912 TrackSegment ts = checkTrackSegmentPopUps(dLoc); 3913 if (ts != null) { 3914 TrackSegmentView tsv = getTrackSegmentView(ts); 3915 tsv.showPopup(event); 3916 break; 3917 } 3918 3919 SensorIcon s = checkSensorIconPopUps(dLoc); 3920 if (s != null) { 3921 showPopUp(s, event); 3922 break; 3923 } 3924 3925 LocoIcon lo = checkMarkerPopUps(dLoc); 3926 if (lo != null) { 3927 showPopUp(lo, event); 3928 break; 3929 } 3930 3931 SignalHeadIcon sh = checkSignalHeadIconPopUps(dLoc); 3932 if (sh != null) { 3933 showPopUp(sh, event); 3934 break; 3935 } 3936 3937 AnalogClock2Display c = checkClockPopUps(dLoc); 3938 if (c != null) { 3939 showPopUp(c, event); 3940 break; 3941 } 3942 3943 MultiSensorIcon ms = checkMultiSensorPopUps(dLoc); 3944 if (ms != null) { 3945 showPopUp(ms, event); 3946 break; 3947 } 3948 3949 PositionableLabel lb = checkLabelImagePopUps(dLoc); 3950 if (lb != null) { 3951 showPopUp(lb, event); 3952 break; 3953 } 3954 3955 PositionableLabel b = checkBackgroundPopUps(dLoc); 3956 if (b != null) { 3957 showPopUp(b, event); 3958 break; 3959 } 3960 3961 SignalMastIcon sm = checkSignalMastIconPopUps(dLoc); 3962 if (sm != null) { 3963 showPopUp(sm, event); 3964 break; 3965 } 3966 LayoutShape ls = checkLayoutShapePopUps(dLoc); 3967 if (ls != null) { 3968 ls.showShapePopUp(event, selectedHitPointType); 3969 break; 3970 } 3971 } while (false); 3972 } 3973 } 3974 3975 /** 3976 * Select the menu items to display for the Positionable's popup. 3977 * @param pos the item containing or requiring the context menu 3978 * @param event the event triggering the menu 3979 */ 3980 public void showPopUp(@Nonnull Positionable pos, @Nonnull JmriMouseEvent event) { 3981 Positionable p = Objects.requireNonNull(pos); 3982 3983 if (!((Component) p).isVisible()) { 3984 return; // component must be showing on the screen to determine its location 3985 } 3986 JPopupMenu popup = new JPopupMenu(); 3987 3988 if (p.isEditable()) { 3989 JMenuItem jmi; 3990 3991 if (showAlignPopup()) { 3992 setShowAlignmentMenu(popup); 3993 popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) { 3994 @Override 3995 public void actionPerformed(ActionEvent event) { 3996 deleteSelectedItems(); 3997 } 3998 }); 3999 } else { 4000 if (p.doViemMenu()) { 4001 String objectType = p.getClass().getName(); 4002 objectType = objectType.substring(objectType.lastIndexOf('.') + 1); 4003 jmi = popup.add(objectType); 4004 jmi.setEnabled(false); 4005 4006 jmi = popup.add(p.getNameString()); 4007 jmi.setEnabled(false); 4008 4009 if (p.isPositionable()) { 4010 setShowCoordinatesMenu(p, popup); 4011 } 4012 setDisplayLevelMenu(p, popup); 4013 setPositionableMenu(p, popup); 4014 } 4015 4016 boolean popupSet = false; 4017 popupSet |= p.setRotateOrthogonalMenu(popup); 4018 popupSet |= p.setRotateMenu(popup); 4019 popupSet |= p.setScaleMenu(popup); 4020 if (popupSet) { 4021 popup.addSeparator(); 4022 popupSet = false; 4023 } 4024 popupSet |= p.setEditIconMenu(popup); 4025 popupSet |= p.setTextEditMenu(popup); 4026 4027 PositionablePopupUtil util = p.getPopupUtility(); 4028 4029 if (util != null) { 4030 util.setFixedTextMenu(popup); 4031 util.setTextMarginMenu(popup); 4032 util.setTextBorderMenu(popup); 4033 util.setTextFontMenu(popup); 4034 util.setBackgroundMenu(popup); 4035 util.setTextJustificationMenu(popup); 4036 util.setTextOrientationMenu(popup); 4037 popup.addSeparator(); 4038 util.propertyUtil(popup); 4039 util.setAdditionalEditPopUpMenu(popup); 4040 popupSet = true; 4041 } 4042 4043 if (popupSet) { 4044 popup.addSeparator(); 4045 // popupSet = false; 4046 } 4047 p.setDisableControlMenu(popup); 4048 setShowAlignmentMenu(popup); 4049 4050 // for Positionables with unique settings 4051 p.showPopUp(popup); 4052 setShowToolTipMenu(p, popup); 4053 4054 setRemoveMenu(p, popup); 4055 4056 if (p.doViemMenu()) { 4057 setHiddenMenu(p, popup); 4058 setEmptyHiddenMenu(p, popup); 4059 setValueEditDisabledMenu(p, popup); 4060 setEditIdMenu(p, popup); 4061 setEditClassesMenu(p, popup); 4062 popup.addSeparator(); 4063 setLogixNGPositionableMenu(p, popup); 4064 } 4065 } 4066 } else { 4067 p.showPopUp(popup); 4068 PositionablePopupUtil util = p.getPopupUtility(); 4069 4070 if (util != null) { 4071 util.setAdditionalViewPopUpMenu(popup); 4072 } 4073 } 4074 4075 addPopupItems(popup, event); 4076 4077 popup.show((Component) p, p.getWidth() / 2 + (int) ((getZoom() - 1.0) * p.getX()), 4078 p.getHeight() / 2 + (int) ((getZoom() - 1.0) * p.getY())); 4079 4080 /*popup.show((Component)pt, event.getX(), event.getY());*/ 4081 } 4082 4083 private long whenReleased = 0; // used to identify event that was popup trigger 4084 private boolean awaitingIconChange = false; 4085 4086 @Override 4087 public void mouseClicked(@Nonnull JmriMouseEvent event) { 4088 // initialize mouse position 4089 calcLocation(event); 4090 4091 if (!isEditable() && _highlightcomponent != null && highlightCursor) { 4092 _highlightcomponent = null; 4093 redrawPanel(); 4094 } 4095 4096 // if alt modifier is down invert the snap to grid behaviour 4097 snapToGridInvert = event.isAltDown(); 4098 4099 if (!event.isMetaDown() && !event.isPopupTrigger() && !event.isAltDown() 4100 && !awaitingIconChange && !event.isShiftDown() && !event.isControlDown()) { 4101 List<Positionable> selections = getSelectedItems(event); 4102 4103 if (!selections.isEmpty()) { 4104 selections.get(0).doMouseClicked(event); 4105 } 4106 } else if (event.isPopupTrigger() && (whenReleased != event.getWhen())) { 4107 4108 if (isEditable()) { 4109 selectedObject = null; 4110 selectedHitPointType = HitPointType.NONE; 4111 showEditPopUps(event); 4112 } else { 4113 LocoIcon lo = checkMarkerPopUps(dLoc); 4114 4115 if (lo != null) { 4116 showPopUp(lo, event); 4117 } 4118 } 4119 } 4120 4121 if (event.isControlDown() && !event.isPopupTrigger()) { 4122 if (findLayoutTracksHitPoint(dLoc)) { 4123 switch (foundHitPointType) { 4124 case POS_POINT: 4125 case TURNOUT_CENTER: 4126 case LEVEL_XING_CENTER: 4127 case SLIP_LEFT: 4128 case SLIP_RIGHT: 4129 case TURNTABLE_CENTER: { 4130 amendSelectionGroup(foundTrack); 4131 break; 4132 } 4133 4134 default: { 4135 break; 4136 } 4137 } 4138 } else { 4139 PositionableLabel s = checkSensorIconPopUps(dLoc); 4140 if (s != null) { 4141 amendSelectionGroup(s); 4142 } else { 4143 PositionableLabel sh = checkSignalHeadIconPopUps(dLoc); 4144 if (sh != null) { 4145 amendSelectionGroup(sh); 4146 } else { 4147 PositionableLabel ms = checkMultiSensorPopUps(dLoc); 4148 if (ms != null) { 4149 amendSelectionGroup(ms); 4150 } else { 4151 PositionableLabel lb = checkLabelImagePopUps(dLoc); 4152 if (lb != null) { 4153 amendSelectionGroup(lb); 4154 } else { 4155 PositionableLabel b = checkBackgroundPopUps(dLoc); 4156 if (b != null) { 4157 amendSelectionGroup(b); 4158 } else { 4159 PositionableLabel sm = checkSignalMastIconPopUps(dLoc); 4160 if (sm != null) { 4161 amendSelectionGroup(sm); 4162 } else { 4163 LayoutShape ls = checkLayoutShapePopUps(dLoc); 4164 if (ls != null) { 4165 amendSelectionGroup(ls); 4166 } 4167 } 4168 } 4169 } 4170 } 4171 } 4172 } 4173 } 4174 } else if ((selectionWidth == 0) || (selectionHeight == 0)) { 4175 clearSelectionGroups(); 4176 } 4177 requestFocusInWindow(); 4178 } 4179 4180 private void checkPointOfPositionable(@Nonnull PositionablePoint p) { 4181 assert p != null; 4182 4183 TrackSegment t = p.getConnect1(); 4184 4185 if (t == null) { 4186 t = p.getConnect2(); 4187 } 4188 4189 // Nothing connected to this bit of track so ignore 4190 if (t == null) { 4191 return; 4192 } 4193 beginTrack = p; 4194 beginHitPointType = HitPointType.POS_POINT; 4195 PositionablePointView pv = getPositionablePointView(p); 4196 Point2D loc = pv.getCoordsCenter(); 4197 4198 if (findLayoutTracksHitPoint(loc, true, p)) { 4199 switch (foundHitPointType) { 4200 case POS_POINT: { 4201 PositionablePoint p2 = (PositionablePoint) foundTrack; 4202 4203 if ((p2.getType() == PositionablePoint.PointType.ANCHOR) && p2.setTrackConnection(t)) { 4204 if (t.getConnect1() == p) { 4205 t.setNewConnect1(p2, foundHitPointType); 4206 } else { 4207 t.setNewConnect2(p2, foundHitPointType); 4208 } 4209 p.removeTrackConnection(t); 4210 4211 if ((p.getConnect1() == null) && (p.getConnect2() == null)) { 4212 removePositionablePoint(p); 4213 } 4214 } 4215 break; 4216 } 4217 case TURNOUT_A: 4218 case TURNOUT_B: 4219 case TURNOUT_C: 4220 case TURNOUT_D: 4221 case SLIP_A: 4222 case SLIP_B: 4223 case SLIP_C: 4224 case SLIP_D: 4225 case LEVEL_XING_A: 4226 case LEVEL_XING_B: 4227 case LEVEL_XING_C: 4228 case LEVEL_XING_D: { 4229 try { 4230 if (foundTrack.getConnection(foundHitPointType) == null) { 4231 foundTrack.setConnection(foundHitPointType, t, HitPointType.TRACK); 4232 4233 if (t.getConnect1() == p) { 4234 t.setNewConnect1(foundTrack, foundHitPointType); 4235 } else { 4236 t.setNewConnect2(foundTrack, foundHitPointType); 4237 } 4238 p.removeTrackConnection(t); 4239 4240 if ((p.getConnect1() == null) && (p.getConnect2() == null)) { 4241 removePositionablePoint(p); 4242 } 4243 } 4244 } catch (JmriException e) { 4245 log.debug("Unable to set location"); 4246 } 4247 break; 4248 } 4249 4250 default: { 4251 if (HitPointType.isTurntableRayHitType(foundHitPointType)) { 4252 LayoutTurntable tt = (LayoutTurntable) foundTrack; 4253 int ray = foundHitPointType.turntableTrackIndex(); 4254 4255 if (tt.getRayConnectIndexed(ray) == null) { 4256 tt.setRayConnect(t, ray); 4257 4258 if (t.getConnect1() == p) { 4259 t.setNewConnect1(tt, foundHitPointType); 4260 } else { 4261 t.setNewConnect2(tt, foundHitPointType); 4262 } 4263 p.removeTrackConnection(t); 4264 4265 if ((p.getConnect1() == null) && (p.getConnect2() == null)) { 4266 removePositionablePoint(p); 4267 } 4268 } 4269 } else { 4270 log.debug("No valid point, so will quit"); 4271 return; 4272 } 4273 break; 4274 } 4275 } 4276 redrawPanel(); 4277 4278 if (t.getLayoutBlock() != null) { 4279 getLEAuxTools().setBlockConnectivityChanged(); 4280 } 4281 } 4282 beginTrack = null; 4283 } 4284 4285 // We just dropped a turnout... see if it will connect to anything 4286 private void hitPointCheckLayoutTurnouts(@Nonnull LayoutTurnout lt) { 4287 beginTrack = lt; 4288 4289 LayoutTurnoutView ltv = getLayoutTurnoutView(lt); 4290 4291 if (lt.getConnectA() == null) { 4292 if (lt instanceof LayoutSlip) { 4293 beginHitPointType = HitPointType.SLIP_A; 4294 } else { 4295 beginHitPointType = HitPointType.TURNOUT_A; 4296 } 4297 dLoc = ltv.getCoordsA(); 4298 hitPointCheckLayoutTurnoutSubs(dLoc); 4299 } 4300 4301 if (lt.getConnectB() == null) { 4302 if (lt instanceof LayoutSlip) { 4303 beginHitPointType = HitPointType.SLIP_B; 4304 } else { 4305 beginHitPointType = HitPointType.TURNOUT_B; 4306 } 4307 dLoc = ltv.getCoordsB(); 4308 hitPointCheckLayoutTurnoutSubs(dLoc); 4309 } 4310 4311 if (lt.getConnectC() == null) { 4312 if (lt instanceof LayoutSlip) { 4313 beginHitPointType = HitPointType.SLIP_C; 4314 } else { 4315 beginHitPointType = HitPointType.TURNOUT_C; 4316 } 4317 dLoc = ltv.getCoordsC(); 4318 hitPointCheckLayoutTurnoutSubs(dLoc); 4319 } 4320 4321 if ((lt.getConnectD() == null) && (lt.isTurnoutTypeXover() || lt.isTurnoutTypeSlip())) { 4322 if (lt instanceof LayoutSlip) { 4323 beginHitPointType = HitPointType.SLIP_D; 4324 } else { 4325 beginHitPointType = HitPointType.TURNOUT_D; 4326 } 4327 dLoc = ltv.getCoordsD(); 4328 hitPointCheckLayoutTurnoutSubs(dLoc); 4329 } 4330 beginTrack = null; 4331 foundTrack = null; 4332 foundTrackView = null; 4333 } 4334 4335 private void hitPointCheckLayoutTurnoutSubs(@Nonnull Point2D dLoc) { 4336 assert dLoc != null; 4337 4338 if (findLayoutTracksHitPoint(dLoc, true)) { 4339 switch (foundHitPointType) { 4340 case POS_POINT: { 4341 PositionablePoint p2 = (PositionablePoint) foundTrack; 4342 4343 if (((p2.getConnect1() == null) && (p2.getConnect2() != null)) 4344 || ((p2.getConnect1() != null) && (p2.getConnect2() == null))) { 4345 TrackSegment t = p2.getConnect1(); 4346 4347 if (t == null) { 4348 t = p2.getConnect2(); 4349 } 4350 4351 if (t == null) { 4352 return; 4353 } 4354 LayoutTurnout lt = (LayoutTurnout) beginTrack; 4355 try { 4356 if (lt.getConnection(beginHitPointType) == null) { 4357 lt.setConnection(beginHitPointType, t, HitPointType.TRACK); 4358 p2.removeTrackConnection(t); 4359 4360 if (t.getConnect1() == p2) { 4361 t.setNewConnect1(lt, beginHitPointType); 4362 } else { 4363 t.setNewConnect2(lt, beginHitPointType); 4364 } 4365 removePositionablePoint(p2); 4366 } 4367 4368 if (t.getLayoutBlock() != null) { 4369 getLEAuxTools().setBlockConnectivityChanged(); 4370 } 4371 } catch (JmriException e) { 4372 log.debug("Unable to set location"); 4373 } 4374 } 4375 break; 4376 } 4377 4378 case TURNOUT_A: 4379 case TURNOUT_B: 4380 case TURNOUT_C: 4381 case TURNOUT_D: 4382 case SLIP_A: 4383 case SLIP_B: 4384 case SLIP_C: 4385 case SLIP_D: { 4386 LayoutTurnout ft = (LayoutTurnout) foundTrack; 4387 addTrackSegment(); 4388 4389 if ((ft.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT) || (ft.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT)) { 4390 rotateTurnout(ft); 4391 } 4392 4393 // Assign a block to the new zero length track segment. 4394 ((LayoutTurnoutView) foundTrackView).setTrackSegmentBlock(foundHitPointType, true); 4395 break; 4396 } 4397 4398 default: { 4399 log.warn("Unexpected foundPointType {} in hitPointCheckLayoutTurnoutSubs", foundHitPointType); 4400 break; 4401 } 4402 } 4403 } 4404 } 4405 4406 private void rotateTurnout(@Nonnull LayoutTurnout t) { 4407 assert t != null; 4408 4409 LayoutTurnoutView tv = getLayoutTurnoutView(t); 4410 4411 LayoutTurnout be = (LayoutTurnout) beginTrack; 4412 LayoutTurnoutView bev = getLayoutTurnoutView(be); 4413 4414 if (((beginHitPointType == HitPointType.TURNOUT_A) && ((be.getConnectB() != null) || (be.getConnectC() != null))) 4415 || ((beginHitPointType == HitPointType.TURNOUT_B) && ((be.getConnectA() != null) || (be.getConnectC() != null))) 4416 || ((beginHitPointType == HitPointType.TURNOUT_C) && ((be.getConnectB() != null) || (be.getConnectA() != null)))) { 4417 return; 4418 } 4419 4420 if ((be.getTurnoutType() != LayoutTurnout.TurnoutType.RH_TURNOUT) && (be.getTurnoutType() != LayoutTurnout.TurnoutType.LH_TURNOUT)) { 4421 return; 4422 } 4423 4424 Point2D c, diverg, xy2; 4425 4426 if ((foundHitPointType == HitPointType.TURNOUT_C) && (beginHitPointType == HitPointType.TURNOUT_C)) { 4427 c = tv.getCoordsA(); 4428 diverg = tv.getCoordsB(); 4429 xy2 = MathUtil.subtract(c, diverg); 4430 } else if ((foundHitPointType == HitPointType.TURNOUT_C) 4431 && ((beginHitPointType == HitPointType.TURNOUT_A) || (beginHitPointType == HitPointType.TURNOUT_B))) { 4432 4433 c = tv.getCoordsCenter(); 4434 diverg = tv.getCoordsC(); 4435 4436 if (beginHitPointType == HitPointType.TURNOUT_A) { 4437 xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA()); 4438 } else { 4439 xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB()); 4440 } 4441 } else if (foundHitPointType == HitPointType.TURNOUT_B) { 4442 c = tv.getCoordsA(); 4443 diverg = tv.getCoordsB(); 4444 4445 switch (beginHitPointType) { 4446 case TURNOUT_B: 4447 xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB()); 4448 break; 4449 case TURNOUT_A: 4450 xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA()); 4451 break; 4452 case TURNOUT_C: 4453 default: 4454 xy2 = MathUtil.subtract(bev.getCoordsCenter(), bev.getCoordsC()); 4455 break; 4456 } 4457 } else if (foundHitPointType == HitPointType.TURNOUT_A) { 4458 c = tv.getCoordsA(); 4459 diverg = tv.getCoordsB(); 4460 4461 switch (beginHitPointType) { 4462 case TURNOUT_A: 4463 xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB()); 4464 break; 4465 case TURNOUT_B: 4466 xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA()); 4467 break; 4468 case TURNOUT_C: 4469 default: 4470 xy2 = MathUtil.subtract(bev.getCoordsC(), bev.getCoordsCenter()); 4471 break; 4472 } 4473 } else { 4474 return; 4475 } 4476 Point2D xy = MathUtil.subtract(diverg, c); 4477 double radius = Math.toDegrees(Math.atan2(xy.getY(), xy.getX())); 4478 double eRadius = Math.toDegrees(Math.atan2(xy2.getY(), xy2.getX())); 4479 bev.rotateCoords(radius - eRadius); 4480 4481 Point2D conCord = bev.getCoordsA(); 4482 Point2D tCord = tv.getCoordsC(); 4483 4484 if (foundHitPointType == HitPointType.TURNOUT_B) { 4485 tCord = tv.getCoordsB(); 4486 } 4487 4488 if (foundHitPointType == HitPointType.TURNOUT_A) { 4489 tCord = tv.getCoordsA(); 4490 } 4491 4492 switch (beginHitPointType) { 4493 case TURNOUT_A: 4494 conCord = bev.getCoordsA(); 4495 break; 4496 case TURNOUT_B: 4497 conCord = bev.getCoordsB(); 4498 break; 4499 case TURNOUT_C: 4500 conCord = bev.getCoordsC(); 4501 break; 4502 default: 4503 break; 4504 } 4505 xy = MathUtil.subtract(conCord, tCord); 4506 Point2D offset = MathUtil.subtract(bev.getCoordsCenter(), xy); 4507 bev.setCoordsCenter(offset); 4508 } 4509 4510 public List<Positionable> _positionableSelection = new ArrayList<>(); 4511 public List<LayoutTrack> _layoutTrackSelection = new ArrayList<>(); 4512 public List<LayoutShape> _layoutShapeSelection = new ArrayList<>(); 4513 4514 @Nonnull 4515 public List<Positionable> getPositionalSelection() { 4516 return _positionableSelection; 4517 } 4518 4519 @Nonnull 4520 public List<LayoutTrack> getLayoutTrackSelection() { 4521 return _layoutTrackSelection; 4522 } 4523 4524 @Nonnull 4525 public List<LayoutShape> getLayoutShapeSelection() { 4526 return _layoutShapeSelection; 4527 } 4528 4529 private void createSelectionGroups() { 4530 Rectangle2D selectionRect = getSelectionRect(); 4531 4532 getContents().forEach((o) -> { 4533 if (selectionRect.contains(o.getLocation())) { 4534 4535 log.trace("found item o of class {}", o.getClass()); 4536 if (!_positionableSelection.contains(o)) { 4537 _positionableSelection.add(o); 4538 } 4539 } 4540 }); 4541 4542 getLayoutTracks().forEach((lt) -> { 4543 LayoutTrackView ltv = getLayoutTrackView(lt); 4544 Point2D center = ltv.getCoordsCenter(); 4545 if (selectionRect.contains(center)) { 4546 if (!_layoutTrackSelection.contains(lt)) { 4547 _layoutTrackSelection.add(lt); 4548 } 4549 } 4550 }); 4551 assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty()); 4552 4553 layoutShapes.forEach((ls) -> { 4554 if (selectionRect.intersects(ls.getBounds())) { 4555 if (!_layoutShapeSelection.contains(ls)) { 4556 _layoutShapeSelection.add(ls); 4557 } 4558 } 4559 }); 4560 redrawPanel(); 4561 } 4562 4563 public void clearSelectionGroups() { 4564 selectionActive = false; 4565 _positionableSelection.clear(); 4566 _layoutTrackSelection.clear(); 4567 assignBlockToSelectionMenuItem.setEnabled(false); 4568 _layoutShapeSelection.clear(); 4569 } 4570 4571 private boolean noWarnGlobalDelete = false; 4572 4573 private void deleteSelectedItems() { 4574 if (!noWarnGlobalDelete) { 4575 int selectedValue = JmriJOptionPane.showOptionDialog(this, 4576 Bundle.getMessage("Question6"), Bundle.getMessage("WarningTitle"), 4577 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 4578 new Object[]{Bundle.getMessage("ButtonYes"), 4579 Bundle.getMessage("ButtonNo"), 4580 Bundle.getMessage("ButtonYesPlus")}, 4581 Bundle.getMessage("ButtonNo")); 4582 4583 // array position 1, ButtonNo or Dialog closed. 4584 if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) { 4585 return; // return without creating if "No" response 4586 } 4587 4588 if (selectedValue == 2) { // array positio 2, ButtonYesPlus 4589 // Suppress future warnings, and continue 4590 noWarnGlobalDelete = true; 4591 } 4592 } 4593 4594 _positionableSelection.forEach(this::remove); 4595 4596 _layoutTrackSelection.forEach((lt) -> { 4597 if (lt instanceof PositionablePoint) { 4598 boolean oldWarning = noWarnPositionablePoint; 4599 noWarnPositionablePoint = true; 4600 removePositionablePoint((PositionablePoint) lt); 4601 noWarnPositionablePoint = oldWarning; 4602 } else if (lt instanceof LevelXing) { 4603 boolean oldWarning = noWarnLevelXing; 4604 noWarnLevelXing = true; 4605 removeLevelXing((LevelXing) lt); 4606 noWarnLevelXing = oldWarning; 4607 } else if (lt instanceof LayoutSlip) { 4608 boolean oldWarning = noWarnSlip; 4609 noWarnSlip = true; 4610 removeLayoutSlip((LayoutSlip) lt); 4611 noWarnSlip = oldWarning; 4612 } else if (lt instanceof LayoutTurntable) { 4613 boolean oldWarning = noWarnTurntable; 4614 noWarnTurntable = true; 4615 removeTurntable((LayoutTurntable) lt); 4616 noWarnTurntable = oldWarning; 4617 } else if (lt instanceof LayoutTurnout) { //<== this includes LayoutSlips 4618 boolean oldWarning = noWarnLayoutTurnout; 4619 noWarnLayoutTurnout = true; 4620 removeLayoutTurnout((LayoutTurnout) lt); 4621 noWarnLayoutTurnout = oldWarning; 4622 } 4623 }); 4624 4625 layoutShapes.removeAll(_layoutShapeSelection); 4626 4627 clearSelectionGroups(); 4628 redrawPanel(); 4629 } 4630 4631 private void amendSelectionGroup(@Nonnull Positionable pos) { 4632 Positionable p = Objects.requireNonNull(pos); 4633 4634 if (_positionableSelection.contains(p)) { 4635 _positionableSelection.remove(p); 4636 } else { 4637 _positionableSelection.add(p); 4638 } 4639 redrawPanel(); 4640 } 4641 4642 public void amendSelectionGroup(@Nonnull LayoutTrack track) { 4643 LayoutTrack p = Objects.requireNonNull(track); 4644 4645 if (_layoutTrackSelection.contains(p)) { 4646 _layoutTrackSelection.remove(p); 4647 } else { 4648 _layoutTrackSelection.add(p); 4649 } 4650 assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty()); 4651 redrawPanel(); 4652 } 4653 4654 public void amendSelectionGroup(@Nonnull LayoutShape shape) { 4655 LayoutShape ls = Objects.requireNonNull(shape); 4656 4657 if (_layoutShapeSelection.contains(ls)) { 4658 _layoutShapeSelection.remove(ls); 4659 } else { 4660 _layoutShapeSelection.add(ls); 4661 } 4662 redrawPanel(); 4663 } 4664 4665 public void alignSelection(boolean alignX) { 4666 Point2D minPoint = MathUtil.infinityPoint2D; 4667 Point2D maxPoint = MathUtil.zeroPoint2D; 4668 Point2D sumPoint = MathUtil.zeroPoint2D; 4669 int cnt = 0; 4670 4671 for (Positionable comp : _positionableSelection) { 4672 if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) { 4673 continue; // skip non-positionables 4674 } 4675 Point2D p = MathUtil.pointToPoint2D(comp.getLocation()); 4676 minPoint = MathUtil.min(minPoint, p); 4677 maxPoint = MathUtil.max(maxPoint, p); 4678 sumPoint = MathUtil.add(sumPoint, p); 4679 cnt++; 4680 } 4681 4682 for (LayoutTrack lt : _layoutTrackSelection) { 4683 LayoutTrackView ltv = getLayoutTrackView(lt); 4684 Point2D p = ltv.getCoordsCenter(); 4685 minPoint = MathUtil.min(minPoint, p); 4686 maxPoint = MathUtil.max(maxPoint, p); 4687 sumPoint = MathUtil.add(sumPoint, p); 4688 cnt++; 4689 } 4690 4691 for (LayoutShape ls : _layoutShapeSelection) { 4692 Point2D p = ls.getCoordsCenter(); 4693 minPoint = MathUtil.min(minPoint, p); 4694 maxPoint = MathUtil.max(maxPoint, p); 4695 sumPoint = MathUtil.add(sumPoint, p); 4696 cnt++; 4697 } 4698 4699 Point2D avePoint = MathUtil.divide(sumPoint, cnt); 4700 int aveX = (int) avePoint.getX(); 4701 int aveY = (int) avePoint.getY(); 4702 4703 for (Positionable comp : _positionableSelection) { 4704 if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) { 4705 continue; // skip non-positionables 4706 } 4707 4708 if (alignX) { 4709 comp.setLocation(aveX, comp.getY()); 4710 } else { 4711 comp.setLocation(comp.getX(), aveY); 4712 } 4713 } 4714 4715 _layoutTrackSelection.forEach((lt) -> { 4716 LayoutTrackView ltv = getLayoutTrackView(lt); 4717 if (alignX) { 4718 ltv.setCoordsCenter(new Point2D.Double(aveX, ltv.getCoordsCenter().getY())); 4719 } else { 4720 ltv.setCoordsCenter(new Point2D.Double(ltv.getCoordsCenter().getX(), aveY)); 4721 } 4722 }); 4723 4724 _layoutShapeSelection.forEach((ls) -> { 4725 if (alignX) { 4726 ls.setCoordsCenter(new Point2D.Double(aveX, ls.getCoordsCenter().getY())); 4727 } else { 4728 ls.setCoordsCenter(new Point2D.Double(ls.getCoordsCenter().getX(), aveY)); 4729 } 4730 }); 4731 4732 redrawPanel(); 4733 } 4734 4735 private boolean showAlignPopup() { 4736 return ((!_positionableSelection.isEmpty()) 4737 || (!_layoutTrackSelection.isEmpty()) 4738 || (!_layoutShapeSelection.isEmpty())); 4739 } 4740 4741 /** 4742 * Offer actions to align the selected Positionable items either 4743 * Horizontally (at average y coord) or Vertically (at average x coord). 4744 * 4745 * @param popup the JPopupMenu to add alignment menu to 4746 * @return true if alignment menu added 4747 */ 4748 public boolean setShowAlignmentMenu(@Nonnull JPopupMenu popup) { 4749 if (showAlignPopup()) { 4750 JMenu edit = new JMenu(Bundle.getMessage("EditAlignment")); 4751 edit.add(new AbstractAction(Bundle.getMessage("AlignX")) { 4752 @Override 4753 public void actionPerformed(ActionEvent event) { 4754 alignSelection(true); 4755 } 4756 }); 4757 edit.add(new AbstractAction(Bundle.getMessage("AlignY")) { 4758 @Override 4759 public void actionPerformed(ActionEvent event) { 4760 alignSelection(false); 4761 } 4762 }); 4763 popup.add(edit); 4764 4765 return true; 4766 } 4767 return false; 4768 } 4769 4770 @Override 4771 public void keyPressed(@Nonnull KeyEvent event) { 4772 if (event.getKeyCode() == KeyEvent.VK_DELETE) { 4773 deleteSelectedItems(); 4774 return; 4775 } 4776 4777 double deltaX = returnDeltaPositionX(event); 4778 double deltaY = returnDeltaPositionY(event); 4779 4780 if ((deltaX != 0) || (deltaY != 0)) { 4781 selectionX += deltaX; 4782 selectionY += deltaY; 4783 4784 Point2D delta = new Point2D.Double(deltaX, deltaY); 4785 _positionableSelection.forEach((c) -> { 4786 Point2D newPoint = c.getLocation(); 4787 if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) { 4788 MemoryIcon pm = (MemoryIcon) c; 4789 newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY()); 4790 } 4791 newPoint = MathUtil.add(newPoint, delta); 4792 newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint); 4793 c.setLocation(MathUtil.point2DToPoint(newPoint)); 4794 }); 4795 4796 _layoutTrackSelection.forEach((lt) -> { 4797 LayoutTrackView ltv = getLayoutTrackView(lt); 4798 Point2D newPoint = MathUtil.add(ltv.getCoordsCenter(), delta); 4799 newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint); 4800 getLayoutTrackView(lt).setCoordsCenter(newPoint); 4801 }); 4802 4803 _layoutShapeSelection.forEach((ls) -> { 4804 Point2D newPoint = MathUtil.add(ls.getCoordsCenter(), delta); 4805 newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint); 4806 ls.setCoordsCenter(newPoint); 4807 }); 4808 redrawPanel(); 4809 return; 4810 } 4811 getLayoutEditorToolBarPanel().keyPressed(event); 4812 } 4813 4814 private double returnDeltaPositionX(@Nonnull KeyEvent event) { 4815 double result = 0.0; 4816 double amount = event.isShiftDown() ? 5.0 : 1.0; 4817 4818 switch (event.getKeyCode()) { 4819 case KeyEvent.VK_LEFT: { 4820 result = -amount; 4821 break; 4822 } 4823 4824 case KeyEvent.VK_RIGHT: { 4825 result = +amount; 4826 break; 4827 } 4828 4829 default: { 4830 break; 4831 } 4832 } 4833 return result; 4834 } 4835 4836 private double returnDeltaPositionY(@Nonnull KeyEvent event) { 4837 double result = 0.0; 4838 double amount = event.isShiftDown() ? 5.0 : 1.0; 4839 4840 switch (event.getKeyCode()) { 4841 case KeyEvent.VK_UP: { 4842 result = -amount; 4843 break; 4844 } 4845 4846 case KeyEvent.VK_DOWN: { 4847 result = +amount; 4848 break; 4849 } 4850 4851 default: { 4852 break; 4853 } 4854 } 4855 return result; 4856 } 4857 4858 int _prevNumSel = 0; 4859 4860 @Override 4861 public void mouseMoved(@Nonnull JmriMouseEvent event) { 4862 // initialize mouse position 4863 calcLocation(event); 4864 4865 // if alt modifier is down invert the snap to grid behaviour 4866 snapToGridInvert = event.isAltDown(); 4867 4868 if (isEditable()) { 4869 leToolBarPanel.setLocationText(dLoc); 4870 } 4871 List<Positionable> selections = getSelectedItems(event); 4872 Positionable selection = null; 4873 int numSel = selections.size(); 4874 4875 if (numSel > 0) { 4876 selection = selections.get(0); 4877 } 4878 4879 if ((selection != null) && (selection.getDisplayLevel() > Editor.BKG) && selection.showToolTip()) { 4880 showToolTip(selection, event); 4881 } else if (_targetPanel.getCursor() != Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)) { 4882 super.setToolTip(null); 4883 } 4884 4885 if (numSel != _prevNumSel) { 4886 redrawPanel(); 4887 _prevNumSel = numSel; 4888 } 4889 4890 if (findLayoutTracksHitPoint(dLoc)) { 4891 // log.debug("foundTrack: {}", foundTrack); 4892 if (HitPointType.isControlHitType(foundHitPointType)) { 4893 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 4894 setTurnoutTooltip(); 4895 } else { 4896 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 4897 } 4898 foundTrack = null; 4899 foundHitPointType = HitPointType.NONE; 4900 } else { 4901 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 4902 } 4903 } // mouseMoved 4904 4905 private void setTurnoutTooltip() { 4906 if (foundTrackView instanceof LayoutTurnoutView) { 4907 var ltv = (LayoutTurnoutView) foundTrackView; 4908 var lt = ltv.getLayoutTurnout(); 4909 if (lt.showToolTip()) { 4910 var tt = lt.getToolTip(); 4911 if (tt != null) { 4912 tt.setText(lt.getNameString()); 4913 var coords = ltv.getCoordsCenter(); 4914 int offsetY = (int) (getTurnoutCircleSize() * SIZE); 4915 tt.setLocation((int) coords.getX(), (int) coords.getY() + offsetY); 4916 setToolTip(tt); 4917 } 4918 } 4919 } 4920 } 4921 4922 public void setAllShowLayoutTurnoutToolTip(boolean state) { 4923 log.debug("setAllShowLayoutTurnoutToolTip: {}", state); 4924 for (LayoutTurnout lt : getLayoutTurnoutsAndSlips()) { 4925 lt.setShowToolTip(state); 4926 } 4927 } 4928 4929 private boolean isDragging = false; 4930 4931 @Override 4932 public void mouseDragged(@Nonnull JmriMouseEvent event) { 4933 // initialize mouse position 4934 calcLocation(event); 4935 4936 checkHighlightCursor(); 4937 4938 // ignore this event if still at the original point 4939 if ((!isDragging) && (xLoc == getAnchorX()) && (yLoc == getAnchorY())) { 4940 return; 4941 } 4942 4943 // if alt modifier is down invert the snap to grid behaviour 4944 snapToGridInvert = event.isAltDown(); 4945 4946 // process this mouse dragged event 4947 if (isEditable()) { 4948 leToolBarPanel.setLocationText(dLoc); 4949 } 4950 currentPoint = MathUtil.add(dLoc, startDelta); 4951 // don't allow negative placement, objects could become unreachable 4952 currentPoint = MathUtil.max(currentPoint, MathUtil.zeroPoint2D); 4953 4954 if ((selectedObject != null) && (event.isMetaDown() || event.isAltDown()) 4955 && (selectedHitPointType == HitPointType.MARKER)) { 4956 // marker moves regardless of editMode or positionable 4957 PositionableLabel pl = (PositionableLabel) selectedObject; 4958 pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY()); 4959 isDragging = true; 4960 redrawPanel(); 4961 return; 4962 } 4963 4964 if (isEditable()) { 4965 if ((selectedObject != null) && event.isMetaDown() && allPositionable()) { 4966 if (snapToGridOnMove != snapToGridInvert) { 4967 // this snaps currentPoint to the grid 4968 currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize()); 4969 xLoc = (int) currentPoint.getX(); 4970 yLoc = (int) currentPoint.getY(); 4971 leToolBarPanel.setLocationText(currentPoint); 4972 } 4973 4974 if ((!_positionableSelection.isEmpty()) 4975 || (!_layoutTrackSelection.isEmpty()) 4976 || (!_layoutShapeSelection.isEmpty())) { 4977 Point2D lastPoint = new Point2D.Double(_lastX, _lastY); 4978 Point2D offset = MathUtil.subtract(currentPoint, lastPoint); 4979 Point2D newPoint; 4980 4981 for (Positionable c : _positionableSelection) { 4982 if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) { 4983 MemoryIcon pm = (MemoryIcon) c; 4984 newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY()); 4985 } else { 4986 newPoint = c.getLocation(); 4987 } 4988 newPoint = MathUtil.add(newPoint, offset); 4989 // don't allow negative placement, objects could become unreachable 4990 newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D); 4991 c.setLocation(MathUtil.point2DToPoint(newPoint)); 4992 } 4993 4994 for (LayoutTrack lt : _layoutTrackSelection) { 4995 LayoutTrackView ltv = getLayoutTrackView(lt); 4996 Point2D center = ltv.getCoordsCenter(); 4997 newPoint = MathUtil.add(center, offset); 4998 // don't allow negative placement, objects could become unreachable 4999 newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D); 5000 getLayoutTrackView(lt).setCoordsCenter(newPoint); 5001 } 5002 5003 for (LayoutShape ls : _layoutShapeSelection) { 5004 Point2D center = ls.getCoordsCenter(); 5005 newPoint = MathUtil.add(center, offset); 5006 // don't allow negative placement, objects could become unreachable 5007 newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D); 5008 ls.setCoordsCenter(newPoint); 5009 } 5010 5011 _lastX = xLoc; 5012 _lastY = yLoc; 5013 } else { 5014 switch (selectedHitPointType) { 5015 case POS_POINT: 5016 case TURNOUT_CENTER: 5017 case LEVEL_XING_CENTER: 5018 case SLIP_LEFT: 5019 case SLIP_RIGHT: 5020 case TURNTABLE_CENTER: { 5021 getLayoutTrackView((LayoutTrack) selectedObject).setCoordsCenter(currentPoint); 5022 isDragging = true; 5023 break; 5024 } 5025 5026 case TURNOUT_A: { 5027 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsA(currentPoint); 5028 break; 5029 } 5030 5031 case TURNOUT_B: { 5032 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsB(currentPoint); 5033 break; 5034 } 5035 5036 case TURNOUT_C: { 5037 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsC(currentPoint); 5038 break; 5039 } 5040 5041 case TURNOUT_D: { 5042 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsD(currentPoint); 5043 break; 5044 } 5045 5046 case LEVEL_XING_A: { 5047 getLevelXingView((LevelXing) selectedObject).setCoordsA(currentPoint); 5048 break; 5049 } 5050 5051 case LEVEL_XING_B: { 5052 getLevelXingView((LevelXing) selectedObject).setCoordsB(currentPoint); 5053 break; 5054 } 5055 5056 case LEVEL_XING_C: { 5057 getLevelXingView((LevelXing) selectedObject).setCoordsC(currentPoint); 5058 break; 5059 } 5060 5061 case LEVEL_XING_D: { 5062 getLevelXingView((LevelXing) selectedObject).setCoordsD(currentPoint); 5063 break; 5064 } 5065 5066 case SLIP_A: { 5067 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsA(currentPoint); 5068 break; 5069 } 5070 5071 case SLIP_B: { 5072 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsB(currentPoint); 5073 break; 5074 } 5075 5076 case SLIP_C: { 5077 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsC(currentPoint); 5078 break; 5079 } 5080 5081 case SLIP_D: { 5082 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsD(currentPoint); 5083 break; 5084 } 5085 5086 case LAYOUT_POS_LABEL: 5087 case MULTI_SENSOR: { 5088 PositionableLabel pl = (PositionableLabel) selectedObject; 5089 5090 if (pl.isPositionable()) { 5091 pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY()); 5092 isDragging = true; 5093 } 5094 break; 5095 } 5096 5097 case LAYOUT_POS_JCOMP: { 5098 PositionableJComponent c = (PositionableJComponent) selectedObject; 5099 5100 if (c.isPositionable()) { 5101 c.setLocation((int) currentPoint.getX(), (int) currentPoint.getY()); 5102 isDragging = true; 5103 } 5104 break; 5105 } 5106 5107 case TRACK_CIRCLE_CENTRE: { 5108 TrackSegmentView tv = getTrackSegmentView((TrackSegment) selectedObject); 5109 tv.reCalculateTrackSegmentAngle(currentPoint.getX(), currentPoint.getY()); 5110 break; 5111 } 5112 5113 default: { 5114 if (HitPointType.isBezierHitType(foundHitPointType)) { 5115 int index = selectedHitPointType.bezierPointIndex(); 5116 getTrackSegmentView((TrackSegment) selectedObject).setBezierControlPoint(currentPoint, index); 5117 } else if ((selectedHitPointType == HitPointType.SHAPE_CENTER)) { 5118 ((LayoutShape) selectedObject).setCoordsCenter(currentPoint); 5119 } else if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) { 5120 int index = selectedHitPointType.shapePointIndex(); 5121 ((LayoutShape) selectedObject).setPoint(index, currentPoint); 5122 } else if (HitPointType.isTurntableRayHitType(selectedHitPointType)) { 5123 LayoutTurntable turn = (LayoutTurntable) selectedObject; 5124 LayoutTurntableView turnView = getLayoutTurntableView(turn); 5125 turnView.setRayCoordsIndexed(currentPoint.getX(), currentPoint.getY(), 5126 selectedHitPointType.turntableTrackIndex()); 5127 } 5128 break; 5129 } 5130 } 5131 } 5132 } else if ((beginTrack != null) 5133 && event.isShiftDown() 5134 && leToolBarPanel.trackButton.isSelected()) { 5135 // dragging from first end of Track Segment 5136 currentLocation = new Point2D.Double(xLoc, yLoc); 5137 boolean needResetCursor = (foundTrack != null); 5138 5139 if (findLayoutTracksHitPoint(currentLocation, true)) { 5140 // have match to free connection point, change cursor 5141 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 5142 } else if (needResetCursor) { 5143 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 5144 } 5145 } else if (event.isShiftDown() 5146 && leToolBarPanel.shapeButton.isSelected() && (selectedObject != null)) { 5147 // dragging from end of shape 5148 currentLocation = new Point2D.Double(xLoc, yLoc); 5149 } else if (selectionActive && !event.isShiftDown() && !event.isMetaDown()) { 5150 selectionWidth = xLoc - selectionX; 5151 selectionHeight = yLoc - selectionY; 5152 } 5153 redrawPanel(); 5154 } else { 5155 Rectangle r = new Rectangle(event.getX(), event.getY(), 1, 1); 5156 ((JComponent) event.getSource()).scrollRectToVisible(r); 5157 } // if (isEditable()) 5158 } // mouseDragged 5159 5160 @Override 5161 public void mouseEntered(@Nonnull JmriMouseEvent event) { 5162 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 5163 } 5164 5165 /** 5166 * Add an Anchor point. 5167 */ 5168 public void addAnchor() { 5169 addAnchor(currentPoint); 5170 } 5171 5172 @Nonnull 5173 public PositionablePoint addAnchor(@Nonnull Point2D point) { 5174 Point2D p = Objects.requireNonNull(point); 5175 5176 // get unique name 5177 String name = finder.uniqueName("A", ++numAnchors); 5178 5179 // create object 5180 PositionablePoint o = new PositionablePoint(name, 5181 PositionablePoint.PointType.ANCHOR, this); 5182 PositionablePointView pv = new PositionablePointView(o, p, this); 5183 addLayoutTrack(o, pv); 5184 5185 setDirty(); 5186 5187 return o; 5188 } 5189 5190 /** 5191 * Add an End Bumper point. 5192 */ 5193 public void addEndBumper() { 5194 // get unique name 5195 String name = finder.uniqueName("EB", ++numEndBumpers); 5196 5197 // create object 5198 PositionablePoint o = new PositionablePoint(name, 5199 PositionablePoint.PointType.END_BUMPER, this); 5200 PositionablePointView pv = new PositionablePointView(o, currentPoint, this); 5201 addLayoutTrack(o, pv); 5202 5203 setDirty(); 5204 } 5205 5206 /** 5207 * Add an Edge Connector point. 5208 */ 5209 public void addEdgeConnector() { 5210 // get unique name 5211 String name = finder.uniqueName("EC", ++numEdgeConnectors); 5212 5213 // create object 5214 PositionablePoint o = new PositionablePoint(name, 5215 PositionablePoint.PointType.EDGE_CONNECTOR, this); 5216 PositionablePointView pv = new PositionablePointView(o, currentPoint, this); 5217 addLayoutTrack(o, pv); 5218 5219 setDirty(); 5220 } 5221 5222 /** 5223 * Add a Track Segment 5224 */ 5225 public void addTrackSegment() { 5226 // get unique name 5227 String name = finder.uniqueName("T", ++numTrackSegments); 5228 5229 // create object 5230 newTrack = new TrackSegment(name, beginTrack, beginHitPointType, 5231 foundTrack, foundHitPointType, 5232 leToolBarPanel.mainlineTrack.isSelected(), this); 5233 5234 TrackSegmentView tsv = new TrackSegmentView( 5235 newTrack, 5236 this 5237 ); 5238 addLayoutTrack(newTrack, tsv); 5239 5240 setDirty(); 5241 5242 // link to connected objects 5243 setLink(beginTrack, beginHitPointType, newTrack, HitPointType.TRACK); 5244 setLink(foundTrack, foundHitPointType, newTrack, HitPointType.TRACK); 5245 5246 // check on layout block 5247 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5248 if (newName == null) { 5249 newName = ""; 5250 } 5251 LayoutBlock b = provideLayoutBlock(newName); 5252 5253 if (b != null) { 5254 newTrack.setLayoutBlock(b); 5255 getLEAuxTools().setBlockConnectivityChanged(); 5256 5257 // check on occupancy sensor 5258 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5259 if (sensorName == null) { 5260 sensorName = ""; 5261 } 5262 5263 if (!sensorName.isEmpty()) { 5264 if (!validateSensor(sensorName, b, this)) { 5265 b.setOccupancySensorName(""); 5266 } else { 5267 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5268 } 5269 } 5270 newTrack.updateBlockInfo(); 5271 } 5272 } 5273 5274 /** 5275 * Add a Level Crossing 5276 */ 5277 public void addLevelXing() { 5278 // get unique name 5279 String name = finder.uniqueName("X", ++numLevelXings); 5280 5281 // create object 5282 LevelXing o = new LevelXing(name, this); 5283 LevelXingView ov = new LevelXingView(o, currentPoint, this); 5284 addLayoutTrack(o, ov); 5285 5286 setDirty(); 5287 5288 // check on layout block 5289 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5290 if (newName == null) { 5291 newName = ""; 5292 } 5293 LayoutBlock b = provideLayoutBlock(newName); 5294 5295 if (b != null) { 5296 o.setLayoutBlockAC(b); 5297 o.setLayoutBlockBD(b); 5298 5299 // check on occupancy sensor 5300 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5301 if (sensorName == null) { 5302 sensorName = ""; 5303 } 5304 5305 if (!sensorName.isEmpty()) { 5306 if (!validateSensor(sensorName, b, this)) { 5307 b.setOccupancySensorName(""); 5308 } else { 5309 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5310 } 5311 } 5312 } 5313 } 5314 5315 /** 5316 * Add a LayoutSlip 5317 * 5318 * @param type the slip type 5319 */ 5320 public void addLayoutSlip(LayoutTurnout.TurnoutType type) { 5321 // get the rotation entry 5322 double rot; 5323 String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim(); 5324 5325 if (s.isEmpty()) { 5326 rot = 0.0; 5327 } else { 5328 try { 5329 rot = IntlUtilities.doubleValue(s); 5330 } catch (ParseException e) { 5331 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " " 5332 + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5333 5334 return; 5335 } 5336 } 5337 5338 // get unique name 5339 String name = finder.uniqueName("SL", ++numLayoutSlips); 5340 5341 // create object 5342 LayoutSlip o; 5343 LayoutSlipView ov; 5344 5345 switch (type) { 5346 case DOUBLE_SLIP: 5347 LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this); 5348 o = lds; 5349 ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this); 5350 break; 5351 case SINGLE_SLIP: 5352 LayoutSingleSlip lss = new LayoutSingleSlip(name, this); 5353 o = lss; 5354 ov = new LayoutSingleSlipView(lss, currentPoint, rot, this); 5355 break; 5356 default: 5357 log.error("can't create slip {} with type {}", name, type); 5358 return; // without creating 5359 } 5360 5361 addLayoutTrack(o, ov); 5362 5363 setDirty(); 5364 5365 // check on layout block 5366 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5367 if (newName == null) { 5368 newName = ""; 5369 } 5370 LayoutBlock b = provideLayoutBlock(newName); 5371 5372 if (b != null) { 5373 ov.setLayoutBlock(b); 5374 5375 // check on occupancy sensor 5376 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5377 if (sensorName == null) { 5378 sensorName = ""; 5379 } 5380 5381 if (!sensorName.isEmpty()) { 5382 if (!validateSensor(sensorName, b, this)) { 5383 b.setOccupancySensorName(""); 5384 } else { 5385 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5386 } 5387 } 5388 } 5389 5390 String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName(); 5391 if (turnoutName == null) { 5392 turnoutName = ""; 5393 } 5394 5395 if (validatePhysicalTurnout(turnoutName, this)) { 5396 // turnout is valid and unique. 5397 o.setTurnout(turnoutName); 5398 5399 if (o.getTurnout().getSystemName().equals(turnoutName)) { 5400 leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout()); 5401 } 5402 } else { 5403 o.setTurnout(""); 5404 leToolBarPanel.turnoutNameComboBox.setSelectedItem(null); 5405 leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1); 5406 } 5407 turnoutName = leToolBarPanel.extraTurnoutNameComboBox.getSelectedItemDisplayName(); 5408 if (turnoutName == null) { 5409 turnoutName = ""; 5410 } 5411 5412 if (validatePhysicalTurnout(turnoutName, this)) { 5413 // turnout is valid and unique. 5414 o.setTurnoutB(turnoutName); 5415 5416 if (o.getTurnoutB().getSystemName().equals(turnoutName)) { 5417 leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(o.getTurnoutB()); 5418 } 5419 } else { 5420 o.setTurnoutB(""); 5421 leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(null); 5422 leToolBarPanel.extraTurnoutNameComboBox.setSelectedIndex(-1); 5423 } 5424 } 5425 5426 /** 5427 * Add a Layout Turnout 5428 * 5429 * @param type the turnout type 5430 */ 5431 public void addLayoutTurnout(LayoutTurnout.TurnoutType type) { 5432 // get the rotation entry 5433 double rot; 5434 String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim(); 5435 5436 if (s.isEmpty()) { 5437 rot = 0.0; 5438 } else { 5439 try { 5440 rot = IntlUtilities.doubleValue(s); 5441 } catch (ParseException e) { 5442 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " " 5443 + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5444 5445 return; 5446 } 5447 } 5448 5449 // get unique name 5450 String name = finder.uniqueName("TO", ++numLayoutTurnouts); 5451 5452 // create object - check all types, although not clear all actually reach here 5453 LayoutTurnout o; 5454 LayoutTurnoutView ov; 5455 5456 switch (type) { 5457 5458 case RH_TURNOUT: 5459 LayoutRHTurnout lrht = new LayoutRHTurnout(name, this); 5460 o = lrht; 5461 ov = new LayoutRHTurnoutView(lrht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5462 break; 5463 case LH_TURNOUT: 5464 LayoutLHTurnout llht = new LayoutLHTurnout(name, this); 5465 o = llht; 5466 ov = new LayoutLHTurnoutView(llht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5467 break; 5468 case WYE_TURNOUT: 5469 LayoutWye lw = new LayoutWye(name, this); 5470 o = lw; 5471 ov = new LayoutWyeView(lw, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5472 break; 5473 case DOUBLE_XOVER: 5474 LayoutDoubleXOver ldx = new LayoutDoubleXOver(name, this); 5475 o = ldx; 5476 ov = new LayoutDoubleXOverView(ldx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5477 break; 5478 case RH_XOVER: 5479 LayoutRHXOver lrx = new LayoutRHXOver(name, this); 5480 o = lrx; 5481 ov = new LayoutRHXOverView(lrx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5482 break; 5483 case LH_XOVER: 5484 LayoutLHXOver llx = new LayoutLHXOver(name, this); 5485 o = llx; 5486 ov = new LayoutLHXOverView(llx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5487 break; 5488 5489 case DOUBLE_SLIP: 5490 LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this); 5491 o = lds; 5492 ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this); 5493 log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name); 5494 break; 5495 case SINGLE_SLIP: 5496 LayoutSingleSlip lss = new LayoutSingleSlip(name, this); 5497 o = lss; 5498 ov = new LayoutSingleSlipView(lss, currentPoint, rot, this); 5499 log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name); 5500 break; 5501 5502 default: 5503 log.error("can't create LayoutTrack {} with type {}", name, type); 5504 return; // without creating 5505 } 5506 5507 addLayoutTrack(o, ov); 5508 5509 setDirty(); 5510 5511 // check on layout block 5512 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5513 if (newName == null) { 5514 newName = ""; 5515 } 5516 LayoutBlock b = provideLayoutBlock(newName); 5517 5518 if (b != null) { 5519 ov.setLayoutBlock(b); 5520 5521 // check on occupancy sensor 5522 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5523 if (sensorName == null) { 5524 sensorName = ""; 5525 } 5526 5527 if (!sensorName.isEmpty()) { 5528 if (!validateSensor(sensorName, b, this)) { 5529 b.setOccupancySensorName(""); 5530 } else { 5531 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5532 } 5533 } 5534 } 5535 5536 // set default continuing route Turnout State 5537 o.setContinuingSense(Turnout.CLOSED); 5538 5539 // check on a physical turnout 5540 String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName(); 5541 if (turnoutName == null) { 5542 turnoutName = ""; 5543 } 5544 5545 if (validatePhysicalTurnout(turnoutName, this)) { 5546 // turnout is valid and unique. 5547 o.setTurnout(turnoutName); 5548 5549 if (o.getTurnout().getSystemName().equals(turnoutName)) { 5550 leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout()); 5551 } 5552 } else { 5553 o.setTurnout(""); 5554 leToolBarPanel.turnoutNameComboBox.setSelectedItem(null); 5555 leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1); 5556 } 5557 } 5558 5559 /** 5560 * Validates that a physical turnout exists and is unique among Layout 5561 * Turnouts Returns true if valid turnout was entered, false otherwise 5562 * 5563 * @param inTurnoutName the (system or user) name of the turnout 5564 * @param inOpenPane the pane over which to show dialogs (null to 5565 * suppress dialogs) 5566 * @return true if valid 5567 */ 5568 public boolean validatePhysicalTurnout( 5569 @Nonnull String inTurnoutName, 5570 @CheckForNull Component inOpenPane) { 5571 // check if turnout name was entered 5572 if (inTurnoutName.isEmpty()) { 5573 // no turnout entered 5574 return false; 5575 } 5576 5577 // check that the unique turnout name corresponds to a defined physical turnout 5578 Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(inTurnoutName); 5579 if (t == null) { 5580 // There is no turnout corresponding to this name 5581 if (inOpenPane != null) { 5582 JmriJOptionPane.showMessageDialog(inOpenPane, 5583 MessageFormat.format(Bundle.getMessage("Error8"), inTurnoutName), 5584 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5585 } 5586 return false; 5587 } 5588 5589 log.debug("validatePhysicalTurnout('{}')", inTurnoutName); 5590 boolean result = true; // assume success (optimist!) 5591 5592 // ensure that this turnout is unique among Layout Turnouts in this Layout 5593 for (LayoutTurnout lt : getLayoutTurnouts()) { 5594 t = lt.getTurnout(); 5595 if (t != null) { 5596 String sname = t.getSystemName(); 5597 String uname = t.getUserName(); 5598 log.debug("{}: Turnout tested '{}' and '{}'.", lt.getName(), sname, uname); 5599 if ((sname.equals(inTurnoutName)) 5600 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5601 result = false; 5602 break; 5603 } 5604 } 5605 5606 // Only check for the second turnout if the type is a double cross over 5607 // otherwise the second turnout is used to throw an additional turnout at 5608 // the same time. 5609 if (lt.isTurnoutTypeXover()) { 5610 t = lt.getSecondTurnout(); 5611 if (t != null) { 5612 String sname = t.getSystemName(); 5613 String uname = t.getUserName(); 5614 log.debug("{}: 2nd Turnout tested '{}' and '{}'.", lt.getName(), sname, uname); 5615 if ((sname.equals(inTurnoutName)) 5616 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5617 result = false; 5618 break; 5619 } 5620 } 5621 } 5622 } 5623 5624 if (result) { // only need to test slips if we haven't failed yet... 5625 // ensure that this turnout is unique among Layout slips in this Layout 5626 for (LayoutSlip sl : getLayoutSlips()) { 5627 t = sl.getTurnout(); 5628 if (t != null) { 5629 String sname = t.getSystemName(); 5630 String uname = t.getUserName(); 5631 log.debug("{}: slip Turnout tested '{}' and '{}'.", sl.getName(), sname, uname); 5632 if ((sname.equals(inTurnoutName)) 5633 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5634 result = false; 5635 break; 5636 } 5637 } 5638 5639 t = sl.getTurnoutB(); 5640 if (t != null) { 5641 String sname = t.getSystemName(); 5642 String uname = t.getUserName(); 5643 log.debug("{}: slip Turnout B tested '{}' and '{}'.", sl.getName(), sname, uname); 5644 if ((sname.equals(inTurnoutName)) 5645 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5646 result = false; 5647 break; 5648 } 5649 } 5650 } 5651 } 5652 5653 if (result) { // only need to test Turntable turnouts if we haven't failed yet... 5654 // ensure that this turntable turnout is unique among turnouts in this Layout 5655 for (LayoutTurntable tt : getLayoutTurntables()) { 5656 for (LayoutTurntable.RayTrack ray : tt.getRayTrackList()) { 5657 t = ray.getTurnout(); 5658 if (t != null) { 5659 String sname = t.getSystemName(); 5660 String uname = t.getUserName(); 5661 log.debug("{}: Turntable turnout tested '{}' and '{}'.", ray.getTurnoutName(), sname, uname); 5662 if ((sname.equals(inTurnoutName)) 5663 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5664 result = false; 5665 break; 5666 } 5667 } 5668 } 5669 } 5670 } 5671 5672 if (!result && (inOpenPane != null)) { 5673 JmriJOptionPane.showMessageDialog(inOpenPane, 5674 MessageFormat.format(Bundle.getMessage("Error4"), inTurnoutName), 5675 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5676 } 5677 return result; 5678 } 5679 5680 /** 5681 * link the 'from' object and type to the 'to' object and type 5682 * 5683 * @param fromObject the object to link from 5684 * @param fromPointType the object type to link from 5685 * @param toObject the object to link to 5686 * @param toPointType the object type to link to 5687 */ 5688 public void setLink(@Nonnull LayoutTrack fromObject, HitPointType fromPointType, 5689 @Nonnull LayoutTrack toObject, HitPointType toPointType) { 5690 switch (fromPointType) { 5691 case POS_POINT: { 5692 if ((toPointType == HitPointType.TRACK) && (fromObject instanceof PositionablePoint)) { 5693 ((PositionablePoint) fromObject).setTrackConnection((TrackSegment) toObject); 5694 } else { 5695 log.error("Attempt to link a non-TRACK connection ('{}')to a Positionable Point ('{}')", 5696 toObject.getName(), fromObject.getName()); 5697 } 5698 break; 5699 } 5700 5701 case TURNOUT_A: 5702 case TURNOUT_B: 5703 case TURNOUT_C: 5704 case TURNOUT_D: 5705 case SLIP_A: 5706 case SLIP_B: 5707 case SLIP_C: 5708 case SLIP_D: 5709 case LEVEL_XING_A: 5710 case LEVEL_XING_B: 5711 case LEVEL_XING_C: 5712 case LEVEL_XING_D: { 5713 try { 5714 fromObject.setConnection(fromPointType, toObject, toPointType); 5715 } catch (JmriException e) { 5716 // ignore (log.error in setConnection method) 5717 } 5718 break; 5719 } 5720 5721 case TRACK: { 5722 // should never happen, Track Segment links are set in ctor 5723 log.error("Illegal request to set a Track Segment link"); 5724 break; 5725 } 5726 5727 default: { 5728 if (HitPointType.isTurntableRayHitType(fromPointType) && (fromObject instanceof LayoutTurntable)) { 5729 if (toObject instanceof TrackSegment) { 5730 ((LayoutTurntable) fromObject).setRayConnect((TrackSegment) toObject, 5731 fromPointType.turntableTrackIndex()); 5732 } else { 5733 log.warn("setLink found expected toObject type {} with fromPointType {} fromObject type {}", 5734 toObject.getClass(), fromPointType, fromObject.getClass(), new Exception("traceback")); 5735 } 5736 } else { 5737 log.warn("setLink found expected fromObject type {} with fromPointType {} toObject type {}", 5738 fromObject.getClass(), fromPointType, toObject.getClass(), new Exception("traceback")); 5739 } 5740 break; 5741 } 5742 } 5743 } 5744 5745 /** 5746 * Return a layout block with the entered name, creating a new one if 5747 * needed. Note that the entered name becomes the user name of the 5748 * LayoutBlock, and a system name is automatically created by 5749 * LayoutBlockManager if needed. 5750 * <p> 5751 * If the block name is a system name, then the user will have to supply a 5752 * user name for the block. 5753 * <p> 5754 * Some, but not all, errors pop a Swing error dialog in addition to 5755 * logging. 5756 * 5757 * @param inBlockName the entered name 5758 * @return the provided LayoutBlock 5759 */ 5760 public LayoutBlock provideLayoutBlock(@Nonnull String inBlockName) { 5761 LayoutBlock result = null; // assume failure (pessimist!) 5762 LayoutBlock newBlk = null; // assume failure (pessimist!) 5763 5764 if (inBlockName.isEmpty()) { 5765 // nothing entered, try autoAssign 5766 if (autoAssignBlocks) { 5767 newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(); 5768 if (null == newBlk) { 5769 log.error("provideLayoutBlock: Failure to auto-assign for empty LayoutBlock name"); 5770 } 5771 } else { 5772 log.debug("provideLayoutBlock: no name given and not assigning auto block names"); 5773 } 5774 } else { 5775 // check if this Layout Block already exists 5776 result = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(inBlockName); 5777 if (result == null) { //(no) 5778 // The combo box name can be either a block system name or a block user name 5779 Block checkBlock = InstanceManager.getDefault(BlockManager.class).getBlock(inBlockName); 5780 if (checkBlock == null) { 5781 log.error("provideLayoutBlock: The block name '{}' does not return a block.", inBlockName); 5782 } else { 5783 String checkUserName = checkBlock.getUserName(); 5784 if (checkUserName != null && checkUserName.equals(inBlockName)) { 5785 // Go ahead and use the name for the layout block 5786 newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, inBlockName); 5787 if (newBlk == null) { 5788 log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}'.", inBlockName); 5789 } 5790 } else { 5791 // Appears to be a system name, request a user name 5792 String blkUserName = (String)JmriJOptionPane.showInputDialog(getTargetFrame(), 5793 Bundle.getMessage("BlkUserNameMsg"), 5794 Bundle.getMessage("BlkUserNameTitle"), 5795 JmriJOptionPane.PLAIN_MESSAGE, null, null, ""); 5796 if (blkUserName != null && !blkUserName.isEmpty()) { 5797 // Verify the user name 5798 Block checkDuplicate = InstanceManager.getDefault(BlockManager.class).getByUserName(blkUserName); 5799 if (checkDuplicate != null) { 5800 JmriJOptionPane.showMessageDialog(getTargetFrame(), 5801 Bundle.getMessage("BlkUserNameInUse", blkUserName), 5802 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5803 } else { 5804 // OK to use as a block user name 5805 checkBlock.setUserName(blkUserName); 5806 newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, blkUserName); 5807 if (newBlk == null) { 5808 log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}' with a new user name.", blkUserName); 5809 } 5810 } 5811 } 5812 } 5813 } 5814 } 5815 } 5816 5817 // if we created a new block 5818 if (newBlk != null) { 5819 // initialize the new block 5820 // log.debug("provideLayoutBlock :: Init new block {}", inBlockName); 5821 newBlk.initializeLayoutBlock(); 5822 newBlk.initializeLayoutBlockRouting(); 5823 newBlk.setBlockTrackColor(defaultTrackColor); 5824 newBlk.setBlockOccupiedColor(defaultOccupiedTrackColor); 5825 newBlk.setBlockExtraColor(defaultAlternativeTrackColor); 5826 result = newBlk; 5827 } 5828 5829 if (result != null) { 5830 // set both new and previously existing block 5831 result.addLayoutEditor(this); 5832 result.incrementUse(); 5833 setDirty(); 5834 } 5835 return result; 5836 } 5837 5838 /** 5839 * Validates that the supplied occupancy sensor name corresponds to an 5840 * existing sensor and is unique among all blocks. If valid, returns true 5841 * and sets the block sensor name in the block. Else returns false, and does 5842 * nothing to the block. 5843 * 5844 * @param sensorName the sensor name to validate 5845 * @param blk the LayoutBlock in which to set it 5846 * @param openFrame the frame (Component) it is in 5847 * @return true if sensor is valid 5848 */ 5849 public boolean validateSensor( 5850 @Nonnull String sensorName, 5851 @Nonnull LayoutBlock blk, 5852 @Nonnull Component openFrame) { 5853 boolean result = false; // assume failure (pessimist!) 5854 5855 // check if anything entered 5856 if (!sensorName.isEmpty()) { 5857 // get a validated sensor corresponding to this name and assigned to block 5858 if (blk.getOccupancySensorName().equals(sensorName)) { 5859 result = true; 5860 } else { 5861 Sensor s = blk.validateSensor(sensorName, openFrame); 5862 result = (s != null); // if sensor returned result is true. 5863 } 5864 } 5865 return result; 5866 } 5867 5868 /** 5869 * Return a layout block with the given name if one exists. Registers this 5870 * LayoutEditor with the layout block. This method is designed to be used 5871 * when a panel is loaded. The calling method must handle whether the use 5872 * count should be incremented. 5873 * 5874 * @param blockID the given name 5875 * @return null if blockID does not already exist 5876 */ 5877 public LayoutBlock getLayoutBlock(@Nonnull String blockID) { 5878 // check if this Layout Block already exists 5879 LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(blockID); 5880 if (blk == null) { 5881 log.error("LayoutBlock '{}' not found when panel loaded", blockID); 5882 return null; 5883 } 5884 blk.addLayoutEditor(this); 5885 return blk; 5886 } 5887 5888 /** 5889 * Remove object from all Layout Editor temporary lists of items not part of 5890 * track schematic 5891 * 5892 * @param s the object to remove 5893 * @return true if found 5894 */ 5895 private boolean remove(@Nonnull Object s) { 5896 boolean found = false; 5897 5898 if (backgroundImage.contains(s)) { 5899 backgroundImage.remove(s); 5900 found = true; 5901 } 5902 if (memoryLabelList.contains(s)) { 5903 memoryLabelList.remove(s); 5904 found = true; 5905 } 5906 if (globalVariableLabelList.contains(s)) { 5907 globalVariableLabelList.remove(s); 5908 found = true; 5909 } 5910 if (blockContentsLabelList.contains(s)) { 5911 blockContentsLabelList.remove(s); 5912 found = true; 5913 } 5914 if (multiSensors.contains(s)) { 5915 multiSensors.remove(s); 5916 found = true; 5917 } 5918 if (clocks.contains(s)) { 5919 clocks.remove(s); 5920 found = true; 5921 } 5922 if (labelImage.contains(s)) { 5923 labelImage.remove(s); 5924 found = true; 5925 } 5926 5927 if (sensorImage.contains(s) || sensorList.contains(s)) { 5928 Sensor sensor = ((SensorIcon) s).getSensor(); 5929 if (sensor != null) { 5930 if (removeAttachedBean((sensor))) { 5931 sensorImage.remove(s); 5932 sensorList.remove(s); 5933 found = true; 5934 } else { 5935 return false; 5936 } 5937 } 5938 } 5939 5940 if (signalHeadImage.contains(s) || signalList.contains(s)) { 5941 SignalHead head = ((SignalHeadIcon) s).getSignalHead(); 5942 if (head != null) { 5943 if (removeAttachedBean((head))) { 5944 signalHeadImage.remove(s); 5945 signalList.remove(s); 5946 found = true; 5947 } else { 5948 return false; 5949 } 5950 } 5951 } 5952 5953 if (signalMastList.contains(s)) { 5954 SignalMast mast = ((SignalMastIcon) s).getSignalMast(); 5955 if (mast != null) { 5956 if (removeAttachedBean((mast))) { 5957 signalMastList.remove(s); 5958 found = true; 5959 } else { 5960 return false; 5961 } 5962 } 5963 } 5964 5965 super.removeFromContents((Positionable) s); 5966 5967 if (found) { 5968 setDirty(); 5969 redrawPanel(); 5970 } 5971 return found; 5972 } 5973 5974 @Override 5975 public boolean removeFromContents(@Nonnull Positionable l) { 5976 return remove(l); 5977 } 5978 5979 private String findBeanUsage(@Nonnull NamedBean bean) { 5980 PositionablePoint pe; 5981 PositionablePoint pw; 5982 LayoutTurnout lt; 5983 LevelXing lx; 5984 LayoutSlip ls; 5985 boolean found = false; 5986 StringBuilder sb = new StringBuilder(); 5987 String msgKey = "DeleteReference"; // NOI18N 5988 String beanKey = "None"; // NOI18N 5989 String beanValue = bean.getDisplayName(); 5990 5991 if (bean instanceof SignalMast) { 5992 beanKey = "BeanNameSignalMast"; // NOI18N 5993 5994 if (InstanceManager.getDefault(SignalMastLogicManager.class).isSignalMastUsed((SignalMast) bean)) { 5995 SignalMastLogic sml = InstanceManager.getDefault( 5996 SignalMastLogicManager.class).getSignalMastLogic((SignalMast) bean); 5997 if ((sml != null) && sml.useLayoutEditor(sml.getDestinationList().get(0))) { 5998 msgKey = "DeleteSmlReference"; // NOI18N 5999 } 6000 } 6001 } else if (bean instanceof Sensor) { 6002 beanKey = "BeanNameSensor"; // NOI18N 6003 } else if (bean instanceof SignalHead) { 6004 beanKey = "BeanNameSignalHead"; // NOI18N 6005 } 6006 if (!beanKey.equals("None")) { // NOI18N 6007 sb.append(Bundle.getMessage(msgKey, Bundle.getMessage(beanKey), beanValue)); 6008 } 6009 6010 if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) { 6011 TrackSegment t1 = pw.getConnect1(); 6012 TrackSegment t2 = pw.getConnect2(); 6013 if (t1 != null) { 6014 if (t2 != null) { 6015 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 6016 sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName())); // NOI18N 6017 } else { 6018 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 6019 } 6020 } 6021 found = true; 6022 } 6023 6024 if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) { 6025 TrackSegment t1 = pe.getConnect1(); 6026 TrackSegment t2 = pe.getConnect2(); 6027 6028 if (t1 != null) { 6029 if (t2 != null) { 6030 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 6031 sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName())); // NOI18N 6032 } else { 6033 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 6034 } 6035 } 6036 found = true; 6037 } 6038 6039 if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) { 6040 sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("BeanNameTurnout"), lt.getTurnoutName())); // NOI18N 6041 found = true; 6042 } 6043 6044 if ((lx = finder.findLevelXingByBean(bean)) != null) { 6045 sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("LevelCrossing"), lx.getId())); // NOI18N 6046 found = true; 6047 } 6048 6049 if ((ls = finder.findLayoutSlipByBean(bean)) != null) { 6050 sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("Slip"), ls.getTurnoutName())); // NOI18N 6051 found = true; 6052 } 6053 6054 if (!found) { 6055 return null; 6056 } 6057 return sb.toString(); 6058 } 6059 6060 /** 6061 * NX Sensors, Signal Heads and Signal Masts can be attached to positional 6062 * points, turnouts and level crossings. If an attachment exists, present an 6063 * option to cancel the remove action, remove the attachement or retain the 6064 * attachment. 6065 * 6066 * @param bean The named bean to be removed. 6067 * @return true if OK to remove the related icon. 6068 */ 6069 private boolean removeAttachedBean(@Nonnull NamedBean bean) { 6070 String usage = findBeanUsage(bean); 6071 6072 if (usage != null) { 6073 usage = String.format("<html>%s</html>", usage); 6074 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6075 usage, Bundle.getMessage("WarningTitle"), 6076 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6077 new Object[]{Bundle.getMessage("ButtonYes"), 6078 Bundle.getMessage("ButtonNo"), 6079 Bundle.getMessage("ButtonCancel")}, 6080 Bundle.getMessage("ButtonYes")); 6081 6082 if (selectedValue == 1 ) { // array pos 1, No 6083 return true; // return leaving the references in place but allow the icon to be deleted. 6084 } 6085 // array pos 2, cancel or Dialog closed 6086 if (selectedValue == 2 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) { 6087 return false; // do not delete the item 6088 } 6089 if (bean instanceof Sensor) { 6090 // Additional actions for NX sensor pairs 6091 return getLETools().removeSensorAssignment((Sensor) bean); 6092 } else { 6093 removeBeanRefs(bean); 6094 } 6095 } 6096 return true; 6097 } 6098 6099 private void removeBeanRefs(@Nonnull NamedBean bean) { 6100 PositionablePoint pe; 6101 PositionablePoint pw; 6102 LayoutTurnout lt; 6103 LevelXing lx; 6104 LayoutSlip ls; 6105 6106 if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) { 6107 pw.removeBeanReference(bean); 6108 } 6109 6110 if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) { 6111 pe.removeBeanReference(bean); 6112 } 6113 6114 if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) { 6115 lt.removeBeanReference(bean); 6116 } 6117 6118 if ((lx = finder.findLevelXingByBean(bean)) != null) { 6119 lx.removeBeanReference(bean); 6120 } 6121 6122 if ((ls = finder.findLayoutSlipByBean(bean)) != null) { 6123 ls.removeBeanReference(bean); 6124 } 6125 } 6126 6127 private boolean noWarnPositionablePoint = false; 6128 6129 /** 6130 * Remove a PositionablePoint -- an Anchor or an End Bumper. 6131 * 6132 * @param o the PositionablePoint to remove 6133 * @return true if removed 6134 */ 6135 public boolean removePositionablePoint(@Nonnull PositionablePoint o) { 6136 // First verify with the user that this is really wanted, only show message if there is a bit of track connected 6137 if ((o.getConnect1() != null) || (o.getConnect2() != null)) { 6138 if (!noWarnPositionablePoint) { 6139 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6140 Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"), 6141 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6142 new Object[]{Bundle.getMessage("ButtonYes"), 6143 Bundle.getMessage("ButtonNo"), 6144 Bundle.getMessage("ButtonYesPlus")}, 6145 Bundle.getMessage("ButtonNo")); 6146 6147 // array position 1, ButtonNo , or Dialog Closed. 6148 if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) { 6149 return false; // return without creating if "No" response 6150 } 6151 6152 if (selectedValue == 2) { // array position 2, ButtonYesPlus 6153 // Suppress future warnings, and continue 6154 noWarnPositionablePoint = true; 6155 } 6156 } 6157 6158 // remove from selection information 6159 if (selectedObject == o) { 6160 selectedObject = null; 6161 } 6162 6163 if (prevSelectedObject == o) { 6164 prevSelectedObject = null; 6165 } 6166 6167 // remove connections if any 6168 TrackSegment t1 = o.getConnect1(); 6169 TrackSegment t2 = o.getConnect2(); 6170 6171 if (t1 != null) { 6172 removeTrackSegment(t1); 6173 } 6174 6175 if (t2 != null) { 6176 removeTrackSegment(t2); 6177 } 6178 6179 // delete from array 6180 } 6181 6182 return removeLayoutTrackAndRedraw(o); 6183 } 6184 6185 private boolean noWarnLayoutTurnout = false; 6186 6187 /** 6188 * Remove a LayoutTurnout 6189 * 6190 * @param o the LayoutTurnout to remove 6191 * @return true if removed 6192 */ 6193 public boolean removeLayoutTurnout(@Nonnull LayoutTurnout o) { 6194 // First verify with the user that this is really wanted 6195 if (!noWarnLayoutTurnout) { 6196 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6197 Bundle.getMessage("Question1r"), Bundle.getMessage("WarningTitle"), 6198 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6199 new Object[]{Bundle.getMessage("ButtonYes"), 6200 Bundle.getMessage("ButtonNo"), 6201 Bundle.getMessage("ButtonYesPlus")}, 6202 Bundle.getMessage("ButtonNo")); 6203 6204 // return without removing if array position 1 "No" response or Dialog closed 6205 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6206 return false; 6207 } 6208 6209 if (selectedValue == 2 ) { // ButtonYesPlus in array position 2 6210 // Suppress future warnings, and continue 6211 noWarnLayoutTurnout = true; 6212 } 6213 } 6214 6215 // remove from selection information 6216 if (selectedObject == o) { 6217 selectedObject = null; 6218 } 6219 6220 if (prevSelectedObject == o) { 6221 prevSelectedObject = null; 6222 } 6223 6224 // remove connections if any 6225 TrackSegment t = (TrackSegment) o.getConnectA(); 6226 6227 if (t != null) { 6228 substituteAnchor(getLayoutTurnoutView(o).getCoordsA(), o, t); 6229 } 6230 t = (TrackSegment) o.getConnectB(); 6231 6232 if (t != null) { 6233 substituteAnchor(getLayoutTurnoutView(o).getCoordsB(), o, t); 6234 } 6235 t = (TrackSegment) o.getConnectC(); 6236 6237 if (t != null) { 6238 substituteAnchor(getLayoutTurnoutView(o).getCoordsC(), o, t); 6239 } 6240 t = (TrackSegment) o.getConnectD(); 6241 6242 if (t != null) { 6243 substituteAnchor(getLayoutTurnoutView(o).getCoordsD(), o, t); 6244 } 6245 6246 // decrement Block use count(s) 6247 LayoutBlock b = o.getLayoutBlock(); 6248 6249 if (b != null) { 6250 b.decrementUse(); 6251 } 6252 6253 if (o.isTurnoutTypeXover() || o.isTurnoutTypeSlip()) { 6254 LayoutBlock b2 = o.getLayoutBlockB(); 6255 6256 if ((b2 != null) && (b2 != b)) { 6257 b2.decrementUse(); 6258 } 6259 LayoutBlock b3 = o.getLayoutBlockC(); 6260 6261 if ((b3 != null) && (b3 != b) && (b3 != b2)) { 6262 b3.decrementUse(); 6263 } 6264 LayoutBlock b4 = o.getLayoutBlockD(); 6265 6266 if ((b4 != null) && (b4 != b) 6267 && (b4 != b2) && (b4 != b3)) { 6268 b4.decrementUse(); 6269 } 6270 } 6271 6272 return removeLayoutTrackAndRedraw(o); 6273 } 6274 6275 private void substituteAnchor(@Nonnull Point2D loc, 6276 @Nonnull LayoutTrack o, @Nonnull TrackSegment t) { 6277 PositionablePoint p = addAnchor(loc); 6278 6279 if (t.getConnect1() == o) { 6280 t.setNewConnect1(p, HitPointType.POS_POINT); 6281 } 6282 6283 if (t.getConnect2() == o) { 6284 t.setNewConnect2(p, HitPointType.POS_POINT); 6285 } 6286 p.setTrackConnection(t); 6287 } 6288 6289 private boolean noWarnLevelXing = false; 6290 6291 /** 6292 * Remove a Level Crossing 6293 * 6294 * @param o the LevelXing to remove 6295 * @return true if removed 6296 */ 6297 public boolean removeLevelXing(@Nonnull LevelXing o) { 6298 // First verify with the user that this is really wanted 6299 if (!noWarnLevelXing) { 6300 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6301 Bundle.getMessage("Question3r"), Bundle.getMessage("WarningTitle"), 6302 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6303 new Object[]{Bundle.getMessage("ButtonYes"), 6304 Bundle.getMessage("ButtonNo"), 6305 Bundle.getMessage("ButtonYesPlus")}, 6306 Bundle.getMessage("ButtonNo")); 6307 6308 // array position 1 Button No, or Dialog closed. 6309 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6310 return false; 6311 } 6312 6313 if (selectedValue == 2 ) { // array position 2 ButtonYesPlus 6314 // Suppress future warnings, and continue 6315 noWarnLevelXing = true; 6316 } 6317 } 6318 6319 // remove from selection information 6320 if (selectedObject == o) { 6321 selectedObject = null; 6322 } 6323 6324 if (prevSelectedObject == o) { 6325 prevSelectedObject = null; 6326 } 6327 6328 // remove connections if any 6329 LevelXingView ov = getLevelXingView(o); 6330 6331 TrackSegment t = (TrackSegment) o.getConnectA(); 6332 if (t != null) { 6333 substituteAnchor(ov.getCoordsA(), o, t); 6334 } 6335 t = (TrackSegment) o.getConnectB(); 6336 6337 if (t != null) { 6338 substituteAnchor(ov.getCoordsB(), o, t); 6339 } 6340 t = (TrackSegment) o.getConnectC(); 6341 6342 if (t != null) { 6343 substituteAnchor(ov.getCoordsC(), o, t); 6344 } 6345 t = (TrackSegment) o.getConnectD(); 6346 6347 if (t != null) { 6348 substituteAnchor(ov.getCoordsD(), o, t); 6349 } 6350 6351 // decrement block use count if any blocks in use 6352 LayoutBlock lb = o.getLayoutBlockAC(); 6353 6354 if (lb != null) { 6355 lb.decrementUse(); 6356 } 6357 LayoutBlock lbx = o.getLayoutBlockBD(); 6358 6359 if ((lbx != null) && (lb != null) && (lbx != lb)) { 6360 lb.decrementUse(); 6361 } 6362 6363 return removeLayoutTrackAndRedraw(o); 6364 } 6365 6366 private boolean noWarnSlip = false; 6367 6368 /** 6369 * Remove a slip 6370 * 6371 * @param o the LayoutSlip to remove 6372 * @return true if removed 6373 */ 6374 public boolean removeLayoutSlip(@Nonnull LayoutTurnout o) { 6375 if (!(o instanceof LayoutSlip)) { 6376 return false; 6377 } 6378 6379 // First verify with the user that this is really wanted 6380 if (!noWarnSlip) { 6381 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6382 Bundle.getMessage("Question5r"), Bundle.getMessage("WarningTitle"), 6383 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6384 new Object[]{Bundle.getMessage("ButtonYes"), 6385 Bundle.getMessage("ButtonNo"), 6386 Bundle.getMessage("ButtonYesPlus")}, 6387 Bundle.getMessage("ButtonNo")); 6388 6389 // return without removing if array position 1 "No" response or Dialog closed 6390 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6391 return false; 6392 } 6393 6394 if (selectedValue == 2 ) { // ButtonYesPlus in array position 2 6395 // Suppress future warnings, and continue 6396 noWarnSlip = true; 6397 } 6398 } 6399 6400 LayoutTurnoutView ov = getLayoutTurnoutView(o); 6401 6402 // remove from selection information 6403 if (selectedObject == o) { 6404 selectedObject = null; 6405 } 6406 6407 if (prevSelectedObject == o) { 6408 prevSelectedObject = null; 6409 } 6410 6411 // remove connections if any 6412 TrackSegment t = (TrackSegment) o.getConnectA(); 6413 6414 if (t != null) { 6415 substituteAnchor(ov.getCoordsA(), o, t); 6416 } 6417 t = (TrackSegment) o.getConnectB(); 6418 6419 if (t != null) { 6420 substituteAnchor(ov.getCoordsB(), o, t); 6421 } 6422 t = (TrackSegment) o.getConnectC(); 6423 6424 if (t != null) { 6425 substituteAnchor(ov.getCoordsC(), o, t); 6426 } 6427 t = (TrackSegment) o.getConnectD(); 6428 6429 if (t != null) { 6430 substituteAnchor(ov.getCoordsD(), o, t); 6431 } 6432 6433 // decrement block use count if any blocks in use 6434 LayoutBlock lb = o.getLayoutBlock(); 6435 6436 if (lb != null) { 6437 lb.decrementUse(); 6438 } 6439 6440 return removeLayoutTrackAndRedraw(o); 6441 } 6442 6443 private boolean noWarnTurntable = false; 6444 6445 /** 6446 * Remove a Layout Turntable 6447 * 6448 * @param o the LayoutTurntable to remove 6449 * @return true if removed 6450 */ 6451 public boolean removeTurntable(@Nonnull LayoutTurntable o) { 6452 // First verify with the user that this is really wanted 6453 if (!noWarnTurntable) { 6454 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6455 Bundle.getMessage("Question4r"), Bundle.getMessage("WarningTitle"), 6456 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6457 new Object[]{Bundle.getMessage("ButtonYes"), 6458 Bundle.getMessage("ButtonNo"), 6459 Bundle.getMessage("ButtonYesPlus")}, 6460 Bundle.getMessage("ButtonNo")); 6461 6462 // return without removing if array position 1 "No" response or Dialog closed 6463 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6464 return false; 6465 } 6466 6467 if (selectedValue == 2 ) { // ButtonYesPlus in array position 2 6468 // Suppress future warnings, and continue 6469 noWarnTurntable = true; 6470 } 6471 } 6472 6473 // remove from selection information 6474 if (selectedObject == o) { 6475 selectedObject = null; 6476 } 6477 6478 if (prevSelectedObject == o) { 6479 prevSelectedObject = null; 6480 } 6481 6482 // remove connections if any 6483 LayoutTurntableView ov = getLayoutTurntableView(o); 6484 for (int j = 0; j < o.getNumberRays(); j++) { 6485 TrackSegment t = ov.getRayConnectOrdered(j); 6486 6487 if (t != null) { 6488 substituteAnchor(ov.getRayCoordsIndexed(j), o, t); 6489 } 6490 } 6491 6492 return removeLayoutTrackAndRedraw(o); 6493 } 6494 6495 /** 6496 * Remove a Track Segment 6497 * 6498 * @param o the TrackSegment to remove 6499 */ 6500 public void removeTrackSegment(@Nonnull TrackSegment o) { 6501 // save affected blocks 6502 LayoutBlock block1 = null; 6503 LayoutBlock block2 = null; 6504 LayoutBlock block = o.getLayoutBlock(); 6505 6506 // remove any connections 6507 HitPointType type = o.getType1(); 6508 6509 if (type == HitPointType.POS_POINT) { 6510 PositionablePoint p = (PositionablePoint) (o.getConnect1()); 6511 6512 if (p != null) { 6513 p.removeTrackConnection(o); 6514 6515 if (p.getConnect1() != null) { 6516 block1 = p.getConnect1().getLayoutBlock(); 6517 } else if (p.getConnect2() != null) { 6518 block1 = p.getConnect2().getLayoutBlock(); 6519 } 6520 } 6521 } else { 6522 block1 = getAffectedBlock(o.getConnect1(), type); 6523 disconnect(o.getConnect1(), type); 6524 } 6525 type = o.getType2(); 6526 6527 if (type == HitPointType.POS_POINT) { 6528 PositionablePoint p = (PositionablePoint) (o.getConnect2()); 6529 6530 if (p != null) { 6531 p.removeTrackConnection(o); 6532 6533 if (p.getConnect1() != null) { 6534 block2 = p.getConnect1().getLayoutBlock(); 6535 } else if (p.getConnect2() != null) { 6536 block2 = p.getConnect2().getLayoutBlock(); 6537 } 6538 } 6539 } else { 6540 block2 = getAffectedBlock(o.getConnect2(), type); 6541 disconnect(o.getConnect2(), type); 6542 } 6543 6544 // delete from array 6545 removeLayoutTrack(o); 6546 6547 // update affected blocks 6548 if (block != null) { 6549 // decrement Block use count 6550 block.decrementUse(); 6551 getLEAuxTools().setBlockConnectivityChanged(); 6552 block.updatePaths(); 6553 } 6554 6555 if ((block1 != null) && (block1 != block)) { 6556 block1.updatePaths(); 6557 } 6558 6559 if ((block2 != null) && (block2 != block) && (block2 != block1)) { 6560 block2.updatePaths(); 6561 } 6562 6563 // 6564 setDirty(); 6565 redrawPanel(); 6566 } 6567 6568 private void disconnect(@Nonnull LayoutTrack o, HitPointType type) { 6569 switch (type) { 6570 case TURNOUT_A: 6571 case TURNOUT_B: 6572 case TURNOUT_C: 6573 case TURNOUT_D: 6574 case SLIP_A: 6575 case SLIP_B: 6576 case SLIP_C: 6577 case SLIP_D: 6578 case LEVEL_XING_A: 6579 case LEVEL_XING_B: 6580 case LEVEL_XING_C: 6581 case LEVEL_XING_D: { 6582 try { 6583 o.setConnection(type, null, HitPointType.NONE); 6584 } catch (JmriException e) { 6585 // ignore (log.error in setConnection method) 6586 } 6587 break; 6588 } 6589 6590 default: { 6591 if (HitPointType.isTurntableRayHitType(type)) { 6592 ((LayoutTurntable) o).setRayConnect(null, type.turntableTrackIndex()); 6593 } 6594 break; 6595 } 6596 } 6597 } 6598 6599 /** 6600 * Depending on the given type, and the real class of the given LayoutTrack, 6601 * determine the connected LayoutTrack. This provides a variable-indirect 6602 * form of e.g. trk.getLayoutBlockC() for example. Perhaps "Connected Block" 6603 * captures the idea better, but that method name is being used for 6604 * something else. 6605 * 6606 * 6607 * @param track The track who's connected blocks are being examined 6608 * @param type This point to check for connected blocks, i.e. TURNOUT_B 6609 * @return The block at a particular point on the track object, or null if 6610 * none. 6611 */ 6612 // Temporary - this should certainly be a LayoutTrack method. 6613 public LayoutBlock getAffectedBlock(@Nonnull LayoutTrack track, HitPointType type) { 6614 LayoutBlock result = null; 6615 6616 switch (type) { 6617 case TURNOUT_A: 6618 case SLIP_A: { 6619 if (track instanceof LayoutTurnout) { 6620 LayoutTurnout lt = (LayoutTurnout) track; 6621 result = lt.getLayoutBlock(); 6622 } 6623 break; 6624 } 6625 6626 case TURNOUT_B: 6627 case SLIP_B: { 6628 if (track instanceof LayoutTurnout) { 6629 LayoutTurnout lt = (LayoutTurnout) track; 6630 result = lt.getLayoutBlockB(); 6631 } 6632 break; 6633 } 6634 6635 case TURNOUT_C: 6636 case SLIP_C: { 6637 if (track instanceof LayoutTurnout) { 6638 LayoutTurnout lt = (LayoutTurnout) track; 6639 result = lt.getLayoutBlockC(); 6640 } 6641 break; 6642 } 6643 6644 case TURNOUT_D: 6645 case SLIP_D: { 6646 if (track instanceof LayoutTurnout) { 6647 LayoutTurnout lt = (LayoutTurnout) track; 6648 result = lt.getLayoutBlockD(); 6649 } 6650 break; 6651 } 6652 6653 case LEVEL_XING_A: 6654 case LEVEL_XING_C: { 6655 if (track instanceof LevelXing) { 6656 LevelXing lx = (LevelXing) track; 6657 result = lx.getLayoutBlockAC(); 6658 } 6659 break; 6660 } 6661 6662 case LEVEL_XING_B: 6663 case LEVEL_XING_D: { 6664 if (track instanceof LevelXing) { 6665 LevelXing lx = (LevelXing) track; 6666 result = lx.getLayoutBlockBD(); 6667 } 6668 break; 6669 } 6670 6671 case TRACK: { 6672 if (track instanceof TrackSegment) { 6673 TrackSegment ts = (TrackSegment) track; 6674 result = ts.getLayoutBlock(); 6675 } 6676 break; 6677 } 6678 default: { 6679 log.warn("Unhandled track type: {}", type); 6680 break; 6681 } 6682 } 6683 return result; 6684 } 6685 6686 /** 6687 * Add a sensor indicator to the Draw Panel 6688 */ 6689 void addSensor() { 6690 String newName = leToolBarPanel.sensorComboBox.getSelectedItemDisplayName(); 6691 if (newName == null) { 6692 newName = ""; 6693 } 6694 6695 if (newName.isEmpty()) { 6696 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"), 6697 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6698 return; 6699 } 6700 SensorIcon l = new SensorIcon(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif", 6701 "resources/icons/smallschematics/tracksegments/circuit-error.gif"), this); 6702 6703 l.setIcon("SensorStateActive", leToolBarPanel.sensorIconEditor.getIcon(0)); 6704 l.setIcon("SensorStateInactive", leToolBarPanel.sensorIconEditor.getIcon(1)); 6705 l.setIcon("BeanStateInconsistent", leToolBarPanel.sensorIconEditor.getIcon(2)); 6706 l.setIcon("BeanStateUnknown", leToolBarPanel.sensorIconEditor.getIcon(3)); 6707 l.setSensor(newName); 6708 l.setDisplayLevel(Editor.SENSORS); 6709 6710 leToolBarPanel.sensorComboBox.setSelectedItem(l.getSensor()); 6711 setNextLocation(l); 6712 try { 6713 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6714 } catch (Positionable.DuplicateIdException e) { 6715 // This should never happen 6716 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6717 } 6718 } 6719 6720 public void putSensor(@Nonnull SensorIcon l) { 6721 l.updateSize(); 6722 l.setDisplayLevel(Editor.SENSORS); 6723 try { 6724 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6725 } catch (Positionable.DuplicateIdException e) { 6726 // This should never happen 6727 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6728 } 6729 } 6730 6731 /** 6732 * Add a signal head to the Panel 6733 */ 6734 void addSignalHead() { 6735 // check for valid signal head entry 6736 String newName = leToolBarPanel.signalHeadComboBox.getSelectedItemDisplayName(); 6737 if (newName == null) { 6738 newName = ""; 6739 } 6740 SignalHead mHead = null; 6741 6742 if (!newName.isEmpty()) { 6743 mHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(newName); 6744 6745 /*if (mHead == null) 6746 mHead = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(newName); 6747 else */ 6748 leToolBarPanel.signalHeadComboBox.setSelectedItem(mHead); 6749 } 6750 6751 if (mHead == null) { 6752 // There is no signal head corresponding to this name 6753 JmriJOptionPane.showMessageDialog(this, 6754 MessageFormat.format(Bundle.getMessage("Error9"), newName), 6755 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6756 return; 6757 } 6758 6759 // create and set up signal icon 6760 SignalHeadIcon l = new SignalHeadIcon(this); 6761 l.setSignalHead(newName); 6762 l.setIcon("SignalHeadStateRed", leToolBarPanel.signalIconEditor.getIcon(0)); 6763 l.setIcon("SignalHeadStateFlashingRed", leToolBarPanel.signalIconEditor.getIcon(1)); 6764 l.setIcon("SignalHeadStateYellow", leToolBarPanel.signalIconEditor.getIcon(2)); 6765 l.setIcon("SignalHeadStateFlashingYellow", leToolBarPanel.signalIconEditor.getIcon(3)); 6766 l.setIcon("SignalHeadStateGreen", leToolBarPanel.signalIconEditor.getIcon(4)); 6767 l.setIcon("SignalHeadStateFlashingGreen", leToolBarPanel.signalIconEditor.getIcon(5)); 6768 l.setIcon("SignalHeadStateDark", leToolBarPanel.signalIconEditor.getIcon(6)); 6769 l.setIcon("SignalHeadStateHeld", leToolBarPanel.signalIconEditor.getIcon(7)); 6770 l.setIcon("SignalHeadStateLunar", leToolBarPanel.signalIconEditor.getIcon(8)); 6771 l.setIcon("SignalHeadStateFlashingLunar", leToolBarPanel.signalIconEditor.getIcon(9)); 6772 unionToPanelBounds(l.getBounds()); 6773 setNextLocation(l); 6774 setDirty(); 6775 putSignal(l); 6776 } 6777 6778 public void putSignal(@Nonnull SignalHeadIcon l) { 6779 l.updateSize(); 6780 l.setDisplayLevel(Editor.SIGNALS); 6781 try { 6782 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6783 } catch (Positionable.DuplicateIdException e) { 6784 // This should never happen 6785 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6786 } 6787 } 6788 6789 @CheckForNull 6790 SignalHead getSignalHead(@Nonnull String name) { 6791 SignalHead sh = InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(name); 6792 6793 if (sh == null) { 6794 sh = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(name); 6795 } 6796 6797 if (sh == null) { 6798 log.warn("did not find a SignalHead named {}", name); 6799 } 6800 return sh; 6801 } 6802 6803 public boolean containsSignalHead(@CheckForNull SignalHead head) { 6804 if (head != null) { 6805 for (SignalHeadIcon h : signalList) { 6806 if (h.getSignalHead() == head) { 6807 return true; 6808 } 6809 } 6810 } 6811 return false; 6812 } 6813 6814 public void removeSignalHead(@CheckForNull SignalHead head) { 6815 if (head != null) { 6816 for (SignalHeadIcon h : signalList) { 6817 if (h.getSignalHead() == head) { 6818 signalList.remove(h); 6819 h.remove(); 6820 h.dispose(); 6821 setDirty(); 6822 redrawPanel(); 6823 break; 6824 } 6825 } 6826 } 6827 } 6828 6829 void addSignalMast() { 6830 // check for valid signal head entry 6831 String newName = leToolBarPanel.signalMastComboBox.getSelectedItemDisplayName(); 6832 if (newName == null) { 6833 newName = ""; 6834 } 6835 SignalMast mMast = null; 6836 6837 if (!newName.isEmpty()) { 6838 mMast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(newName); 6839 leToolBarPanel.signalMastComboBox.setSelectedItem(mMast); 6840 } 6841 6842 if (mMast == null) { 6843 // There is no signal head corresponding to this name 6844 JmriJOptionPane.showMessageDialog(this, 6845 MessageFormat.format(Bundle.getMessage("Error9"), newName), 6846 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6847 6848 return; 6849 } 6850 6851 // create and set up signal icon 6852 SignalMastIcon l = new SignalMastIcon(this); 6853 l.setSignalMast(newName); 6854 unionToPanelBounds(l.getBounds()); 6855 setNextLocation(l); 6856 setDirty(); 6857 putSignalMast(l); 6858 } 6859 6860 public void putSignalMast(@Nonnull SignalMastIcon l) { 6861 l.updateSize(); 6862 l.setDisplayLevel(Editor.SIGNALS); 6863 try { 6864 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6865 } catch (Positionable.DuplicateIdException e) { 6866 // This should never happen 6867 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6868 } 6869 } 6870 6871 SignalMast getSignalMast(@Nonnull String name) { 6872 SignalMast sh = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name); 6873 6874 if (sh == null) { 6875 sh = InstanceManager.getDefault(SignalMastManager.class).getByUserName(name); 6876 } 6877 6878 if (sh == null) { 6879 log.warn("did not find a SignalMast named {}", name); 6880 } 6881 return sh; 6882 } 6883 6884 public boolean containsSignalMast(@Nonnull SignalMast mast) { 6885 for (SignalMastIcon h : signalMastList) { 6886 if (h.getSignalMast() == mast) { 6887 return true; 6888 } 6889 } 6890 return false; 6891 } 6892 6893 /** 6894 * Add a label to the Draw Panel 6895 */ 6896 void addLabel() { 6897 String labelText = leToolBarPanel.textLabelTextField.getText(); 6898 labelText = (labelText != null) ? labelText.trim() : ""; 6899 6900 if (labelText.isEmpty()) { 6901 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11"), 6902 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6903 return; 6904 } 6905 PositionableLabel l = super.addLabel(labelText); 6906 unionToPanelBounds(l.getBounds()); 6907 setDirty(); 6908 l.setForeground(defaultTextColor); 6909 } 6910 6911 @Override 6912 public void putItem(@Nonnull Positionable l) throws Positionable.DuplicateIdException { 6913 super.putItem(l); 6914 6915 if (l instanceof SensorIcon) { 6916 sensorImage.add((SensorIcon) l); 6917 sensorList.add((SensorIcon) l); 6918 } else if (l instanceof LocoIcon) { 6919 markerImage.add((LocoIcon) l); 6920 } else if (l instanceof SignalHeadIcon) { 6921 signalHeadImage.add((SignalHeadIcon) l); 6922 signalList.add((SignalHeadIcon) l); 6923 } else if (l instanceof SignalMastIcon) { 6924 signalMastList.add((SignalMastIcon) l); 6925 } else if (l instanceof MemoryIcon) { 6926 memoryLabelList.add((MemoryIcon) l); 6927 } else if (l instanceof GlobalVariableIcon) { 6928 globalVariableLabelList.add((GlobalVariableIcon) l); 6929 } else if (l instanceof BlockContentsIcon) { 6930 blockContentsLabelList.add((BlockContentsIcon) l); 6931 } else if (l instanceof AnalogClock2Display) { 6932 clocks.add((AnalogClock2Display) l); 6933 } else if (l instanceof MultiSensorIcon) { 6934 multiSensors.add((MultiSensorIcon) l); 6935 } 6936 6937 if (l instanceof PositionableLabel) { 6938 if (((PositionableLabel) l).isBackground()) { 6939 backgroundImage.add((PositionableLabel) l); 6940 } else { 6941 labelImage.add((PositionableLabel) l); 6942 } 6943 } 6944 unionToPanelBounds(l.getBounds(new Rectangle())); 6945 setDirty(); 6946 } 6947 6948 /** 6949 * Add a memory label to the Draw Panel 6950 */ 6951 void addMemory() { 6952 String memoryName = leToolBarPanel.textMemoryComboBox.getSelectedItemDisplayName(); 6953 if (memoryName == null) { 6954 memoryName = ""; 6955 } 6956 6957 if (memoryName.isEmpty()) { 6958 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11a"), 6959 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6960 return; 6961 } 6962 MemoryIcon l = new MemoryIcon(" ", this); 6963 l.setMemory(memoryName); 6964 Memory xMemory = l.getMemory(); 6965 6966 if (xMemory != null) { 6967 String uname = xMemory.getDisplayName(); 6968 if (!uname.equals(memoryName)) { 6969 // put the system name in the memory field 6970 leToolBarPanel.textMemoryComboBox.setSelectedItem(xMemory); 6971 } 6972 } 6973 setNextLocation(l); 6974 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 6975 l.setDisplayLevel(Editor.LABELS); 6976 l.setForeground(defaultTextColor); 6977 unionToPanelBounds(l.getBounds()); 6978 try { 6979 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6980 } catch (Positionable.DuplicateIdException e) { 6981 // This should never happen 6982 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6983 } 6984 } 6985 6986 void addGlobalVariable() { 6987 String globalVariableName = leToolBarPanel.textGlobalVariableComboBox.getSelectedItemDisplayName(); 6988 if (globalVariableName == null) { 6989 globalVariableName = ""; 6990 } 6991 6992 if (globalVariableName.isEmpty()) { 6993 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11c"), 6994 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6995 return; 6996 } 6997 GlobalVariableIcon l = new GlobalVariableIcon(" ", this); 6998 l.setGlobalVariable(globalVariableName); 6999 GlobalVariable xGlobalVariable = l.getGlobalVariable(); 7000 7001 if (xGlobalVariable != null) { 7002 String uname = xGlobalVariable.getDisplayName(); 7003 if (!uname.equals(globalVariableName)) { 7004 // put the system name in the memory field 7005 leToolBarPanel.textGlobalVariableComboBox.setSelectedItem(xGlobalVariable); 7006 } 7007 } 7008 setNextLocation(l); 7009 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 7010 l.setDisplayLevel(Editor.LABELS); 7011 l.setForeground(defaultTextColor); 7012 unionToPanelBounds(l.getBounds()); 7013 try { 7014 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7015 } catch (Positionable.DuplicateIdException e) { 7016 // This should never happen 7017 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7018 } 7019 } 7020 7021 void addBlockContents() { 7022 String newName = leToolBarPanel.blockContentsComboBox.getSelectedItemDisplayName(); 7023 if (newName == null) { 7024 newName = ""; 7025 } 7026 7027 if (newName.isEmpty()) { 7028 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11b"), 7029 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 7030 return; 7031 } 7032 BlockContentsIcon l = new BlockContentsIcon(" ", this); 7033 l.setBlock(newName); 7034 Block xMemory = l.getBlock(); 7035 7036 if (xMemory != null) { 7037 String uname = xMemory.getDisplayName(); 7038 if (!uname.equals(newName)) { 7039 // put the system name in the memory field 7040 leToolBarPanel.blockContentsComboBox.setSelectedItem(xMemory); 7041 } 7042 } 7043 setNextLocation(l); 7044 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 7045 l.setDisplayLevel(Editor.LABELS); 7046 l.setForeground(defaultTextColor); 7047 try { 7048 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7049 } catch (Positionable.DuplicateIdException e) { 7050 // This should never happen 7051 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7052 } 7053 } 7054 7055 /** 7056 * Add a Reporter Icon to the panel. 7057 * 7058 * @param reporter the reporter icon to add. 7059 * @param xx the horizontal location. 7060 * @param yy the vertical location. 7061 */ 7062 public void addReporter(@Nonnull Reporter reporter, int xx, int yy) { 7063 ReporterIcon l = new ReporterIcon(this); 7064 l.setReporter(reporter); 7065 l.setLocation(xx, yy); 7066 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 7067 l.setDisplayLevel(Editor.LABELS); 7068 unionToPanelBounds(l.getBounds()); 7069 try { 7070 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7071 } catch (Positionable.DuplicateIdException e) { 7072 // This should never happen 7073 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7074 } 7075 } 7076 7077 /** 7078 * Add an icon to the target 7079 */ 7080 void addIcon() { 7081 PositionableLabel l = new PositionableLabel(leToolBarPanel.iconEditor.getIcon(0), this); 7082 setNextLocation(l); 7083 l.setDisplayLevel(Editor.ICONS); 7084 unionToPanelBounds(l.getBounds()); 7085 l.updateSize(); 7086 try { 7087 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7088 } catch (Positionable.DuplicateIdException e) { 7089 // This should never happen 7090 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7091 } 7092 } 7093 7094 /** 7095 * Add a LogixNG icon to the target 7096 */ 7097 void addLogixNGIcon() { 7098 LogixNGIcon l = new LogixNGIcon(leToolBarPanel.logixngEditor.getIcon(0), this); 7099 setNextLocation(l); 7100 l.setDisplayLevel(Editor.ICONS); 7101 unionToPanelBounds(l.getBounds()); 7102 l.updateSize(); 7103 try { 7104 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7105 } catch (Positionable.DuplicateIdException e) { 7106 // This should never happen 7107 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7108 } 7109 } 7110 7111 /** 7112 * Add a LogixNG icon to the target 7113 */ 7114 void addAudioIcon() { 7115 String audioName = leToolBarPanel.textAudioComboBox.getSelectedItemDisplayName(); 7116 if (audioName == null) { 7117 audioName = ""; 7118 } 7119 7120 if (audioName.isEmpty()) { 7121 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11d"), 7122 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 7123 return; 7124 } 7125 7126 AudioIcon l = new AudioIcon(leToolBarPanel.audioEditor.getIcon(0), this); 7127 l.setAudio(audioName); 7128 Audio xAudio = l.getAudio(); 7129 7130 if (xAudio != null) { 7131 String uname = xAudio.getDisplayName(); 7132 if (!uname.equals(audioName)) { 7133 // put the system name in the memory field 7134 leToolBarPanel.textAudioComboBox.setSelectedItem(xAudio); 7135 } 7136 } 7137 7138 setNextLocation(l); 7139 l.setDisplayLevel(Editor.ICONS); 7140 unionToPanelBounds(l.getBounds()); 7141 l.updateSize(); 7142 try { 7143 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7144 } catch (Positionable.DuplicateIdException e) { 7145 // This should never happen 7146 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7147 } 7148 } 7149 7150 /** 7151 * Add a loco marker to the target 7152 */ 7153 @Override 7154 public LocoIcon addLocoIcon(@Nonnull String name) { 7155 LocoIcon l = new LocoIcon(this); 7156 Point2D pt = windowCenter(); 7157 if (selectionActive) { 7158 pt = MathUtil.midPoint(getSelectionRect()); 7159 } 7160 l.setLocation((int) pt.getX(), (int) pt.getY()); 7161 putLocoIcon(l, name); 7162 l.setPositionable(true); 7163 unionToPanelBounds(l.getBounds()); 7164 return l; 7165 } 7166 7167 @Override 7168 public void putLocoIcon(@Nonnull LocoIcon l, @Nonnull String name) { 7169 super.putLocoIcon(l, name); 7170 markerImage.add(l); 7171 unionToPanelBounds(l.getBounds()); 7172 } 7173 7174 private JFileChooser inputFileChooser = null; 7175 7176 /** 7177 * Add a background image 7178 */ 7179 public void addBackground() { 7180 if (inputFileChooser == null) { 7181 inputFileChooser = new jmri.util.swing.JmriJFileChooser( 7182 String.format("%s%sresources%sicons", 7183 System.getProperty("user.dir"), 7184 File.separator, 7185 File.separator)); 7186 7187 inputFileChooser.setFileFilter(new FileNameExtensionFilter("Graphics Files", "gif", "jpg", "png")); 7188 } 7189 inputFileChooser.rescanCurrentDirectory(); 7190 7191 int retVal = inputFileChooser.showOpenDialog(this); 7192 7193 if (retVal != JFileChooser.APPROVE_OPTION) { 7194 return; // give up if no file selected 7195 } 7196 7197 // NamedIcon icon = new NamedIcon(inputFileChooser.getSelectedFile().getPath(), 7198 // inputFileChooser.getSelectedFile().getPath()); 7199 String name = inputFileChooser.getSelectedFile().getPath(); 7200 7201 // convert to portable path 7202 name = FileUtil.getPortableFilename(name); 7203 7204 // setup icon 7205 PositionableLabel o = super.setUpBackground(name); 7206 backgroundImage.add(o); 7207 unionToPanelBounds(o.getBounds()); 7208 setDirty(); 7209 } 7210 7211 // there is no way to call this; could that 7212 // private boolean remove(@Nonnull Object s) 7213 // is being used instead. 7214 // 7215 ///** 7216 // * Remove a background image from the list of background images 7217 // * 7218 // * @param b PositionableLabel to remove 7219 // */ 7220 //private void removeBackground(@Nonnull PositionableLabel b) { 7221 // if (backgroundImage.contains(b)) { 7222 // backgroundImage.remove(b); 7223 // setDirty(); 7224 // } 7225 //} 7226 /** 7227 * add a layout shape to the list of layout shapes 7228 * 7229 * @param p Point2D where the shape should be 7230 * @return the LayoutShape 7231 */ 7232 @Nonnull 7233 private LayoutShape addLayoutShape(@Nonnull Point2D p) { 7234 // get unique name 7235 String name = finder.uniqueName("S", getLayoutShapes().size() + 1); 7236 7237 // create object 7238 LayoutShape o = new LayoutShape(name, p, this); 7239 layoutShapes.add(o); 7240 unionToPanelBounds(o.getBounds()); 7241 setDirty(); 7242 return o; 7243 } 7244 7245 /** 7246 * Remove a layout shape from the list of layout shapes 7247 * 7248 * @param s the LayoutShape to add 7249 * @return true if added 7250 */ 7251 public boolean removeLayoutShape(@Nonnull LayoutShape s) { 7252 boolean result = false; 7253 if (layoutShapes.contains(s)) { 7254 layoutShapes.remove(s); 7255 setDirty(); 7256 result = true; 7257 redrawPanel(); 7258 } 7259 return result; 7260 } 7261 7262 /** 7263 * Invoke a window to allow you to add a MultiSensor indicator to the target 7264 */ 7265 private int multiLocX; 7266 private int multiLocY; 7267 7268 void startMultiSensor() { 7269 multiLocX = xLoc; 7270 multiLocY = yLoc; 7271 7272 if (leToolBarPanel.multiSensorFrame == null) { 7273 // create a common edit frame 7274 leToolBarPanel.multiSensorFrame = new MultiSensorIconFrame(this); 7275 leToolBarPanel.multiSensorFrame.initComponents(); 7276 leToolBarPanel.multiSensorFrame.pack(); 7277 } 7278 leToolBarPanel.multiSensorFrame.setVisible(true); 7279 } 7280 7281 // Invoked when window has new multi-sensor ready 7282 public void addMultiSensor(@Nonnull MultiSensorIcon l) { 7283 l.setLocation(multiLocX, multiLocY); 7284 try { 7285 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7286 } catch (Positionable.DuplicateIdException e) { 7287 // This should never happen 7288 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7289 } 7290 leToolBarPanel.multiSensorFrame.dispose(); 7291 leToolBarPanel.multiSensorFrame = null; 7292 } 7293 7294 /** 7295 * Set object location and size for icon and label object as it is created. 7296 * Size comes from the preferredSize; location comes from the fields where 7297 * the user can spec it. 7298 * 7299 * @param obj the positionable object. 7300 */ 7301 @Override 7302 public void setNextLocation(@Nonnull Positionable obj) { 7303 obj.setLocation(xLoc, yLoc); 7304 } 7305 7306 // 7307 // singleton (one per-LayoutEditor) accessors 7308 // 7309 private ConnectivityUtil conTools = null; 7310 7311 @Nonnull 7312 public ConnectivityUtil getConnectivityUtil() { 7313 if (conTools == null) { 7314 conTools = new ConnectivityUtil(this); 7315 } 7316 return conTools; 7317 } 7318 7319 private LayoutEditorTools tools = null; 7320 7321 @Nonnull 7322 public LayoutEditorTools getLETools() { 7323 if (tools == null) { 7324 tools = new LayoutEditorTools(this); 7325 } 7326 return tools; 7327 } 7328 7329 private LayoutEditorAuxTools auxTools = null; 7330 7331 @Override 7332 @Nonnull 7333 public LayoutEditorAuxTools getLEAuxTools() { 7334 if (auxTools == null) { 7335 auxTools = new LayoutEditorAuxTools(this); 7336 } 7337 return auxTools; 7338 } 7339 7340 private LayoutEditorChecks layoutEditorChecks = null; 7341 7342 @Nonnull 7343 public LayoutEditorChecks getLEChecks() { 7344 if (layoutEditorChecks == null) { 7345 layoutEditorChecks = new LayoutEditorChecks(this); 7346 } 7347 return layoutEditorChecks; 7348 } 7349 7350 /** 7351 * Invoked by DeletePanel menu item Validate user intent before deleting 7352 */ 7353 @Override 7354 public boolean deletePanel() { 7355 if (canDeletePanel()) { 7356 // verify deletion 7357 if (!super.deletePanel()) { 7358 return false; // return without deleting if "No" response 7359 } 7360 clearLayoutTracks(); 7361 return true; 7362 } 7363 return false; 7364 } 7365 7366 /** 7367 * Check for conditions that prevent a delete. 7368 * <ul> 7369 * <li>The panel has active edge connector links</li> 7370 * <li>The panel is used by EntryExit</li> 7371 * </ul> 7372 * @return true if ok to delete 7373 */ 7374 public boolean canDeletePanel() { 7375 var messages = new ArrayList<String>(); 7376 7377 var points = getPositionablePoints(); 7378 for (PositionablePoint point : points) { 7379 if (point.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) { 7380 var panelName = point.getLinkedEditorName(); 7381 if (!panelName.isEmpty()) { 7382 messages.add(Bundle.getMessage("ActiveEdgeConnector", point.getId(), point.getLinkedEditorName())); 7383 } 7384 } 7385 } 7386 7387 var entryExitPairs = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class); 7388 if (!entryExitPairs.getNxSource(this).isEmpty()) { 7389 messages.add(Bundle.getMessage("ActiveEntryExit")); 7390 } 7391 7392 if (!messages.isEmpty()) { 7393 StringBuilder msg = new StringBuilder(Bundle.getMessage("PanelRelationshipsError")); 7394 for (String message : messages) { 7395 msg.append(message); 7396 } 7397 JmriJOptionPane.showMessageDialog(null, 7398 msg.toString(), 7399 Bundle.getMessage("ErrorTitle"), // NOI18N 7400 JmriJOptionPane.ERROR_MESSAGE); 7401 } 7402 7403 return messages.isEmpty(); 7404 } 7405 7406 /** 7407 * Control whether target panel items are editable. Does this by invoking 7408 * the {@link Editor#setAllEditable} function of the parent class. This also 7409 * controls the relevant pop-up menu items (which are the primary way that 7410 * items are edited). 7411 * 7412 * @param editable true for editable. 7413 */ 7414 @Override 7415 public void setAllEditable(boolean editable) { 7416 int restoreScroll = _scrollState; 7417 7418 super.setAllEditable(editable); 7419 7420 if (toolBarSide.equals(ToolBarSide.eFLOAT)) { 7421 if (editable) { 7422 createfloatingEditToolBoxFrame(); 7423 createFloatingHelpPanel(); 7424 } else { 7425 deletefloatingEditToolBoxFrame(); 7426 } 7427 } else { 7428 editToolBarContainerPanel.setVisible(editable); 7429 } 7430 setShowHidden(editable); 7431 7432 if (editable) { 7433 setScroll(Editor.SCROLL_BOTH); 7434 _scrollState = restoreScroll; 7435 } else { 7436 setScroll(_scrollState); 7437 } 7438 7439 // these may not be set up yet... 7440 if (helpBarPanel != null) { 7441 if (toolBarSide.equals(ToolBarSide.eFLOAT)) { 7442 if (floatEditHelpPanel != null) { 7443 floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar()); 7444 } 7445 } else { 7446 helpBarPanel.setVisible(editable && getShowHelpBar()); 7447 } 7448 } 7449 awaitingIconChange = false; 7450 editModeCheckBoxMenuItem.setSelected(editable); 7451 redrawPanel(); 7452 } 7453 7454 /** 7455 * Control whether panel items are positionable. Markers are always 7456 * positionable. 7457 * 7458 * @param state true for positionable. 7459 */ 7460 @Override 7461 public void setAllPositionable(boolean state) { 7462 super.setAllPositionable(state); 7463 7464 markerImage.forEach((p) -> p.setPositionable(true)); 7465 } 7466 7467 /** 7468 * Control whether target panel items are controlling layout items. Does 7469 * this by invoke the {@link Positionable#setControlling} function of each 7470 * item on the target panel. This also controls the relevant pop-up menu 7471 * items. 7472 * 7473 * @param state true for controlling. 7474 */ 7475 public void setTurnoutAnimation(boolean state) { 7476 if (animationCheckBoxMenuItem.isSelected() != state) { 7477 animationCheckBoxMenuItem.setSelected(state); 7478 } 7479 7480 if (animatingLayout != state) { 7481 animatingLayout = state; 7482 redrawPanel(); 7483 } 7484 } 7485 7486 public boolean isAnimating() { 7487 return animatingLayout; 7488 } 7489 7490 public boolean getScroll() { 7491 // deprecated but kept to allow opening files 7492 // on version 2.5.1 and earlier 7493 return _scrollState != Editor.SCROLL_NONE; 7494 } 7495 7496// public Color getDefaultBackgroundColor() { 7497// return defaultBackgroundColor; 7498// } 7499 public String getDefaultTrackColor() { 7500 return ColorUtil.colorToColorName(defaultTrackColor); 7501 } 7502 7503 /** 7504 * 7505 * Getter defaultTrackColor. 7506 * 7507 * @return block default color as Color 7508 */ 7509 @Nonnull 7510 public Color getDefaultTrackColorColor() { 7511 return defaultTrackColor; 7512 } 7513 7514 @Nonnull 7515 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7516 public String getDefaultOccupiedTrackColor() { 7517 return ColorUtil.colorToColorName(defaultOccupiedTrackColor); 7518 } 7519 7520 /** 7521 * 7522 * Getter defaultOccupiedTrackColor. 7523 * 7524 * @return block default occupied color as Color 7525 */ 7526 @Nonnull 7527 public Color getDefaultOccupiedTrackColorColor() { 7528 return defaultOccupiedTrackColor; 7529 } 7530 7531 @Nonnull 7532 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7533 public String getDefaultAlternativeTrackColor() { 7534 return ColorUtil.colorToColorName(defaultAlternativeTrackColor); 7535 } 7536 7537 /** 7538 * 7539 * Getter defaultAlternativeTrackColor. 7540 * 7541 * @return block default alternative color as Color 7542 */ 7543 @Nonnull 7544 public Color getDefaultAlternativeTrackColorColor() { 7545 return defaultAlternativeTrackColor; 7546 } 7547 7548 @Nonnull 7549 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7550 public String getDefaultTextColor() { 7551 return ColorUtil.colorToColorName(defaultTextColor); 7552 } 7553 7554 @Nonnull 7555 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7556 public String getTurnoutCircleColor() { 7557 return ColorUtil.colorToColorName(turnoutCircleColor); 7558 } 7559 7560 @Nonnull 7561 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7562 public String getTurnoutCircleThrownColor() { 7563 return ColorUtil.colorToColorName(turnoutCircleThrownColor); 7564 } 7565 7566 public boolean isTurnoutFillControlCircles() { 7567 return turnoutFillControlCircles; 7568 } 7569 7570 public int getTurnoutCircleSize() { 7571 return turnoutCircleSize; 7572 } 7573 7574 public boolean isTurnoutDrawUnselectedLeg() { 7575 return turnoutDrawUnselectedLeg; 7576 } 7577 7578 public boolean isHighlightCursor() { 7579 return highlightCursor; 7580 } 7581 7582 public String getLayoutName() { 7583 return layoutName; 7584 } 7585 7586 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7587 public boolean getShowHelpBar() { 7588 return showHelpBar; 7589 } 7590 7591 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7592 public boolean getDrawGrid() { 7593 return drawGrid; 7594 } 7595 7596 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7597 public boolean getSnapOnAdd() { 7598 return snapToGridOnAdd; 7599 } 7600 7601 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7602 public boolean getSnapOnMove() { 7603 return snapToGridOnMove; 7604 } 7605 7606 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7607 public boolean getAntialiasingOn() { 7608 return antialiasingOn; 7609 } 7610 7611 public boolean isDrawLayoutTracksLabel() { 7612 return drawLayoutTracksLabel; 7613 } 7614 7615 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7616 public boolean getHighlightSelectedBlock() { 7617 return highlightSelectedBlockFlag; 7618 } 7619 7620 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7621 public boolean getTurnoutCircles() { 7622 return turnoutCirclesWithoutEditMode; 7623 } 7624 7625 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7626 public boolean getTooltipsNotEdit() { 7627 return tooltipsWithoutEditMode; 7628 } 7629 7630 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7631 public boolean getTooltipsInEdit() { 7632 return tooltipsInEditMode; 7633 } 7634 7635 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7636 public boolean getAutoBlockAssignment() { 7637 return autoAssignBlocks; 7638 } 7639 7640 public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight) { 7641 setLayoutDimensions(windowWidth, windowHeight, windowX, windowY, panelWidth, panelHeight, false); 7642 } 7643 7644 public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight, boolean merge) { 7645 7646 gContext.setUpperLeftX(windowX); 7647 gContext.setUpperLeftY(windowY); 7648 setLocation(gContext.getUpperLeftX(), gContext.getUpperLeftY()); 7649 7650 gContext.setWindowWidth(windowWidth); 7651 gContext.setWindowHeight(windowHeight); 7652 setSize(windowWidth, windowHeight); 7653 7654 Rectangle2D panelBounds = new Rectangle2D.Double(0.0, 0.0, panelWidth, panelHeight); 7655 7656 if (merge) { 7657 panelBounds.add(calculateMinimumLayoutBounds()); 7658 } 7659 setPanelBounds(panelBounds); 7660 } 7661 7662 @Nonnull 7663 public Rectangle2D getPanelBounds() { 7664 return new Rectangle2D.Double(0.0, 0.0, gContext.getLayoutWidth(), gContext.getLayoutHeight()); 7665 } 7666 7667 public void setPanelBounds(@Nonnull Rectangle2D newBounds) { 7668 // don't let origin go negative 7669 newBounds = newBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D); 7670 7671 if (!getPanelBounds().equals(newBounds)) { 7672 gContext.setLayoutWidth((int) newBounds.getWidth()); 7673 gContext.setLayoutHeight((int) newBounds.getHeight()); 7674 resetTargetSize(); 7675 } 7676 log.debug("setPanelBounds(({})", newBounds); 7677 } 7678 7679 private void resetTargetSize() { 7680 int newTargetWidth = (int) (gContext.getLayoutWidth() * getZoom()); 7681 int newTargetHeight = (int) (gContext.getLayoutHeight() * getZoom()); 7682 7683 Dimension targetPanelSize = getTargetPanelSize(); 7684 int oldTargetWidth = (int) targetPanelSize.getWidth(); 7685 int oldTargetHeight = (int) targetPanelSize.getHeight(); 7686 7687 if ((newTargetWidth != oldTargetWidth) || (newTargetHeight != oldTargetHeight)) { 7688 setTargetPanelSize(newTargetWidth, newTargetHeight); 7689 adjustScrollBars(); 7690 } 7691 } 7692 7693 // this will grow the panel bounds based on items added to the layout 7694 @Nonnull 7695 public Rectangle2D unionToPanelBounds(@Nonnull Rectangle2D bounds) { 7696 Rectangle2D result = getPanelBounds(); 7697 7698 // make room to expand 7699 Rectangle2D b = MathUtil.inset(bounds, gContext.getGridSize() * gContext.getGridSize2nd() / -2.0); 7700 7701 // don't let origin go negative 7702 b = b.createIntersection(MathUtil.zeroToInfinityRectangle2D); 7703 7704 result.add(b); 7705 7706 setPanelBounds(result); 7707 return result; 7708 } 7709 7710 /** 7711 * @param color value to set the default track color to. 7712 */ 7713 public void setDefaultTrackColor(@Nonnull Color color) { 7714 defaultTrackColor = color; 7715 JmriColorChooser.addRecentColor(color); 7716 } 7717 7718 /** 7719 * @param color value to set the default occupied track color to. 7720 */ 7721 public void setDefaultOccupiedTrackColor(@Nonnull Color color) { 7722 defaultOccupiedTrackColor = color; 7723 JmriColorChooser.addRecentColor(color); 7724 } 7725 7726 /** 7727 * @param color value to set the default alternate track color to. 7728 */ 7729 public void setDefaultAlternativeTrackColor(@Nonnull Color color) { 7730 defaultAlternativeTrackColor = color; 7731 JmriColorChooser.addRecentColor(color); 7732 } 7733 7734 /** 7735 * @param color new color for turnout circle. 7736 */ 7737 public void setTurnoutCircleColor(@CheckForNull Color color) { 7738 if (color == null) { 7739 turnoutCircleColor = getDefaultTrackColorColor(); 7740 } else { 7741 turnoutCircleColor = color; 7742 JmriColorChooser.addRecentColor(color); 7743 } 7744 } 7745 7746 /** 7747 * @param color new color for turnout circle. 7748 */ 7749 public void setTurnoutCircleThrownColor(@CheckForNull Color color) { 7750 if (color == null) { 7751 turnoutCircleThrownColor = getDefaultTrackColorColor(); 7752 } else { 7753 turnoutCircleThrownColor = color; 7754 JmriColorChooser.addRecentColor(color); 7755 } 7756 } 7757 7758 /** 7759 * Should only be invoked on the GUI (Swing) thread. 7760 * 7761 * @param state true to fill in turnout control circles, else false. 7762 */ 7763 @InvokeOnGuiThread 7764 public void setTurnoutFillControlCircles(boolean state) { 7765 if (turnoutFillControlCircles != state) { 7766 turnoutFillControlCircles = state; 7767 turnoutFillControlCirclesCheckBoxMenuItem.setSelected(turnoutFillControlCircles); 7768 } 7769 } 7770 7771 public void setTurnoutCircleSize(int size) { 7772 // this is an int 7773 turnoutCircleSize = size; 7774 7775 // these are doubles 7776 circleRadius = SIZE * size; 7777 circleDiameter = 2.0 * circleRadius; 7778 7779 setOptionMenuTurnoutCircleSize(); 7780 } 7781 7782 /** 7783 * Should only be invoked on the GUI (Swing) thread. 7784 * 7785 * @param state true to draw unselected legs, else false. 7786 */ 7787 @InvokeOnGuiThread 7788 public void setTurnoutDrawUnselectedLeg(boolean state) { 7789 if (turnoutDrawUnselectedLeg != state) { 7790 turnoutDrawUnselectedLeg = state; 7791 turnoutDrawUnselectedLegCheckBoxMenuItem.setSelected(turnoutDrawUnselectedLeg); 7792 } 7793 } 7794 7795 /** 7796 * Should only be invoked on the GUI (Swing) thread. 7797 * 7798 * @param state true to enable highlighting the cursor (mouse/finger press/drag) 7799 */ 7800 @InvokeOnGuiThread 7801 public void setHighlightCursor(boolean state) { 7802 if (highlightCursor != state) { 7803 highlightCursor = state; 7804 highlightCursorCheckBoxMenuItem.setSelected(highlightCursor); 7805 } 7806 } 7807 7808 /** 7809 * @param color value to set the default text color to. 7810 */ 7811 public void setDefaultTextColor(@Nonnull Color color) { 7812 defaultTextColor = color; 7813 JmriColorChooser.addRecentColor(color); 7814 } 7815 7816 /** 7817 * @param color value to set the panel background to. 7818 */ 7819 public void setDefaultBackgroundColor(@Nonnull Color color) { 7820 defaultBackgroundColor = color; 7821 JmriColorChooser.addRecentColor(color); 7822 } 7823 7824 public void setLayoutName(@Nonnull String name) { 7825 layoutName = name; 7826 } 7827 7828 /** 7829 * Should only be invoked on the GUI (Swing) thread. 7830 * 7831 * @param state true to show the help bar, else false. 7832 */ 7833 @InvokeOnGuiThread // due to the setSelected call on a possibly-visible item 7834 public void setShowHelpBar(boolean state) { 7835 if (showHelpBar != state) { 7836 showHelpBar = state; 7837 7838 // these may not be set up yet... 7839 if (showHelpCheckBoxMenuItem != null) { 7840 showHelpCheckBoxMenuItem.setSelected(showHelpBar); 7841 } 7842 7843 if (toolBarSide.equals(ToolBarSide.eFLOAT)) { 7844 if (floatEditHelpPanel != null) { 7845 floatEditHelpPanel.setVisible(isEditable() && showHelpBar); 7846 } 7847 } else { 7848 if (helpBarPanel != null) { 7849 helpBarPanel.setVisible(isEditable() && showHelpBar); 7850 7851 } 7852 } 7853 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".showHelpBar", showHelpBar)); 7854 } 7855 } 7856 7857 /** 7858 * Should only be invoked on the GUI (Swing) thread. 7859 * 7860 * @param state true to show the draw grid, else false. 7861 */ 7862 @InvokeOnGuiThread 7863 public void setDrawGrid(boolean state) { 7864 if (drawGrid != state) { 7865 drawGrid = state; 7866 showGridCheckBoxMenuItem.setSelected(drawGrid); 7867 } 7868 } 7869 7870 /** 7871 * Should only be invoked on the GUI (Swing) thread. 7872 * 7873 * @param state true to set snap to grid on add, else false. 7874 */ 7875 @InvokeOnGuiThread 7876 public void setSnapOnAdd(boolean state) { 7877 if (snapToGridOnAdd != state) { 7878 snapToGridOnAdd = state; 7879 snapToGridOnAddCheckBoxMenuItem.setSelected(snapToGridOnAdd); 7880 } 7881 } 7882 7883 /** 7884 * Should only be invoked on the GUI (Swing) thread. 7885 * 7886 * @param state true to set snap on move, else false. 7887 */ 7888 @InvokeOnGuiThread 7889 public void setSnapOnMove(boolean state) { 7890 if (snapToGridOnMove != state) { 7891 snapToGridOnMove = state; 7892 snapToGridOnMoveCheckBoxMenuItem.setSelected(snapToGridOnMove); 7893 } 7894 } 7895 7896 /** 7897 * Should only be invoked on the GUI (Swing) thread. 7898 * 7899 * @param state true to set anti-aliasing flag on, else false. 7900 */ 7901 @InvokeOnGuiThread 7902 public void setAntialiasingOn(boolean state) { 7903 if (antialiasingOn != state) { 7904 antialiasingOn = state; 7905 7906 // this may not be set up yet... 7907 if (antialiasingOnCheckBoxMenuItem != null) { 7908 antialiasingOnCheckBoxMenuItem.setSelected(antialiasingOn); 7909 7910 } 7911 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".antialiasingOn", antialiasingOn)); 7912 } 7913 } 7914 7915 /** 7916 * 7917 * @param state true to set anti-aliasing flag on, else false. 7918 */ 7919 public void setDrawLayoutTracksLabel(boolean state) { 7920 if (drawLayoutTracksLabel != state) { 7921 drawLayoutTracksLabel = state; 7922 7923 // this may not be set up yet... 7924 if (drawLayoutTracksLabelCheckBoxMenuItem != null) { 7925 drawLayoutTracksLabelCheckBoxMenuItem.setSelected(drawLayoutTracksLabel); 7926 7927 } 7928 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".drawLayoutTracksLabel", drawLayoutTracksLabel)); 7929 } 7930 } 7931 7932 // enable/disable using the "Extra" color to highlight the selected block 7933 public void setHighlightSelectedBlock(boolean state) { 7934 if (highlightSelectedBlockFlag != state) { 7935 highlightSelectedBlockFlag = state; 7936 7937 // this may not be set up yet... 7938 if (leToolBarPanel.highlightBlockCheckBox != null) { 7939 leToolBarPanel.highlightBlockCheckBox.setSelected(highlightSelectedBlockFlag); 7940 7941 } 7942 7943 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".highlightSelectedBlock", highlightSelectedBlockFlag)); 7944 7945 // thread this so it won't break the AppVeyor checks 7946 ThreadingUtil.newThread(() -> { 7947 if (highlightSelectedBlockFlag) { 7948 // use the "Extra" color to highlight the selected block 7949 if (!highlightBlockInComboBox(leToolBarPanel.blockIDComboBox)) { 7950 highlightBlockInComboBox(leToolBarPanel.blockContentsComboBox); 7951 } 7952 } else { 7953 // undo using the "Extra" color to highlight the selected block 7954 Block block = leToolBarPanel.blockIDComboBox.getSelectedItem(); 7955 highlightBlock(null); 7956 leToolBarPanel.blockIDComboBox.setSelectedItem(block); 7957 } 7958 }).start(); 7959 } 7960 } 7961 7962 // 7963 // highlight the block selected by the specified combo Box 7964 // 7965 public boolean highlightBlockInComboBox(@Nonnull NamedBeanComboBox<Block> inComboBox) { 7966 return highlightBlock(inComboBox.getSelectedItem()); 7967 } 7968 7969 /** 7970 * highlight the specified block 7971 * 7972 * @param inBlock the block 7973 * @return true if block was highlighted 7974 */ 7975 public boolean highlightBlock(@CheckForNull Block inBlock) { 7976 boolean result = false; // assume failure (pessimist!) 7977 7978 if (leToolBarPanel.blockIDComboBox.getSelectedItem() != inBlock) { 7979 leToolBarPanel.blockIDComboBox.setSelectedItem(inBlock); 7980 } 7981 7982 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class 7983 ); 7984 Set<Block> l = leToolBarPanel.blockIDComboBox.getManager().getNamedBeanSet(); 7985 for (Block b : l) { 7986 LayoutBlock lb = lbm.getLayoutBlock(b); 7987 if (lb != null) { 7988 boolean enable = ((inBlock != null) && b.equals(inBlock)); 7989 lb.setUseExtraColor(enable); 7990 result |= enable; 7991 } 7992 } 7993 return result; 7994 } 7995 7996 /** 7997 * highlight the specified layout block 7998 * 7999 * @param inLayoutBlock the layout block 8000 * @return true if layout block was highlighted 8001 */ 8002 public boolean highlightLayoutBlock(@Nonnull LayoutBlock inLayoutBlock) { 8003 return highlightBlock(inLayoutBlock.getBlock()); 8004 } 8005 8006 public void setTurnoutCircles(boolean state) { 8007 if (turnoutCirclesWithoutEditMode != state) { 8008 turnoutCirclesWithoutEditMode = state; 8009 if (turnoutCirclesOnCheckBoxMenuItem != null) { 8010 turnoutCirclesOnCheckBoxMenuItem.setSelected(turnoutCirclesWithoutEditMode); 8011 } 8012 } 8013 } 8014 8015 public void setAutoBlockAssignment(boolean boo) { 8016 if (autoAssignBlocks != boo) { 8017 autoAssignBlocks = boo; 8018 if (autoAssignBlocksCheckBoxMenuItem != null) { 8019 autoAssignBlocksCheckBoxMenuItem.setSelected(autoAssignBlocks); 8020 } 8021 } 8022 } 8023 8024 public void setTooltipsNotEdit(boolean state) { 8025 if (tooltipsWithoutEditMode != state) { 8026 tooltipsWithoutEditMode = state; 8027 setTooltipSubMenu(); 8028 setTooltipsAlwaysOrNever(); 8029 } 8030 } 8031 8032 public void setTooltipsInEdit(boolean state) { 8033 if (tooltipsInEditMode != state) { 8034 tooltipsInEditMode = state; 8035 setTooltipSubMenu(); 8036 setTooltipsAlwaysOrNever(); 8037 } 8038 } 8039 8040 private void setTooltipsAlwaysOrNever() { 8041 tooltipsAlwaysOrNever = ((tooltipsInEditMode && tooltipsWithoutEditMode) || 8042 (!tooltipsInEditMode && !tooltipsWithoutEditMode)); 8043 } 8044 8045 private void setTooltipSubMenu() { 8046 if (tooltipNoneMenuItem != null) { 8047 tooltipNoneMenuItem.setSelected((!tooltipsInEditMode) && (!tooltipsWithoutEditMode)); 8048 tooltipAlwaysMenuItem.setSelected((tooltipsInEditMode) && (tooltipsWithoutEditMode)); 8049 tooltipInEditMenuItem.setSelected((tooltipsInEditMode) && (!tooltipsWithoutEditMode)); 8050 tooltipNotInEditMenuItem.setSelected((!tooltipsInEditMode) && (tooltipsWithoutEditMode)); 8051 } 8052 } 8053 8054 // accessor routines for turnout size parameters 8055 public void setTurnoutBX(double bx) { 8056 turnoutBX = bx; 8057 setDirty(); 8058 } 8059 8060 public double getTurnoutBX() { 8061 return turnoutBX; 8062 } 8063 8064 public void setTurnoutCX(double cx) { 8065 turnoutCX = cx; 8066 setDirty(); 8067 } 8068 8069 public double getTurnoutCX() { 8070 return turnoutCX; 8071 } 8072 8073 public void setTurnoutWid(double wid) { 8074 turnoutWid = wid; 8075 setDirty(); 8076 } 8077 8078 public double getTurnoutWid() { 8079 return turnoutWid; 8080 } 8081 8082 public void setXOverLong(double lg) { 8083 xOverLong = lg; 8084 setDirty(); 8085 } 8086 8087 public double getXOverLong() { 8088 return xOverLong; 8089 } 8090 8091 public void setXOverHWid(double hwid) { 8092 xOverHWid = hwid; 8093 setDirty(); 8094 } 8095 8096 public double getXOverHWid() { 8097 return xOverHWid; 8098 } 8099 8100 public void setXOverShort(double sh) { 8101 xOverShort = sh; 8102 setDirty(); 8103 } 8104 8105 public double getXOverShort() { 8106 return xOverShort; 8107 } 8108 8109 // reset turnout sizes to program defaults 8110 private void resetTurnoutSize() { 8111 turnoutBX = LayoutTurnout.turnoutBXDefault; 8112 turnoutCX = LayoutTurnout.turnoutCXDefault; 8113 turnoutWid = LayoutTurnout.turnoutWidDefault; 8114 xOverLong = LayoutTurnout.xOverLongDefault; 8115 xOverHWid = LayoutTurnout.xOverHWidDefault; 8116 xOverShort = LayoutTurnout.xOverShortDefault; 8117 setDirty(); 8118 } 8119 8120 public void setDirectTurnoutControl(boolean boo) { 8121 useDirectTurnoutControl = boo; 8122 useDirectTurnoutControlCheckBoxMenuItem.setSelected(useDirectTurnoutControl); 8123 } 8124 8125 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 8126 public boolean getDirectTurnoutControl() { 8127 return useDirectTurnoutControl; 8128 } 8129 8130 // final initialization routine for loading a LayoutEditor 8131 public void setConnections() { 8132 getLayoutTracks().forEach((lt) -> lt.setObjects(this)); 8133 getLEAuxTools().initializeBlockConnectivity(); 8134 log.debug("Initializing Block Connectivity for {}", getLayoutName()); 8135 8136 // reset the panel changed bit 8137 resetDirty(); 8138 } 8139 8140 // these are convenience methods to return rectangles 8141 // to use when (hit point-in-rect testing 8142 // 8143 // compute the control point rect at inPoint 8144 public @Nonnull 8145 Rectangle2D layoutEditorControlRectAt(@Nonnull Point2D inPoint) { 8146 return new Rectangle2D.Double(inPoint.getX() - SIZE, 8147 inPoint.getY() - SIZE, SIZE2, SIZE2); 8148 } 8149 8150 // compute the turnout circle control rect at inPoint 8151 public @Nonnull 8152 Rectangle2D layoutEditorControlCircleRectAt(@Nonnull Point2D inPoint) { 8153 return new Rectangle2D.Double(inPoint.getX() - circleRadius, 8154 inPoint.getY() - circleRadius, circleDiameter, circleDiameter); 8155 } 8156 8157 /** 8158 * Special internal class to allow drawing of layout to a JLayeredPane This 8159 * is the 'target' pane where the layout is displayed 8160 */ 8161 @Override 8162 public void paintTargetPanel(@Nonnull Graphics g) { 8163 // Nothing to do here 8164 // All drawing has been moved into LayoutEditorComponent 8165 // which calls draw. 8166 // This is so the layout is drawn at level three 8167 // (above or below the Positionables) 8168 } 8169 8170 // get selection rectangle 8171 @Nonnull 8172 public Rectangle2D getSelectionRect() { 8173 double selX = Math.min(selectionX, selectionX + selectionWidth); 8174 double selY = Math.min(selectionY, selectionY + selectionHeight); 8175 return new Rectangle2D.Double(selX, selY, 8176 Math.abs(selectionWidth), Math.abs(selectionHeight)); 8177 } 8178 8179 // set selection rectangle 8180 public void setSelectionRect(@Nonnull Rectangle2D selectionRect) { 8181 // selectionRect = selectionRect.createIntersection(MathUtil.zeroToInfinityRectangle2D); 8182 selectionX = selectionRect.getX(); 8183 selectionY = selectionRect.getY(); 8184 selectionWidth = selectionRect.getWidth(); 8185 selectionHeight = selectionRect.getHeight(); 8186 8187 // There's already code in the super class (Editor) to draw 8188 // the selection rect... We just have to set _selectRect 8189 _selectRect = MathUtil.rectangle2DToRectangle(selectionRect); 8190 8191 selectionRect = MathUtil.scale(selectionRect, getZoom()); 8192 8193 JComponent targetPanel = getTargetPanel(); 8194 Rectangle targetRect = targetPanel.getVisibleRect(); 8195 // this will make it the size of the targetRect 8196 // (effectively centering it onscreen) 8197 Rectangle2D selRect2D = MathUtil.inset(selectionRect, 8198 (selectionRect.getWidth() - targetRect.getWidth()) / 2.0, 8199 (selectionRect.getHeight() - targetRect.getHeight()) / 2.0); 8200 // don't let the origin go negative 8201 selRect2D = selRect2D.createIntersection(MathUtil.zeroToInfinityRectangle2D); 8202 Rectangle selRect = MathUtil.rectangle2DToRectangle(selRect2D); 8203 if (!targetRect.contains(selRect)) { 8204 targetPanel.scrollRectToVisible(selRect); 8205 } 8206 8207 clearSelectionGroups(); 8208 selectionActive = true; 8209 createSelectionGroups(); 8210 // redrawPanel(); // createSelectionGroups already calls this 8211 } 8212 8213 public void setSelectRect(Rectangle rectangle) { 8214 _selectRect = rectangle; 8215 } 8216 8217 /* 8218 // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class 8219 public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<LayoutTrack> layoutTrackClass) { 8220 return getLayoutTracks().stream() 8221 .filter(item -> item instanceof PositionablePoint) 8222 .filter(layoutTrackClass::isInstance) 8223 //.map(layoutTrackClass::cast) // TODO: Do we need this? if not dead-code-strip 8224 .collect(Collectors.toList()); 8225 } 8226 8227 // TODO: This compiles but I can't get the syntax correct to pass the array of (sub-)classes 8228 public List<LayoutTrack> getLayoutTracksOfClasses(@Nonnull List<Class<? extends LayoutTrack>> layoutTrackClasses) { 8229 return getLayoutTracks().stream() 8230 .filter(o -> layoutTrackClasses.contains(o.getClass())) 8231 .collect(Collectors.toList()); 8232 } 8233 8234 // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class 8235 public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<? extends LayoutTrack> layoutTrackClass) { 8236 return getLayoutTracksOfClasses(new ArrayList<>(Arrays.asList(layoutTrackClass))); 8237 } 8238 8239 public List<PositionablePoint> getPositionablePoints() { 8240 return getLayoutTracksOfClass(PositionablePoint); 8241 } 8242 */ 8243 @Override 8244 public @Nonnull 8245 Stream<LayoutTrack> getLayoutTracksOfClass(Class<? extends LayoutTrack> layoutTrackClass) { 8246 return getLayoutTracks().stream() 8247 .filter(layoutTrackClass::isInstance) 8248 .map(layoutTrackClass::cast); 8249 } 8250 8251 @Override 8252 public @Nonnull 8253 Stream<LayoutTrackView> getLayoutTrackViewsOfClass(Class<? extends LayoutTrackView> layoutTrackViewClass) { 8254 return getLayoutTrackViews().stream() 8255 .filter(layoutTrackViewClass::isInstance) 8256 .map(layoutTrackViewClass::cast); 8257 } 8258 8259 @Override 8260 public @Nonnull 8261 List<PositionablePointView> getPositionablePointViews() { 8262 return getLayoutTrackViewsOfClass(PositionablePointView.class) 8263 .map(PositionablePointView.class::cast) 8264 .collect(Collectors.toCollection(ArrayList::new)); 8265 } 8266 8267 @Override 8268 public @Nonnull 8269 List<PositionablePoint> getPositionablePoints() { 8270 return getLayoutTracksOfClass(PositionablePoint.class) 8271 .map(PositionablePoint.class::cast) 8272 .collect(Collectors.toCollection(ArrayList::new)); 8273 } 8274 8275 public @Nonnull 8276 List<LayoutSlipView> getLayoutSlipViews() { 8277 return getLayoutTrackViewsOfClass(LayoutSlipView.class) 8278 .map(LayoutSlipView.class::cast) 8279 .collect(Collectors.toCollection(ArrayList::new)); 8280 } 8281 8282 @Override 8283 public @Nonnull 8284 List<LayoutSlip> getLayoutSlips() { 8285 return getLayoutTracksOfClass(LayoutSlip.class) 8286 .map(LayoutSlip.class::cast) 8287 .collect(Collectors.toCollection(ArrayList::new)); 8288 } 8289 8290 @Override 8291 public @Nonnull 8292 List<TrackSegmentView> getTrackSegmentViews() { 8293 return getLayoutTrackViewsOfClass(TrackSegmentView.class) 8294 .map(TrackSegmentView.class::cast) 8295 .collect(Collectors.toCollection(ArrayList::new)); 8296 } 8297 8298 @Override 8299 public @Nonnull 8300 List<TrackSegment> getTrackSegments() { 8301 return getLayoutTracksOfClass(TrackSegment.class) 8302 .map(TrackSegment.class::cast) 8303 .collect(Collectors.toCollection(ArrayList::new)); 8304 } 8305 8306 public @Nonnull 8307 List<LayoutTurnoutView> getLayoutTurnoutViews() { // this specifically does not include slips 8308 return getLayoutTrackViews().stream() // next line excludes LayoutSlips 8309 .filter((o) -> (!(o instanceof LayoutSlipView) && (o instanceof LayoutTurnoutView))) 8310 .map(LayoutTurnoutView.class::cast) 8311 .collect(Collectors.toCollection(ArrayList::new)); 8312 } 8313 8314 @Override 8315 public @Nonnull 8316 List<LayoutTurnout> getLayoutTurnouts() { // this specifically does not include slips 8317 return getLayoutTracks().stream() // next line excludes LayoutSlips 8318 .filter((o) -> (!(o instanceof LayoutSlip) && (o instanceof LayoutTurnout))) 8319 .map(LayoutTurnout.class::cast) 8320 .collect(Collectors.toCollection(ArrayList::new)); 8321 } 8322 8323 @Override 8324 public @Nonnull 8325 List<LayoutTurntable> getLayoutTurntables() { 8326 return getLayoutTracksOfClass(LayoutTurntable.class) 8327 .map(LayoutTurntable.class::cast) 8328 .collect(Collectors.toCollection(ArrayList::new)); 8329 } 8330 8331 public @Nonnull 8332 List<LayoutTurntableView> getLayoutTurntableViews() { 8333 return getLayoutTrackViewsOfClass(LayoutTurntableView.class) 8334 .map(LayoutTurntableView.class::cast) 8335 .collect(Collectors.toCollection(ArrayList::new)); 8336 } 8337 8338 @Override 8339 public @Nonnull 8340 List<LevelXing> getLevelXings() { 8341 return getLayoutTracksOfClass(LevelXing.class) 8342 .map(LevelXing.class::cast) 8343 .collect(Collectors.toCollection(ArrayList::new)); 8344 } 8345 8346 @Override 8347 public @Nonnull 8348 List<LevelXingView> getLevelXingViews() { 8349 return getLayoutTrackViewsOfClass(LevelXingView.class) 8350 .map(LevelXingView.class::cast) 8351 .collect(Collectors.toCollection(ArrayList::new)); 8352 } 8353 8354 /** 8355 * Read-only access to the list of LayoutTrack family objects. The returned 8356 * list will throw UnsupportedOperationException if you attempt to modify 8357 * it. 8358 * 8359 * @return unmodifiable copy of layout track list. 8360 */ 8361 @Override 8362 @Nonnull 8363 final public List<LayoutTrack> getLayoutTracks() { 8364 return Collections.unmodifiableList(layoutTrackList); 8365 } 8366 8367 public @Nonnull 8368 List<LayoutTurnoutView> getLayoutTurnoutAndSlipViews() { 8369 return getLayoutTrackViewsOfClass(LayoutTurnoutView.class 8370 ) 8371 .map(LayoutTurnoutView.class::cast) 8372 .collect(Collectors.toCollection(ArrayList::new)); 8373 } 8374 8375 @Override 8376 public @Nonnull 8377 List<LayoutTurnout> getLayoutTurnoutsAndSlips() { 8378 return getLayoutTracksOfClass(LayoutTurnout.class 8379 ) 8380 .map(LayoutTurnout.class::cast) 8381 .collect(Collectors.toCollection(ArrayList::new)); 8382 } 8383 8384 /** 8385 * Read-only access to the list of LayoutTrackView family objects. The 8386 * returned list will throw UnsupportedOperationException if you attempt to 8387 * modify it. 8388 * 8389 * @return unmodifiable copy of track views. 8390 */ 8391 @Override 8392 @Nonnull 8393 final public List<LayoutTrackView> getLayoutTrackViews() { 8394 return Collections.unmodifiableList(layoutTrackViewList); 8395 } 8396 8397 private final List<LayoutTrack> layoutTrackList = new ArrayList<>(); 8398 private final List<LayoutTrackView> layoutTrackViewList = new ArrayList<>(); 8399 private final Map<LayoutTrack, LayoutTrackView> trkToView = new HashMap<>(); 8400 private final Map<LayoutTrackView, LayoutTrack> viewToTrk = new HashMap<>(); 8401 8402 // temporary 8403 @Override 8404 final public LayoutTrackView getLayoutTrackView(LayoutTrack trk) { 8405 LayoutTrackView lv = trkToView.get(trk); 8406 if (lv == null) { 8407 log.warn("No View found for {} class {}", trk, trk.getClass()); 8408 throw new IllegalArgumentException("No View found: " + trk.getClass()); 8409 } 8410 return lv; 8411 } 8412 8413 // temporary 8414 @Override 8415 final public LevelXingView getLevelXingView(LevelXing xing) { 8416 LayoutTrackView lv = trkToView.get(xing); 8417 if (lv == null) { 8418 log.warn("No View found for {} class {}", xing, xing.getClass()); 8419 throw new IllegalArgumentException("No View found: " + xing.getClass()); 8420 } 8421 if (lv instanceof LevelXingView) { 8422 return (LevelXingView) lv; 8423 } else { 8424 log.error("wrong type {} {} found {}", xing, xing.getClass(), lv); 8425 } 8426 throw new IllegalArgumentException("Wrong type: " + xing.getClass()); 8427 } 8428 8429 // temporary 8430 @Override 8431 final public LayoutTurnoutView getLayoutTurnoutView(LayoutTurnout to) { 8432 LayoutTrackView lv = trkToView.get(to); 8433 if (lv == null) { 8434 log.warn("No View found for {} class {}", to, to.getClass()); 8435 throw new IllegalArgumentException("No View found: " + to); 8436 } 8437 if (lv instanceof LayoutTurnoutView) { 8438 return (LayoutTurnoutView) lv; 8439 } else { 8440 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8441 } 8442 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8443 } 8444 8445 // temporary 8446 @Override 8447 final public LayoutTurntableView getLayoutTurntableView(LayoutTurntable to) { 8448 LayoutTrackView lv = trkToView.get(to); 8449 if (lv == null) { 8450 log.warn("No View found for {} class {}", to, to.getClass()); 8451 throw new IllegalArgumentException("No matching View found: " + to); 8452 } 8453 if (lv instanceof LayoutTurntableView) { 8454 return (LayoutTurntableView) lv; 8455 } else { 8456 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8457 } 8458 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8459 } 8460 8461 // temporary 8462 final public LayoutSlipView getLayoutSlipView(LayoutSlip to) { 8463 LayoutTrackView lv = trkToView.get(to); 8464 if (lv == null) { 8465 log.warn("No View found for {} class {}", to, to.getClass()); 8466 throw new IllegalArgumentException("No matching View found: " + to); 8467 } 8468 if (lv instanceof LayoutSlipView) { 8469 return (LayoutSlipView) lv; 8470 } else { 8471 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8472 } 8473 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8474 } 8475 8476 // temporary 8477 @Override 8478 final public TrackSegmentView getTrackSegmentView(TrackSegment to) { 8479 LayoutTrackView lv = trkToView.get(to); 8480 if (lv == null) { 8481 log.warn("No View found for {} class {}", to, to.getClass()); 8482 throw new IllegalArgumentException("No matching View found: " + to); 8483 } 8484 if (lv instanceof TrackSegmentView) { 8485 return (TrackSegmentView) lv; 8486 } else { 8487 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8488 } 8489 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8490 } 8491 8492 // temporary 8493 @Override 8494 final public PositionablePointView getPositionablePointView(PositionablePoint to) { 8495 LayoutTrackView lv = trkToView.get(to); 8496 if (lv == null) { 8497 log.warn("No View found for {} class {}", to, to.getClass()); 8498 throw new IllegalArgumentException("No matching View found: " + to); 8499 } 8500 if (lv instanceof PositionablePointView) { 8501 return (PositionablePointView) lv; 8502 } else { 8503 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8504 } 8505 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8506 } 8507 8508 /** 8509 * Add a LayoutTrack and LayoutTrackView to the list of LayoutTrack family 8510 * objects. 8511 * 8512 * @param trk the layout track to add. 8513 */ 8514 @Override 8515 final public void addLayoutTrack(@Nonnull LayoutTrack trk, @Nonnull LayoutTrackView v) { 8516 log.trace("addLayoutTrack {}", trk); 8517 if (layoutTrackList.contains(trk)) { 8518 log.warn("LayoutTrack {} already being maintained", trk.getName()); 8519 } 8520 8521 layoutTrackList.add(trk); 8522 layoutTrackViewList.add(v); 8523 trkToView.put(trk, v); 8524 viewToTrk.put(v, trk); 8525 8526 unionToPanelBounds(v.getBounds()); // temporary - this should probably _not_ be in the topological part 8527 8528 } 8529 8530 /** 8531 * If item present, delete from the list of LayoutTracks and force a dirty 8532 * redraw. 8533 * 8534 * @param trk the layout track to remove and redraw. 8535 * @return true is item was deleted and a redraw done. 8536 */ 8537 final public boolean removeLayoutTrackAndRedraw(@Nonnull LayoutTrack trk) { 8538 log.trace("removeLayoutTrackAndRedraw {}", trk); 8539 if (layoutTrackList.contains(trk)) { 8540 removeLayoutTrack(trk); 8541 setDirty(); 8542 redrawPanel(); 8543 log.trace("removeLayoutTrackAndRedraw present {}", trk); 8544 return true; 8545 } 8546 log.trace("removeLayoutTrackAndRedraw absent {}", trk); 8547 return false; 8548 } 8549 8550 /** 8551 * If item present, delete from the list of LayoutTracks and force a dirty 8552 * redraw. 8553 * 8554 * @param trk the layout track to remove. 8555 */ 8556 @Override 8557 final public void removeLayoutTrack(@Nonnull LayoutTrack trk) { 8558 log.trace("removeLayoutTrack {}", trk); 8559 layoutTrackList.remove(trk); 8560 LayoutTrackView v = trkToView.get(trk); 8561 layoutTrackViewList.remove(v); 8562 trkToView.remove(trk); 8563 viewToTrk.remove(v); 8564 } 8565 8566 /** 8567 * Clear the list of layout tracks. Not intended for general use. 8568 * <p> 8569 */ 8570 private void clearLayoutTracks() { 8571 layoutTrackList.clear(); 8572 layoutTrackViewList.clear(); 8573 trkToView.clear(); 8574 viewToTrk.clear(); 8575 } 8576 8577 @Override 8578 public @Nonnull 8579 List<LayoutShape> getLayoutShapes() { 8580 return layoutShapes; 8581 } 8582 8583 public void sortLayoutShapesByLevel() { 8584 layoutShapes.sort((lhs, rhs) -> { 8585 // -1 == less than, 0 == equal, +1 == greater than 8586 return Integer.signum(lhs.getLevel() - rhs.getLevel()); 8587 }); 8588 } 8589 8590 /** 8591 * {@inheritDoc} 8592 * <p> 8593 * This implementation is temporary, using the on-screen points from the 8594 * LayoutTrackViews via @{link LayoutEditor#getCoords}. 8595 */ 8596 @Override 8597 public int computeDirection(LayoutTrack trk1, HitPointType h1, LayoutTrack trk2, HitPointType h2) { 8598 return Path.computeDirection( 8599 getCoords(trk1, h1), 8600 getCoords(trk2, h2) 8601 ); 8602 } 8603 8604 @Override 8605 public int computeDirectionToCenter(@Nonnull LayoutTrack trk1, @Nonnull HitPointType h1, @Nonnull PositionablePoint p) { 8606 return Path.computeDirection( 8607 getCoords(trk1, h1), 8608 getPositionablePointView(p).getCoordsCenter() 8609 ); 8610 } 8611 8612 @Override 8613 public int computeDirectionFromCenter(@Nonnull PositionablePoint p, @Nonnull LayoutTrack trk1, @Nonnull HitPointType h1) { 8614 return Path.computeDirection( 8615 getPositionablePointView(p).getCoordsCenter(), 8616 getCoords(trk1, h1) 8617 ); 8618 } 8619 8620 @Override 8621 public boolean showAlignPopup(@Nonnull Positionable l) { 8622 return false; 8623 } 8624 8625 @Override 8626 public void showToolTip( 8627 @Nonnull Positionable selection, 8628 @Nonnull JmriMouseEvent event) { 8629 ToolTip tip = selection.getToolTip(); 8630 tip.setLocation(selection.getX() + selection.getWidth() / 2, selection.getY() + selection.getHeight()); 8631 setToolTip(tip); 8632 } 8633 8634 @Override 8635 public void addToPopUpMenu( 8636 @Nonnull NamedBean nb, 8637 @Nonnull JMenuItem item, 8638 int menu) { 8639 if ((nb == null) || (item == null)) { 8640 return; 8641 } 8642 8643 List<?> theList = null; 8644 8645 if (nb instanceof Sensor) { 8646 theList = sensorList; 8647 } else if (nb instanceof SignalHead) { 8648 theList = signalList; 8649 } else if (nb instanceof SignalMast) { 8650 theList = signalMastList; 8651 } else if (nb instanceof Block) { 8652 theList = blockContentsLabelList; 8653 } else if (nb instanceof Memory) { 8654 theList = memoryLabelList; 8655 } else if (nb instanceof GlobalVariable) { 8656 theList = globalVariableLabelList; 8657 } 8658 if (theList != null) { 8659 for (Object o : theList) { 8660 PositionableLabel si = (PositionableLabel) o; 8661 if ((si.getNamedBean() == nb) && (si.getPopupUtility() != null)) { 8662 if (menu != Editor.VIEWPOPUPONLY) { 8663 si.getPopupUtility().addEditPopUpMenu(item); 8664 } 8665 if (menu != Editor.EDITPOPUPONLY) { 8666 si.getPopupUtility().addViewPopUpMenu(item); 8667 } 8668 } 8669 } 8670 } else if (nb instanceof Turnout) { 8671 for (LayoutTurnoutView ltv : getLayoutTurnoutAndSlipViews()) { 8672 if (ltv.getTurnout().equals(nb)) { 8673 if (menu != Editor.VIEWPOPUPONLY) { 8674 ltv.addEditPopUpMenu(item); 8675 } 8676 if (menu != Editor.EDITPOPUPONLY) { 8677 ltv.addViewPopUpMenu(item); 8678 } 8679 } 8680 } 8681 } 8682 } 8683 8684 @Override 8685 public @Nonnull 8686 String toString() { 8687 return String.format("LayoutEditor: %s", getLayoutName()); 8688 } 8689 8690 @Override 8691 public void vetoableChange( 8692 @Nonnull PropertyChangeEvent evt) 8693 throws PropertyVetoException { 8694 NamedBean nb = (NamedBean) evt.getOldValue(); 8695 8696 if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N 8697 StringBuilder message = new StringBuilder(); 8698 message.append(Bundle.getMessage("VetoInUseLayoutEditorHeader", toString())); // NOI18N 8699 message.append("<ul>"); 8700 boolean found = false; 8701 8702 if (nb instanceof SignalHead) { 8703 if (containsSignalHead((SignalHead) nb)) { 8704 found = true; 8705 message.append("<li>"); 8706 message.append(Bundle.getMessage("VetoSignalHeadIconFound")); 8707 message.append("</li>"); 8708 } 8709 LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb); 8710 8711 if (lt != null) { 8712 message.append("<li>"); 8713 message.append(Bundle.getMessage("VetoSignalHeadAssignedToTurnout", lt.getTurnoutName())); 8714 message.append("</li>"); 8715 } 8716 PositionablePoint p = finder.findPositionablePointByBean(nb); 8717 8718 if (p != null) { 8719 message.append("<li>"); 8720 // Need to expand to get the names of blocks 8721 message.append(Bundle.getMessage("VetoSignalHeadAssignedToPoint")); 8722 message.append("</li>"); 8723 } 8724 LevelXing lx = finder.findLevelXingByBean(nb); 8725 8726 if (lx != null) { 8727 message.append("<li>"); 8728 // Need to expand to get the names of blocks 8729 message.append(Bundle.getMessage("VetoSignalHeadAssignedToLevelXing")); 8730 message.append("</li>"); 8731 } 8732 LayoutSlip ls = finder.findLayoutSlipByBean(nb); 8733 8734 if (ls != null) { 8735 message.append("<li>"); 8736 message.append(Bundle.getMessage("VetoSignalHeadAssignedToLayoutSlip", ls.getTurnoutName())); 8737 message.append("</li>"); 8738 } 8739 } else if (nb instanceof Turnout) { 8740 LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb); 8741 8742 if (lt != null) { 8743 found = true; 8744 message.append("<li>"); 8745 message.append(Bundle.getMessage("VetoTurnoutIconFound")); 8746 message.append("</li>"); 8747 } 8748 8749 for (LayoutTurnout t : getLayoutTurnouts()) { 8750 if (t.getLinkedTurnoutName() != null) { 8751 String uname = nb.getUserName(); 8752 8753 if (nb.getSystemName().equals(t.getLinkedTurnoutName()) 8754 || ((uname != null) && uname.equals(t.getLinkedTurnoutName()))) { 8755 found = true; 8756 message.append("<li>"); 8757 message.append(Bundle.getMessage("VetoLinkedTurnout", t.getTurnoutName())); 8758 message.append("</li>"); 8759 } 8760 } 8761 8762 if (nb.equals(t.getSecondTurnout())) { 8763 found = true; 8764 message.append("<li>"); 8765 message.append(Bundle.getMessage("VetoSecondTurnout", t.getTurnoutName())); 8766 message.append("</li>"); 8767 } 8768 } 8769 LayoutSlip ls = finder.findLayoutSlipByBean(nb); 8770 8771 if (ls != null) { 8772 found = true; 8773 message.append("<li>"); 8774 message.append(Bundle.getMessage("VetoSlipIconFound", ls.getDisplayName())); 8775 message.append("</li>"); 8776 } 8777 8778 for (LayoutTurntable lx : getLayoutTurntables()) { 8779 if (lx.isTurnoutControlled()) { 8780 for (int i = 0; i < lx.getNumberRays(); i++) { 8781 if (nb.equals(lx.getRayTurnout(i))) { 8782 found = true; 8783 message.append("<li>"); 8784 message.append(Bundle.getMessage("VetoRayTurntableControl", lx.getId())); 8785 message.append("</li>"); 8786 break; 8787 } 8788 } 8789 } 8790 } 8791 } 8792 8793 if (nb instanceof SignalMast) { 8794 if (containsSignalMast((SignalMast) nb)) { 8795 message.append("<li>"); 8796 message.append("As an Icon"); 8797 message.append("</li>"); 8798 found = true; 8799 } 8800 String foundelsewhere = findBeanUsage(nb); 8801 8802 if (foundelsewhere != null) { 8803 message.append(foundelsewhere); 8804 found = true; 8805 } 8806 } 8807 8808 if (nb instanceof Sensor) { 8809 int count = 0; 8810 8811 for (SensorIcon si : sensorList) { 8812 if (nb.equals(si.getNamedBean())) { 8813 count++; 8814 found = true; 8815 } 8816 } 8817 8818 if (count > 0) { 8819 message.append("<li>"); 8820 message.append(String.format("As an Icon %s times", count)); 8821 message.append("</li>"); 8822 } 8823 String foundelsewhere = findBeanUsage(nb); 8824 8825 if (foundelsewhere != null) { 8826 message.append(foundelsewhere); 8827 found = true; 8828 } 8829 } 8830 8831 if (nb instanceof Memory) { 8832 for (MemoryIcon si : memoryLabelList) { 8833 if (nb.equals(si.getMemory())) { 8834 found = true; 8835 message.append("<li>"); 8836 message.append(Bundle.getMessage("VetoMemoryIconFound")); 8837 message.append("</li>"); 8838 } 8839 } 8840 } 8841 8842 if (nb instanceof GlobalVariable) { 8843 for (GlobalVariableIcon si : globalVariableLabelList) { 8844 if (nb.equals(si.getGlobalVariable())) { 8845 found = true; 8846 message.append("<li>"); 8847 message.append(Bundle.getMessage("VetoGlobalVariableIconFound")); 8848 message.append("</li>"); 8849 } 8850 } 8851 } 8852 8853 if (found) { 8854 message.append("</ul>"); 8855 message.append(Bundle.getMessage("VetoReferencesWillBeRemoved")); // NOI18N 8856 throw new PropertyVetoException(message.toString(), evt); 8857 } 8858 } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N 8859 if (nb instanceof SignalHead) { 8860 removeSignalHead((SignalHead) nb); 8861 removeBeanRefs(nb); 8862 } 8863 8864 if (nb instanceof Turnout) { 8865 LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb); 8866 8867 if (lt != null) { 8868 lt.setTurnout(""); 8869 } 8870 8871 for (LayoutTurnout t : getLayoutTurnouts()) { 8872 if (t.getLinkedTurnoutName() != null) { 8873 if (t.getLinkedTurnoutName().equals(nb.getSystemName()) 8874 || ((nb.getUserName() != null) && t.getLinkedTurnoutName().equals(nb.getUserName()))) { 8875 t.setLinkedTurnoutName(""); 8876 } 8877 } 8878 8879 if (nb.equals(t.getSecondTurnout())) { 8880 t.setSecondTurnout(""); 8881 } 8882 } 8883 8884 for (LayoutSlip sl : getLayoutSlips()) { 8885 if (nb.equals(sl.getTurnout())) { 8886 sl.setTurnout(""); 8887 } 8888 8889 if (nb.equals(sl.getTurnoutB())) { 8890 sl.setTurnoutB(""); 8891 } 8892 } 8893 8894 for (LayoutTurntable lx : getLayoutTurntables()) { 8895 if (lx.isTurnoutControlled()) { 8896 for (int i = 0; i < lx.getNumberRays(); i++) { 8897 if (nb.equals(lx.getRayTurnout(i))) { 8898 lx.setRayTurnout(i, null, NamedBean.UNKNOWN); 8899 } 8900 } 8901 } 8902 } 8903 } 8904 8905 if (nb instanceof SignalMast) { 8906 removeBeanRefs(nb); 8907 8908 if (containsSignalMast((SignalMast) nb)) { 8909 Iterator<SignalMastIcon> icon = signalMastList.iterator(); 8910 8911 while (icon.hasNext()) { 8912 SignalMastIcon i = icon.next(); 8913 8914 if (i.getSignalMast().equals(nb)) { 8915 icon.remove(); 8916 super.removeFromContents(i); 8917 } 8918 } 8919 setDirty(); 8920 redrawPanel(); 8921 } 8922 } 8923 8924 if (nb instanceof Sensor) { 8925 removeBeanRefs(nb); 8926 Iterator<SensorIcon> icon = sensorImage.iterator(); 8927 8928 while (icon.hasNext()) { 8929 SensorIcon i = icon.next(); 8930 8931 if (nb.equals(i.getSensor())) { 8932 icon.remove(); 8933 super.removeFromContents(i); 8934 } 8935 } 8936 setDirty(); 8937 redrawPanel(); 8938 } 8939 8940 if (nb instanceof Memory) { 8941 Iterator<MemoryIcon> icon = memoryLabelList.iterator(); 8942 8943 while (icon.hasNext()) { 8944 MemoryIcon i = icon.next(); 8945 8946 if (nb.equals(i.getMemory())) { 8947 icon.remove(); 8948 super.removeFromContents(i); 8949 } 8950 } 8951 } 8952 8953 if (nb instanceof GlobalVariable) { 8954 Iterator<GlobalVariableIcon> icon = globalVariableLabelList.iterator(); 8955 8956 while (icon.hasNext()) { 8957 GlobalVariableIcon i = icon.next(); 8958 8959 if (nb.equals(i.getGlobalVariable())) { 8960 icon.remove(); 8961 super.removeFromContents(i); 8962 } 8963 } 8964 } 8965 } 8966 } 8967 8968 @Override 8969 public void dispose() { 8970 if (leToolBarPanel != null) { 8971 leToolBarPanel.dispose(); 8972 } 8973 super.dispose(); 8974 8975 } 8976 8977 // package protected 8978 class TurnoutComboBoxPopupMenuListener implements PopupMenuListener { 8979 8980 private final NamedBeanComboBox<Turnout> comboBox; 8981 private final List<Turnout> currentTurnouts; 8982 8983 public TurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) { 8984 this.comboBox = comboBox; 8985 this.currentTurnouts = currentTurnouts; 8986 } 8987 8988 @Override 8989 public void popupMenuWillBecomeVisible(PopupMenuEvent event) { 8990 // This method is called before the popup menu becomes visible. 8991 log.debug("PopupMenuWillBecomeVisible"); 8992 Set<Turnout> l = new HashSet<>(); 8993 comboBox.getManager().getNamedBeanSet().forEach((turnout) -> { 8994 if (!currentTurnouts.contains(turnout)) { 8995 if (!validatePhysicalTurnout(turnout.getDisplayName(), null)) { 8996 l.add(turnout); 8997 } 8998 } 8999 }); 9000 comboBox.setExcludedItems(l); 9001 } 9002 9003 @Override 9004 public void popupMenuWillBecomeInvisible(PopupMenuEvent event) { 9005 // This method is called before the popup menu becomes invisible 9006 log.debug("PopupMenuWillBecomeInvisible"); 9007 } 9008 9009 @Override 9010 public void popupMenuCanceled(PopupMenuEvent event) { 9011 // This method is called when the popup menu is canceled 9012 log.debug("PopupMenuCanceled"); 9013 } 9014 } 9015 9016 /** 9017 * Create a listener that will exclude turnouts that are present in the 9018 * current panel. 9019 * 9020 * @param comboBox The NamedBeanComboBox that contains the turnout list. 9021 * @return A PopupMenuListener 9022 */ 9023 public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox) { 9024 return new TurnoutComboBoxPopupMenuListener(comboBox, new ArrayList<>()); 9025 } 9026 9027 /** 9028 * Create a listener that will exclude turnouts that are present in the 9029 * current panel. The list of current turnouts are not excluded. 9030 * 9031 * @param comboBox The NamedBeanComboBox that contains the turnout 9032 * list. 9033 * @param currentTurnouts The turnouts to be left in the turnout list. 9034 * @return A PopupMenuListener 9035 */ 9036 public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) { 9037 return new TurnoutComboBoxPopupMenuListener(comboBox, currentTurnouts); 9038 } 9039 9040 List<NamedBeanUsageReport> usageReport; 9041 9042 @Override 9043 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 9044 usageReport = new ArrayList<>(); 9045 if (bean != null) { 9046 usageReport = super.getUsageReport(bean); 9047 9048 // LE Specific checks 9049 // Turnouts 9050 findTurnoutUsage(bean); 9051 9052 // Check A, EB, EC for sensors, masts, heads 9053 findPositionalUsage(bean); 9054 9055 // Level Crossings 9056 findXingWhereUsed(bean); 9057 9058 // Track segments 9059 findSegmentWhereUsed(bean); 9060 } 9061 return usageReport; 9062 } 9063 9064 void findTurnoutUsage(NamedBean bean) { 9065 for (LayoutTurnout turnout : getLayoutTurnoutsAndSlips()) { 9066 String data = getUsageData(turnout); 9067 9068 if (bean.equals(turnout.getTurnout())) { 9069 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout", data)); 9070 } 9071 if (bean.equals(turnout.getSecondTurnout())) { 9072 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout2", data)); 9073 } 9074 9075 if (isLBLockUsed(bean, turnout.getLayoutBlock())) { 9076 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 9077 } 9078 if (turnout.hasEnteringDoubleTrack()) { 9079 if (isLBLockUsed(bean, turnout.getLayoutBlockB())) { 9080 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 9081 } 9082 if (isLBLockUsed(bean, turnout.getLayoutBlockC())) { 9083 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 9084 } 9085 if (isLBLockUsed(bean, turnout.getLayoutBlockD())) { 9086 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 9087 } 9088 } 9089 9090 if (bean.equals(turnout.getSensorA())) { 9091 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 9092 } 9093 if (bean.equals(turnout.getSensorB())) { 9094 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 9095 } 9096 if (bean.equals(turnout.getSensorC())) { 9097 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 9098 } 9099 if (bean.equals(turnout.getSensorD())) { 9100 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 9101 } 9102 9103 if (bean.equals(turnout.getSignalAMast())) { 9104 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9105 } 9106 if (bean.equals(turnout.getSignalBMast())) { 9107 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9108 } 9109 if (bean.equals(turnout.getSignalCMast())) { 9110 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9111 } 9112 if (bean.equals(turnout.getSignalDMast())) { 9113 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9114 } 9115 9116 if (bean.equals(turnout.getSignalA1())) { 9117 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9118 } 9119 if (bean.equals(turnout.getSignalA2())) { 9120 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9121 } 9122 if (bean.equals(turnout.getSignalA3())) { 9123 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9124 } 9125 if (bean.equals(turnout.getSignalB1())) { 9126 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9127 } 9128 if (bean.equals(turnout.getSignalB2())) { 9129 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9130 } 9131 if (bean.equals(turnout.getSignalC1())) { 9132 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9133 } 9134 if (bean.equals(turnout.getSignalC2())) { 9135 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9136 } 9137 if (bean.equals(turnout.getSignalD1())) { 9138 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9139 } 9140 if (bean.equals(turnout.getSignalD2())) { 9141 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9142 } 9143 } 9144 } 9145 9146 void findPositionalUsage(NamedBean bean) { 9147 for (PositionablePoint point : getPositionablePoints()) { 9148 String data = getUsageData(point); 9149 if (bean.equals(point.getEastBoundSensor())) { 9150 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data)); 9151 } 9152 if (bean.equals(point.getWestBoundSensor())) { 9153 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data)); 9154 } 9155 if (bean.equals(point.getEastBoundSignalHead())) { 9156 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data)); 9157 } 9158 if (bean.equals(point.getWestBoundSignalHead())) { 9159 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data)); 9160 } 9161 if (bean.equals(point.getEastBoundSignalMast())) { 9162 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data)); 9163 } 9164 if (bean.equals(point.getWestBoundSignalMast())) { 9165 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data)); 9166 } 9167 } 9168 } 9169 9170 void findSegmentWhereUsed(NamedBean bean) { 9171 for (TrackSegment segment : getTrackSegments()) { 9172 if (isLBLockUsed(bean, segment.getLayoutBlock())) { 9173 String data = getUsageData(segment); 9174 usageReport.add(new NamedBeanUsageReport("LayoutEditorSegmentBlock", data)); 9175 } 9176 } 9177 } 9178 9179 void findXingWhereUsed(NamedBean bean) { 9180 for (LevelXing xing : getLevelXings()) { 9181 String data = getUsageData(xing); 9182 if (isLBLockUsed(bean, xing.getLayoutBlockAC())) { 9183 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data)); 9184 } 9185 if (isLBLockUsed(bean, xing.getLayoutBlockBD())) { 9186 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data)); 9187 } 9188 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTA)) { 9189 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9190 } 9191 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTB)) { 9192 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9193 } 9194 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTC)) { 9195 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9196 } 9197 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTD)) { 9198 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9199 } 9200 } 9201 } 9202 9203 String getUsageData(LayoutTrack track) { 9204 LayoutTrackView trackView = getLayoutTrackView(track); 9205 Point2D point = trackView.getCoordsCenter(); 9206 if (trackView instanceof TrackSegmentView) { 9207 TrackSegmentView segmentView = (TrackSegmentView) trackView; 9208 point = new Point2D.Double(segmentView.getCentreSegX(), segmentView.getCentreSegY()); 9209 } 9210 return String.format("%s :: x=%d, y=%d", 9211 track.getClass().getSimpleName(), 9212 Math.round(point.getX()), 9213 Math.round(point.getY())); 9214 } 9215 9216 boolean isLBLockUsed(NamedBean bean, LayoutBlock lblock) { 9217 boolean result = false; 9218 if (lblock != null) { 9219 if (bean.equals(lblock.getBlock())) { 9220 result = true; 9221 } 9222 } 9223 return result; 9224 } 9225 9226 boolean isUsedInXing(NamedBean bean, LevelXing xing, LevelXing.Geometry point) { 9227 boolean result = false; 9228 if (bean.equals(xing.getSensor(point))) { 9229 result = true; 9230 } 9231 if (bean.equals(xing.getSignalHead(point))) { 9232 result = true; 9233 } 9234 if (bean.equals(xing.getSignalMast(point))) { 9235 result = true; 9236 } 9237 return result; 9238 } 9239 9240 // initialize logging 9241 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditor.class); 9242}