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