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