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