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