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