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