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