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