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