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 setEditIdMenu(p, popup); 3973 setEditClassesMenu(p, popup); 3974 popup.addSeparator(); 3975 setLogixNGPositionableMenu(p, popup); 3976 } 3977 } 3978 } else { 3979 p.showPopUp(popup); 3980 PositionablePopupUtil util = p.getPopupUtility(); 3981 3982 if (util != null) { 3983 util.setAdditionalViewPopUpMenu(popup); 3984 } 3985 } 3986 3987 addPopupItems(popup, event); 3988 3989 popup.show((Component) p, p.getWidth() / 2 + (int) ((getZoom() - 1.0) * p.getX()), 3990 p.getHeight() / 2 + (int) ((getZoom() - 1.0) * p.getY())); 3991 3992 /*popup.show((Component)pt, event.getX(), event.getY());*/ 3993 } 3994 3995 private long whenReleased = 0; // used to identify event that was popup trigger 3996 private boolean awaitingIconChange = false; 3997 3998 @Override 3999 public void mouseClicked(@Nonnull JmriMouseEvent event) { 4000 // initialize mouse position 4001 calcLocation(event); 4002 4003 // if alt modifier is down invert the snap to grid behaviour 4004 snapToGridInvert = event.isAltDown(); 4005 4006 if (!event.isMetaDown() && !event.isPopupTrigger() && !event.isAltDown() 4007 && !awaitingIconChange && !event.isShiftDown() && !event.isControlDown()) { 4008 List<Positionable> selections = getSelectedItems(event); 4009 4010 if (!selections.isEmpty()) { 4011 selections.get(0).doMouseClicked(event); 4012 } 4013 } else if (event.isPopupTrigger() && (whenReleased != event.getWhen())) { 4014 4015 if (isEditable()) { 4016 selectedObject = null; 4017 selectedHitPointType = HitPointType.NONE; 4018 showEditPopUps(event); 4019 } else { 4020 LocoIcon lo = checkMarkerPopUps(dLoc); 4021 4022 if (lo != null) { 4023 showPopUp(lo, event); 4024 } 4025 } 4026 } 4027 4028 if (event.isControlDown() && !event.isPopupTrigger()) { 4029 if (findLayoutTracksHitPoint(dLoc)) { 4030 switch (foundHitPointType) { 4031 case POS_POINT: 4032 case TURNOUT_CENTER: 4033 case LEVEL_XING_CENTER: 4034 case SLIP_LEFT: 4035 case SLIP_RIGHT: 4036 case TURNTABLE_CENTER: { 4037 amendSelectionGroup(foundTrack); 4038 break; 4039 } 4040 4041 default: { 4042 break; 4043 } 4044 } 4045 } else { 4046 PositionableLabel s = checkSensorIconPopUps(dLoc); 4047 if (s != null) { 4048 amendSelectionGroup(s); 4049 } else { 4050 PositionableLabel sh = checkSignalHeadIconPopUps(dLoc); 4051 if (sh != null) { 4052 amendSelectionGroup(sh); 4053 } else { 4054 PositionableLabel ms = checkMultiSensorPopUps(dLoc); 4055 if (ms != null) { 4056 amendSelectionGroup(ms); 4057 } else { 4058 PositionableLabel lb = checkLabelImagePopUps(dLoc); 4059 if (lb != null) { 4060 amendSelectionGroup(lb); 4061 } else { 4062 PositionableLabel b = checkBackgroundPopUps(dLoc); 4063 if (b != null) { 4064 amendSelectionGroup(b); 4065 } else { 4066 PositionableLabel sm = checkSignalMastIconPopUps(dLoc); 4067 if (sm != null) { 4068 amendSelectionGroup(sm); 4069 } else { 4070 LayoutShape ls = checkLayoutShapePopUps(dLoc); 4071 if (ls != null) { 4072 amendSelectionGroup(ls); 4073 } 4074 } 4075 } 4076 } 4077 } 4078 } 4079 } 4080 } 4081 } else if ((selectionWidth == 0) || (selectionHeight == 0)) { 4082 clearSelectionGroups(); 4083 } 4084 requestFocusInWindow(); 4085 } 4086 4087 private void checkPointOfPositionable(@Nonnull PositionablePoint p) { 4088 assert p != null; 4089 4090 TrackSegment t = p.getConnect1(); 4091 4092 if (t == null) { 4093 t = p.getConnect2(); 4094 } 4095 4096 // Nothing connected to this bit of track so ignore 4097 if (t == null) { 4098 return; 4099 } 4100 beginTrack = p; 4101 beginHitPointType = HitPointType.POS_POINT; 4102 PositionablePointView pv = getPositionablePointView(p); 4103 Point2D loc = pv.getCoordsCenter(); 4104 4105 if (findLayoutTracksHitPoint(loc, true, p)) { 4106 switch (foundHitPointType) { 4107 case POS_POINT: { 4108 PositionablePoint p2 = (PositionablePoint) foundTrack; 4109 4110 if ((p2.getType() == PositionablePoint.PointType.ANCHOR) && p2.setTrackConnection(t)) { 4111 if (t.getConnect1() == p) { 4112 t.setNewConnect1(p2, foundHitPointType); 4113 } else { 4114 t.setNewConnect2(p2, foundHitPointType); 4115 } 4116 p.removeTrackConnection(t); 4117 4118 if ((p.getConnect1() == null) && (p.getConnect2() == null)) { 4119 removePositionablePoint(p); 4120 } 4121 } 4122 break; 4123 } 4124 case TURNOUT_A: 4125 case TURNOUT_B: 4126 case TURNOUT_C: 4127 case TURNOUT_D: 4128 case SLIP_A: 4129 case SLIP_B: 4130 case SLIP_C: 4131 case SLIP_D: 4132 case LEVEL_XING_A: 4133 case LEVEL_XING_B: 4134 case LEVEL_XING_C: 4135 case LEVEL_XING_D: { 4136 try { 4137 if (foundTrack.getConnection(foundHitPointType) == null) { 4138 foundTrack.setConnection(foundHitPointType, t, HitPointType.TRACK); 4139 4140 if (t.getConnect1() == p) { 4141 t.setNewConnect1(foundTrack, foundHitPointType); 4142 } else { 4143 t.setNewConnect2(foundTrack, foundHitPointType); 4144 } 4145 p.removeTrackConnection(t); 4146 4147 if ((p.getConnect1() == null) && (p.getConnect2() == null)) { 4148 removePositionablePoint(p); 4149 } 4150 } 4151 } catch (JmriException e) { 4152 log.debug("Unable to set location"); 4153 } 4154 break; 4155 } 4156 4157 default: { 4158 if (HitPointType.isTurntableRayHitType(foundHitPointType)) { 4159 LayoutTurntable tt = (LayoutTurntable) foundTrack; 4160 int ray = foundHitPointType.turntableTrackIndex(); 4161 4162 if (tt.getRayConnectIndexed(ray) == null) { 4163 tt.setRayConnect(t, ray); 4164 4165 if (t.getConnect1() == p) { 4166 t.setNewConnect1(tt, foundHitPointType); 4167 } else { 4168 t.setNewConnect2(tt, foundHitPointType); 4169 } 4170 p.removeTrackConnection(t); 4171 4172 if ((p.getConnect1() == null) && (p.getConnect2() == null)) { 4173 removePositionablePoint(p); 4174 } 4175 } 4176 } else { 4177 log.debug("No valid point, so will quit"); 4178 return; 4179 } 4180 break; 4181 } 4182 } 4183 redrawPanel(); 4184 4185 if (t.getLayoutBlock() != null) { 4186 getLEAuxTools().setBlockConnectivityChanged(); 4187 } 4188 } 4189 beginTrack = null; 4190 } 4191 4192 // We just dropped a turnout... see if it will connect to anything 4193 private void hitPointCheckLayoutTurnouts(@Nonnull LayoutTurnout lt) { 4194 beginTrack = lt; 4195 4196 LayoutTurnoutView ltv = getLayoutTurnoutView(lt); 4197 4198 if (lt.getConnectA() == null) { 4199 if (lt instanceof LayoutSlip) { 4200 beginHitPointType = HitPointType.SLIP_A; 4201 } else { 4202 beginHitPointType = HitPointType.TURNOUT_A; 4203 } 4204 dLoc = ltv.getCoordsA(); 4205 hitPointCheckLayoutTurnoutSubs(dLoc); 4206 } 4207 4208 if (lt.getConnectB() == null) { 4209 if (lt instanceof LayoutSlip) { 4210 beginHitPointType = HitPointType.SLIP_B; 4211 } else { 4212 beginHitPointType = HitPointType.TURNOUT_B; 4213 } 4214 dLoc = ltv.getCoordsB(); 4215 hitPointCheckLayoutTurnoutSubs(dLoc); 4216 } 4217 4218 if (lt.getConnectC() == null) { 4219 if (lt instanceof LayoutSlip) { 4220 beginHitPointType = HitPointType.SLIP_C; 4221 } else { 4222 beginHitPointType = HitPointType.TURNOUT_C; 4223 } 4224 dLoc = ltv.getCoordsC(); 4225 hitPointCheckLayoutTurnoutSubs(dLoc); 4226 } 4227 4228 if ((lt.getConnectD() == null) && (lt.isTurnoutTypeXover() || lt.isTurnoutTypeSlip())) { 4229 if (lt instanceof LayoutSlip) { 4230 beginHitPointType = HitPointType.SLIP_D; 4231 } else { 4232 beginHitPointType = HitPointType.TURNOUT_D; 4233 } 4234 dLoc = ltv.getCoordsD(); 4235 hitPointCheckLayoutTurnoutSubs(dLoc); 4236 } 4237 beginTrack = null; 4238 foundTrack = null; 4239 foundTrackView = null; 4240 } 4241 4242 private void hitPointCheckLayoutTurnoutSubs(@Nonnull Point2D dLoc) { 4243 assert dLoc != null; 4244 4245 if (findLayoutTracksHitPoint(dLoc, true)) { 4246 switch (foundHitPointType) { 4247 case POS_POINT: { 4248 PositionablePoint p2 = (PositionablePoint) foundTrack; 4249 4250 if (((p2.getConnect1() == null) && (p2.getConnect2() != null)) 4251 || ((p2.getConnect1() != null) && (p2.getConnect2() == null))) { 4252 TrackSegment t = p2.getConnect1(); 4253 4254 if (t == null) { 4255 t = p2.getConnect2(); 4256 } 4257 4258 if (t == null) { 4259 return; 4260 } 4261 LayoutTurnout lt = (LayoutTurnout) beginTrack; 4262 try { 4263 if (lt.getConnection(beginHitPointType) == null) { 4264 lt.setConnection(beginHitPointType, t, HitPointType.TRACK); 4265 p2.removeTrackConnection(t); 4266 4267 if (t.getConnect1() == p2) { 4268 t.setNewConnect1(lt, beginHitPointType); 4269 } else { 4270 t.setNewConnect2(lt, beginHitPointType); 4271 } 4272 removePositionablePoint(p2); 4273 } 4274 4275 if (t.getLayoutBlock() != null) { 4276 getLEAuxTools().setBlockConnectivityChanged(); 4277 } 4278 } catch (JmriException e) { 4279 log.debug("Unable to set location"); 4280 } 4281 } 4282 break; 4283 } 4284 4285 case TURNOUT_A: 4286 case TURNOUT_B: 4287 case TURNOUT_C: 4288 case TURNOUT_D: 4289 case SLIP_A: 4290 case SLIP_B: 4291 case SLIP_C: 4292 case SLIP_D: { 4293 LayoutTurnout ft = (LayoutTurnout) foundTrack; 4294 addTrackSegment(); 4295 4296 if ((ft.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT) || (ft.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT)) { 4297 rotateTurnout(ft); 4298 } 4299 4300 // Assign a block to the new zero length track segment. 4301 ((LayoutTurnoutView) foundTrackView).setTrackSegmentBlock(foundHitPointType, true); 4302 break; 4303 } 4304 4305 default: { 4306 log.warn("Unexpected foundPointType {} in hitPointCheckLayoutTurnoutSubs", foundHitPointType); 4307 break; 4308 } 4309 } 4310 } 4311 } 4312 4313 private void rotateTurnout(@Nonnull LayoutTurnout t) { 4314 assert t != null; 4315 4316 LayoutTurnoutView tv = getLayoutTurnoutView(t); 4317 4318 LayoutTurnout be = (LayoutTurnout) beginTrack; 4319 LayoutTurnoutView bev = getLayoutTurnoutView(be); 4320 4321 if (((beginHitPointType == HitPointType.TURNOUT_A) && ((be.getConnectB() != null) || (be.getConnectC() != null))) 4322 || ((beginHitPointType == HitPointType.TURNOUT_B) && ((be.getConnectA() != null) || (be.getConnectC() != null))) 4323 || ((beginHitPointType == HitPointType.TURNOUT_C) && ((be.getConnectB() != null) || (be.getConnectA() != null)))) { 4324 return; 4325 } 4326 4327 if ((be.getTurnoutType() != LayoutTurnout.TurnoutType.RH_TURNOUT) && (be.getTurnoutType() != LayoutTurnout.TurnoutType.LH_TURNOUT)) { 4328 return; 4329 } 4330 4331 Point2D c, diverg, xy2; 4332 4333 if ((foundHitPointType == HitPointType.TURNOUT_C) && (beginHitPointType == HitPointType.TURNOUT_C)) { 4334 c = tv.getCoordsA(); 4335 diverg = tv.getCoordsB(); 4336 xy2 = MathUtil.subtract(c, diverg); 4337 } else if ((foundHitPointType == HitPointType.TURNOUT_C) 4338 && ((beginHitPointType == HitPointType.TURNOUT_A) || (beginHitPointType == HitPointType.TURNOUT_B))) { 4339 4340 c = tv.getCoordsCenter(); 4341 diverg = tv.getCoordsC(); 4342 4343 if (beginHitPointType == HitPointType.TURNOUT_A) { 4344 xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA()); 4345 } else { 4346 xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB()); 4347 } 4348 } else if (foundHitPointType == HitPointType.TURNOUT_B) { 4349 c = tv.getCoordsA(); 4350 diverg = tv.getCoordsB(); 4351 4352 switch (beginHitPointType) { 4353 case TURNOUT_B: 4354 xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB()); 4355 break; 4356 case TURNOUT_A: 4357 xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA()); 4358 break; 4359 case TURNOUT_C: 4360 default: 4361 xy2 = MathUtil.subtract(bev.getCoordsCenter(), bev.getCoordsC()); 4362 break; 4363 } 4364 } else if (foundHitPointType == HitPointType.TURNOUT_A) { 4365 c = tv.getCoordsA(); 4366 diverg = tv.getCoordsB(); 4367 4368 switch (beginHitPointType) { 4369 case TURNOUT_A: 4370 xy2 = MathUtil.subtract(bev.getCoordsA(), bev.getCoordsB()); 4371 break; 4372 case TURNOUT_B: 4373 xy2 = MathUtil.subtract(bev.getCoordsB(), bev.getCoordsA()); 4374 break; 4375 case TURNOUT_C: 4376 default: 4377 xy2 = MathUtil.subtract(bev.getCoordsC(), bev.getCoordsCenter()); 4378 break; 4379 } 4380 } else { 4381 return; 4382 } 4383 Point2D xy = MathUtil.subtract(diverg, c); 4384 double radius = Math.toDegrees(Math.atan2(xy.getY(), xy.getX())); 4385 double eRadius = Math.toDegrees(Math.atan2(xy2.getY(), xy2.getX())); 4386 bev.rotateCoords(radius - eRadius); 4387 4388 Point2D conCord = bev.getCoordsA(); 4389 Point2D tCord = tv.getCoordsC(); 4390 4391 if (foundHitPointType == HitPointType.TURNOUT_B) { 4392 tCord = tv.getCoordsB(); 4393 } 4394 4395 if (foundHitPointType == HitPointType.TURNOUT_A) { 4396 tCord = tv.getCoordsA(); 4397 } 4398 4399 switch (beginHitPointType) { 4400 case TURNOUT_A: 4401 conCord = bev.getCoordsA(); 4402 break; 4403 case TURNOUT_B: 4404 conCord = bev.getCoordsB(); 4405 break; 4406 case TURNOUT_C: 4407 conCord = bev.getCoordsC(); 4408 break; 4409 default: 4410 break; 4411 } 4412 xy = MathUtil.subtract(conCord, tCord); 4413 Point2D offset = MathUtil.subtract(bev.getCoordsCenter(), xy); 4414 bev.setCoordsCenter(offset); 4415 } 4416 4417 public List<Positionable> _positionableSelection = new ArrayList<>(); 4418 public List<LayoutTrack> _layoutTrackSelection = new ArrayList<>(); 4419 public List<LayoutShape> _layoutShapeSelection = new ArrayList<>(); 4420 4421 @Nonnull 4422 public List<Positionable> getPositionalSelection() { 4423 return _positionableSelection; 4424 } 4425 4426 @Nonnull 4427 public List<LayoutTrack> getLayoutTrackSelection() { 4428 return _layoutTrackSelection; 4429 } 4430 4431 @Nonnull 4432 public List<LayoutShape> getLayoutShapeSelection() { 4433 return _layoutShapeSelection; 4434 } 4435 4436 private void createSelectionGroups() { 4437 Rectangle2D selectionRect = getSelectionRect(); 4438 4439 getContents().forEach((o) -> { 4440 if (selectionRect.contains(o.getLocation())) { 4441 4442 log.trace("found item o of class {}", o.getClass()); 4443 if (!_positionableSelection.contains(o)) { 4444 _positionableSelection.add(o); 4445 } 4446 } 4447 }); 4448 4449 getLayoutTracks().forEach((lt) -> { 4450 LayoutTrackView ltv = getLayoutTrackView(lt); 4451 Point2D center = ltv.getCoordsCenter(); 4452 if (selectionRect.contains(center)) { 4453 if (!_layoutTrackSelection.contains(lt)) { 4454 _layoutTrackSelection.add(lt); 4455 } 4456 } 4457 }); 4458 assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty()); 4459 4460 layoutShapes.forEach((ls) -> { 4461 if (selectionRect.intersects(ls.getBounds())) { 4462 if (!_layoutShapeSelection.contains(ls)) { 4463 _layoutShapeSelection.add(ls); 4464 } 4465 } 4466 }); 4467 redrawPanel(); 4468 } 4469 4470 public void clearSelectionGroups() { 4471 selectionActive = false; 4472 _positionableSelection.clear(); 4473 _layoutTrackSelection.clear(); 4474 assignBlockToSelectionMenuItem.setEnabled(false); 4475 _layoutShapeSelection.clear(); 4476 } 4477 4478 private boolean noWarnGlobalDelete = false; 4479 4480 private void deleteSelectedItems() { 4481 if (!noWarnGlobalDelete) { 4482 int selectedValue = JmriJOptionPane.showOptionDialog(this, 4483 Bundle.getMessage("Question6"), Bundle.getMessage("WarningTitle"), 4484 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 4485 new Object[]{Bundle.getMessage("ButtonYes"), 4486 Bundle.getMessage("ButtonNo"), 4487 Bundle.getMessage("ButtonYesPlus")}, 4488 Bundle.getMessage("ButtonNo")); 4489 4490 // array position 1, ButtonNo or Dialog closed. 4491 if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) { 4492 return; // return without creating if "No" response 4493 } 4494 4495 if (selectedValue == 2) { // array positio 2, ButtonYesPlus 4496 // Suppress future warnings, and continue 4497 noWarnGlobalDelete = true; 4498 } 4499 } 4500 4501 _positionableSelection.forEach(this::remove); 4502 4503 _layoutTrackSelection.forEach((lt) -> { 4504 if (lt instanceof PositionablePoint) { 4505 boolean oldWarning = noWarnPositionablePoint; 4506 noWarnPositionablePoint = true; 4507 removePositionablePoint((PositionablePoint) lt); 4508 noWarnPositionablePoint = oldWarning; 4509 } else if (lt instanceof LevelXing) { 4510 boolean oldWarning = noWarnLevelXing; 4511 noWarnLevelXing = true; 4512 removeLevelXing((LevelXing) lt); 4513 noWarnLevelXing = oldWarning; 4514 } else if (lt instanceof LayoutSlip) { 4515 boolean oldWarning = noWarnSlip; 4516 noWarnSlip = true; 4517 removeLayoutSlip((LayoutSlip) lt); 4518 noWarnSlip = oldWarning; 4519 } else if (lt instanceof LayoutTurntable) { 4520 boolean oldWarning = noWarnTurntable; 4521 noWarnTurntable = true; 4522 removeTurntable((LayoutTurntable) lt); 4523 noWarnTurntable = oldWarning; 4524 } else if (lt instanceof LayoutTurnout) { //<== this includes LayoutSlips 4525 boolean oldWarning = noWarnLayoutTurnout; 4526 noWarnLayoutTurnout = true; 4527 removeLayoutTurnout((LayoutTurnout) lt); 4528 noWarnLayoutTurnout = oldWarning; 4529 } 4530 }); 4531 4532 layoutShapes.removeAll(_layoutShapeSelection); 4533 4534 clearSelectionGroups(); 4535 redrawPanel(); 4536 } 4537 4538 private void amendSelectionGroup(@Nonnull Positionable p) { 4539 assert p != null; 4540 4541 if (_positionableSelection.contains(p)) { 4542 _positionableSelection.remove(p); 4543 } else { 4544 _positionableSelection.add(p); 4545 } 4546 redrawPanel(); 4547 } 4548 4549 public void amendSelectionGroup(@Nonnull LayoutTrack p) { 4550 assert p != null; 4551 4552 if (_layoutTrackSelection.contains(p)) { 4553 _layoutTrackSelection.remove(p); 4554 } else { 4555 _layoutTrackSelection.add(p); 4556 } 4557 assignBlockToSelectionMenuItem.setEnabled(!_layoutTrackSelection.isEmpty()); 4558 redrawPanel(); 4559 } 4560 4561 public void amendSelectionGroup(@Nonnull LayoutShape ls) { 4562 assert ls != null; 4563 4564 if (_layoutShapeSelection.contains(ls)) { 4565 _layoutShapeSelection.remove(ls); 4566 } else { 4567 _layoutShapeSelection.add(ls); 4568 } 4569 redrawPanel(); 4570 } 4571 4572 public void alignSelection(boolean alignX) { 4573 Point2D minPoint = MathUtil.infinityPoint2D; 4574 Point2D maxPoint = MathUtil.zeroPoint2D; 4575 Point2D sumPoint = MathUtil.zeroPoint2D; 4576 int cnt = 0; 4577 4578 for (Positionable comp : _positionableSelection) { 4579 if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) { 4580 continue; // skip non-positionables 4581 } 4582 Point2D p = MathUtil.pointToPoint2D(comp.getLocation()); 4583 minPoint = MathUtil.min(minPoint, p); 4584 maxPoint = MathUtil.max(maxPoint, p); 4585 sumPoint = MathUtil.add(sumPoint, p); 4586 cnt++; 4587 } 4588 4589 for (LayoutTrack lt : _layoutTrackSelection) { 4590 LayoutTrackView ltv = getLayoutTrackView(lt); 4591 Point2D p = ltv.getCoordsCenter(); 4592 minPoint = MathUtil.min(minPoint, p); 4593 maxPoint = MathUtil.max(maxPoint, p); 4594 sumPoint = MathUtil.add(sumPoint, p); 4595 cnt++; 4596 } 4597 4598 for (LayoutShape ls : _layoutShapeSelection) { 4599 Point2D p = ls.getCoordsCenter(); 4600 minPoint = MathUtil.min(minPoint, p); 4601 maxPoint = MathUtil.max(maxPoint, p); 4602 sumPoint = MathUtil.add(sumPoint, p); 4603 cnt++; 4604 } 4605 4606 Point2D avePoint = MathUtil.divide(sumPoint, cnt); 4607 int aveX = (int) avePoint.getX(); 4608 int aveY = (int) avePoint.getY(); 4609 4610 for (Positionable comp : _positionableSelection) { 4611 if (!getFlag(Editor.OPTION_POSITION, comp.isPositionable())) { 4612 continue; // skip non-positionables 4613 } 4614 4615 if (alignX) { 4616 comp.setLocation(aveX, comp.getY()); 4617 } else { 4618 comp.setLocation(comp.getX(), aveY); 4619 } 4620 } 4621 4622 _layoutTrackSelection.forEach((lt) -> { 4623 LayoutTrackView ltv = getLayoutTrackView(lt); 4624 if (alignX) { 4625 ltv.setCoordsCenter(new Point2D.Double(aveX, ltv.getCoordsCenter().getY())); 4626 } else { 4627 ltv.setCoordsCenter(new Point2D.Double(ltv.getCoordsCenter().getX(), aveY)); 4628 } 4629 }); 4630 4631 _layoutShapeSelection.forEach((ls) -> { 4632 if (alignX) { 4633 ls.setCoordsCenter(new Point2D.Double(aveX, ls.getCoordsCenter().getY())); 4634 } else { 4635 ls.setCoordsCenter(new Point2D.Double(ls.getCoordsCenter().getX(), aveY)); 4636 } 4637 }); 4638 4639 redrawPanel(); 4640 } 4641 4642 private boolean showAlignPopup() { 4643 return ((!_positionableSelection.isEmpty()) 4644 || (!_layoutTrackSelection.isEmpty()) 4645 || (!_layoutShapeSelection.isEmpty())); 4646 } 4647 4648 /** 4649 * Offer actions to align the selected Positionable items either 4650 * Horizontally (at average y coord) or Vertically (at average x coord). 4651 * 4652 * @param popup the JPopupMenu to add alignment menu to 4653 * @return true if alignment menu added 4654 */ 4655 public boolean setShowAlignmentMenu(@Nonnull JPopupMenu popup) { 4656 if (showAlignPopup()) { 4657 JMenu edit = new JMenu(Bundle.getMessage("EditAlignment")); 4658 edit.add(new AbstractAction(Bundle.getMessage("AlignX")) { 4659 @Override 4660 public void actionPerformed(ActionEvent event) { 4661 alignSelection(true); 4662 } 4663 }); 4664 edit.add(new AbstractAction(Bundle.getMessage("AlignY")) { 4665 @Override 4666 public void actionPerformed(ActionEvent event) { 4667 alignSelection(false); 4668 } 4669 }); 4670 popup.add(edit); 4671 4672 return true; 4673 } 4674 return false; 4675 } 4676 4677 @Override 4678 public void keyPressed(@Nonnull KeyEvent event) { 4679 if (event.getKeyCode() == KeyEvent.VK_DELETE) { 4680 deleteSelectedItems(); 4681 return; 4682 } 4683 4684 double deltaX = returnDeltaPositionX(event); 4685 double deltaY = returnDeltaPositionY(event); 4686 4687 if ((deltaX != 0) || (deltaY != 0)) { 4688 selectionX += deltaX; 4689 selectionY += deltaY; 4690 4691 Point2D delta = new Point2D.Double(deltaX, deltaY); 4692 _positionableSelection.forEach((c) -> { 4693 Point2D newPoint = c.getLocation(); 4694 if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) { 4695 MemoryIcon pm = (MemoryIcon) c; 4696 newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY()); 4697 } 4698 newPoint = MathUtil.add(newPoint, delta); 4699 newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint); 4700 c.setLocation(MathUtil.point2DToPoint(newPoint)); 4701 }); 4702 4703 _layoutTrackSelection.forEach((lt) -> { 4704 LayoutTrackView ltv = getLayoutTrackView(lt); 4705 Point2D newPoint = MathUtil.add(ltv.getCoordsCenter(), delta); 4706 newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint); 4707 getLayoutTrackView(lt).setCoordsCenter(newPoint); 4708 }); 4709 4710 _layoutShapeSelection.forEach((ls) -> { 4711 Point2D newPoint = MathUtil.add(ls.getCoordsCenter(), delta); 4712 newPoint = MathUtil.max(MathUtil.zeroPoint2D, newPoint); 4713 ls.setCoordsCenter(newPoint); 4714 }); 4715 redrawPanel(); 4716 return; 4717 } 4718 getLayoutEditorToolBarPanel().keyPressed(event); 4719 } 4720 4721 private double returnDeltaPositionX(@Nonnull KeyEvent event) { 4722 double result = 0.0; 4723 double amount = event.isShiftDown() ? 5.0 : 1.0; 4724 4725 switch (event.getKeyCode()) { 4726 case KeyEvent.VK_LEFT: { 4727 result = -amount; 4728 break; 4729 } 4730 4731 case KeyEvent.VK_RIGHT: { 4732 result = +amount; 4733 break; 4734 } 4735 4736 default: { 4737 break; 4738 } 4739 } 4740 return result; 4741 } 4742 4743 private double returnDeltaPositionY(@Nonnull KeyEvent event) { 4744 double result = 0.0; 4745 double amount = event.isShiftDown() ? 5.0 : 1.0; 4746 4747 switch (event.getKeyCode()) { 4748 case KeyEvent.VK_UP: { 4749 result = -amount; 4750 break; 4751 } 4752 4753 case KeyEvent.VK_DOWN: { 4754 result = +amount; 4755 break; 4756 } 4757 4758 default: { 4759 break; 4760 } 4761 } 4762 return result; 4763 } 4764 4765 int _prevNumSel = 0; 4766 4767 @Override 4768 public void mouseMoved(@Nonnull JmriMouseEvent event) { 4769 // initialize mouse position 4770 calcLocation(event); 4771 4772 // if alt modifier is down invert the snap to grid behaviour 4773 snapToGridInvert = event.isAltDown(); 4774 4775 if (isEditable()) { 4776 leToolBarPanel.setLocationText(dLoc); 4777 } 4778 List<Positionable> selections = getSelectedItems(event); 4779 Positionable selection = null; 4780 int numSel = selections.size(); 4781 4782 if (numSel > 0) { 4783 selection = selections.get(0); 4784 } 4785 4786 if ((selection != null) && (selection.getDisplayLevel() > Editor.BKG) && selection.showToolTip()) { 4787 showToolTip(selection, event); 4788 } else if (_targetPanel.getCursor() != Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)) { 4789 super.setToolTip(null); 4790 } 4791 4792 if (numSel != _prevNumSel) { 4793 redrawPanel(); 4794 _prevNumSel = numSel; 4795 } 4796 4797 if (findLayoutTracksHitPoint(dLoc)) { 4798 // log.debug("foundTrack: {}", foundTrack); 4799 if (HitPointType.isControlHitType(foundHitPointType)) { 4800 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 4801 setTurnoutTooltip(); 4802 } else { 4803 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 4804 } 4805 foundTrack = null; 4806 foundHitPointType = HitPointType.NONE; 4807 } else { 4808 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 4809 } 4810 } // mouseMoved 4811 4812 private void setTurnoutTooltip() { 4813 if (foundTrackView instanceof LayoutTurnoutView) { 4814 var ltv = (LayoutTurnoutView) foundTrackView; 4815 var lt = ltv.getLayoutTurnout(); 4816 if (lt.showToolTip()) { 4817 var tt = lt.getToolTip(); 4818 if (tt != null) { 4819 tt.setText(lt.getNameString()); 4820 var coords = ltv.getCoordsCenter(); 4821 int offsetY = (int) (getTurnoutCircleSize() * SIZE); 4822 tt.setLocation((int) coords.getX(), (int) coords.getY() + offsetY); 4823 setToolTip(tt); 4824 } 4825 } 4826 } 4827 } 4828 4829 public void setAllShowLayoutTurnoutToolTip(boolean state) { 4830 log.debug("setAllShowLayoutTurnoutToolTip: {}", state); 4831 for (LayoutTurnout lt : getLayoutTurnoutsAndSlips()) { 4832 lt.setShowToolTip(state); 4833 } 4834 } 4835 4836 private boolean isDragging = false; 4837 4838 @Override 4839 public void mouseDragged(@Nonnull JmriMouseEvent event) { 4840 // initialize mouse position 4841 calcLocation(event); 4842 4843 // ignore this event if still at the original point 4844 if ((!isDragging) && (xLoc == getAnchorX()) && (yLoc == getAnchorY())) { 4845 return; 4846 } 4847 4848 // if alt modifier is down invert the snap to grid behaviour 4849 snapToGridInvert = event.isAltDown(); 4850 4851 // process this mouse dragged event 4852 if (isEditable()) { 4853 leToolBarPanel.setLocationText(dLoc); 4854 } 4855 currentPoint = MathUtil.add(dLoc, startDelta); 4856 // don't allow negative placement, objects could become unreachable 4857 currentPoint = MathUtil.max(currentPoint, MathUtil.zeroPoint2D); 4858 4859 if ((selectedObject != null) && (event.isMetaDown() || event.isAltDown()) 4860 && (selectedHitPointType == HitPointType.MARKER)) { 4861 // marker moves regardless of editMode or positionable 4862 PositionableLabel pl = (PositionableLabel) selectedObject; 4863 pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY()); 4864 isDragging = true; 4865 redrawPanel(); 4866 return; 4867 } 4868 4869 if (isEditable()) { 4870 if ((selectedObject != null) && event.isMetaDown() && allPositionable()) { 4871 if (snapToGridOnMove != snapToGridInvert) { 4872 // this snaps currentPoint to the grid 4873 currentPoint = MathUtil.granulize(currentPoint, gContext.getGridSize()); 4874 xLoc = (int) currentPoint.getX(); 4875 yLoc = (int) currentPoint.getY(); 4876 leToolBarPanel.setLocationText(currentPoint); 4877 } 4878 4879 if ((!_positionableSelection.isEmpty()) 4880 || (!_layoutTrackSelection.isEmpty()) 4881 || (!_layoutShapeSelection.isEmpty())) { 4882 Point2D lastPoint = new Point2D.Double(_lastX, _lastY); 4883 Point2D offset = MathUtil.subtract(currentPoint, lastPoint); 4884 Point2D newPoint; 4885 4886 for (Positionable c : _positionableSelection) { 4887 if ((c instanceof MemoryIcon) && (c.getPopupUtility().getFixedWidth() == 0)) { 4888 MemoryIcon pm = (MemoryIcon) c; 4889 newPoint = new Point2D.Double(pm.getOriginalX(), pm.getOriginalY()); 4890 } else { 4891 newPoint = c.getLocation(); 4892 } 4893 newPoint = MathUtil.add(newPoint, offset); 4894 // don't allow negative placement, objects could become unreachable 4895 newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D); 4896 c.setLocation(MathUtil.point2DToPoint(newPoint)); 4897 } 4898 4899 for (LayoutTrack lt : _layoutTrackSelection) { 4900 LayoutTrackView ltv = getLayoutTrackView(lt); 4901 Point2D center = ltv.getCoordsCenter(); 4902 newPoint = MathUtil.add(center, offset); 4903 // don't allow negative placement, objects could become unreachable 4904 newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D); 4905 getLayoutTrackView(lt).setCoordsCenter(newPoint); 4906 } 4907 4908 for (LayoutShape ls : _layoutShapeSelection) { 4909 Point2D center = ls.getCoordsCenter(); 4910 newPoint = MathUtil.add(center, offset); 4911 // don't allow negative placement, objects could become unreachable 4912 newPoint = MathUtil.max(newPoint, MathUtil.zeroPoint2D); 4913 ls.setCoordsCenter(newPoint); 4914 } 4915 4916 _lastX = xLoc; 4917 _lastY = yLoc; 4918 } else { 4919 switch (selectedHitPointType) { 4920 case POS_POINT: 4921 case TURNOUT_CENTER: 4922 case LEVEL_XING_CENTER: 4923 case SLIP_LEFT: 4924 case SLIP_RIGHT: 4925 case TURNTABLE_CENTER: { 4926 getLayoutTrackView((LayoutTrack) selectedObject).setCoordsCenter(currentPoint); 4927 isDragging = true; 4928 break; 4929 } 4930 4931 case TURNOUT_A: { 4932 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsA(currentPoint); 4933 break; 4934 } 4935 4936 case TURNOUT_B: { 4937 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsB(currentPoint); 4938 break; 4939 } 4940 4941 case TURNOUT_C: { 4942 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsC(currentPoint); 4943 break; 4944 } 4945 4946 case TURNOUT_D: { 4947 getLayoutTurnoutView((LayoutTurnout) selectedObject).setCoordsD(currentPoint); 4948 break; 4949 } 4950 4951 case LEVEL_XING_A: { 4952 getLevelXingView((LevelXing) selectedObject).setCoordsA(currentPoint); 4953 break; 4954 } 4955 4956 case LEVEL_XING_B: { 4957 getLevelXingView((LevelXing) selectedObject).setCoordsB(currentPoint); 4958 break; 4959 } 4960 4961 case LEVEL_XING_C: { 4962 getLevelXingView((LevelXing) selectedObject).setCoordsC(currentPoint); 4963 break; 4964 } 4965 4966 case LEVEL_XING_D: { 4967 getLevelXingView((LevelXing) selectedObject).setCoordsD(currentPoint); 4968 break; 4969 } 4970 4971 case SLIP_A: { 4972 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsA(currentPoint); 4973 break; 4974 } 4975 4976 case SLIP_B: { 4977 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsB(currentPoint); 4978 break; 4979 } 4980 4981 case SLIP_C: { 4982 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsC(currentPoint); 4983 break; 4984 } 4985 4986 case SLIP_D: { 4987 getLayoutSlipView((LayoutSlip) selectedObject).setCoordsD(currentPoint); 4988 break; 4989 } 4990 4991 case LAYOUT_POS_LABEL: 4992 case MULTI_SENSOR: { 4993 PositionableLabel pl = (PositionableLabel) selectedObject; 4994 4995 if (pl.isPositionable()) { 4996 pl.setLocation((int) currentPoint.getX(), (int) currentPoint.getY()); 4997 isDragging = true; 4998 } 4999 break; 5000 } 5001 5002 case LAYOUT_POS_JCOMP: { 5003 PositionableJComponent c = (PositionableJComponent) selectedObject; 5004 5005 if (c.isPositionable()) { 5006 c.setLocation((int) currentPoint.getX(), (int) currentPoint.getY()); 5007 isDragging = true; 5008 } 5009 break; 5010 } 5011 5012 case TRACK_CIRCLE_CENTRE: { 5013 TrackSegmentView tv = getTrackSegmentView((TrackSegment) selectedObject); 5014 tv.reCalculateTrackSegmentAngle(currentPoint.getX(), currentPoint.getY()); 5015 break; 5016 } 5017 5018 default: { 5019 if (HitPointType.isBezierHitType(foundHitPointType)) { 5020 int index = selectedHitPointType.bezierPointIndex(); 5021 getTrackSegmentView((TrackSegment) selectedObject).setBezierControlPoint(currentPoint, index); 5022 } else if ((selectedHitPointType == HitPointType.SHAPE_CENTER)) { 5023 ((LayoutShape) selectedObject).setCoordsCenter(currentPoint); 5024 } else if (HitPointType.isShapePointOffsetHitPointType(selectedHitPointType)) { 5025 int index = selectedHitPointType.shapePointIndex(); 5026 ((LayoutShape) selectedObject).setPoint(index, currentPoint); 5027 } else if (HitPointType.isTurntableRayHitType(selectedHitPointType)) { 5028 LayoutTurntable turn = (LayoutTurntable) selectedObject; 5029 LayoutTurntableView turnView = getLayoutTurntableView(turn); 5030 turnView.setRayCoordsIndexed(currentPoint.getX(), currentPoint.getY(), 5031 selectedHitPointType.turntableTrackIndex()); 5032 } 5033 break; 5034 } 5035 } 5036 } 5037 } else if ((beginTrack != null) 5038 && event.isShiftDown() 5039 && leToolBarPanel.trackButton.isSelected()) { 5040 // dragging from first end of Track Segment 5041 currentLocation = new Point2D.Double(xLoc, yLoc); 5042 boolean needResetCursor = (foundTrack != null); 5043 5044 if (findLayoutTracksHitPoint(currentLocation, true)) { 5045 // have match to free connection point, change cursor 5046 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 5047 } else if (needResetCursor) { 5048 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 5049 } 5050 } else if (event.isShiftDown() 5051 && leToolBarPanel.shapeButton.isSelected() && (selectedObject != null)) { 5052 // dragging from end of shape 5053 currentLocation = new Point2D.Double(xLoc, yLoc); 5054 } else if (selectionActive && !event.isShiftDown() && !event.isMetaDown()) { 5055 selectionWidth = xLoc - selectionX; 5056 selectionHeight = yLoc - selectionY; 5057 } 5058 redrawPanel(); 5059 } else { 5060 Rectangle r = new Rectangle(event.getX(), event.getY(), 1, 1); 5061 ((JComponent) event.getSource()).scrollRectToVisible(r); 5062 } // if (isEditable()) 5063 } // mouseDragged 5064 5065 @Override 5066 public void mouseEntered(@Nonnull JmriMouseEvent event) { 5067 _targetPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 5068 } 5069 5070 /** 5071 * Add an Anchor point. 5072 */ 5073 public void addAnchor() { 5074 addAnchor(currentPoint); 5075 } 5076 5077 @Nonnull 5078 public PositionablePoint addAnchor(@Nonnull Point2D p) { 5079 assert p != null; 5080 5081 // get unique name 5082 String name = finder.uniqueName("A", ++numAnchors); 5083 5084 // create object 5085 PositionablePoint o = new PositionablePoint(name, 5086 PositionablePoint.PointType.ANCHOR, this); 5087 PositionablePointView pv = new PositionablePointView(o, p, this); 5088 addLayoutTrack(o, pv); 5089 5090 setDirty(); 5091 5092 return o; 5093 } 5094 5095 /** 5096 * Add an End Bumper point. 5097 */ 5098 public void addEndBumper() { 5099 // get unique name 5100 String name = finder.uniqueName("EB", ++numEndBumpers); 5101 5102 // create object 5103 PositionablePoint o = new PositionablePoint(name, 5104 PositionablePoint.PointType.END_BUMPER, this); 5105 PositionablePointView pv = new PositionablePointView(o, currentPoint, this); 5106 addLayoutTrack(o, pv); 5107 5108 setDirty(); 5109 } 5110 5111 /** 5112 * Add an Edge Connector point. 5113 */ 5114 public void addEdgeConnector() { 5115 // get unique name 5116 String name = finder.uniqueName("EC", ++numEdgeConnectors); 5117 5118 // create object 5119 PositionablePoint o = new PositionablePoint(name, 5120 PositionablePoint.PointType.EDGE_CONNECTOR, this); 5121 PositionablePointView pv = new PositionablePointView(o, currentPoint, this); 5122 addLayoutTrack(o, pv); 5123 5124 setDirty(); 5125 } 5126 5127 /** 5128 * Add a Track Segment 5129 */ 5130 public void addTrackSegment() { 5131 // get unique name 5132 String name = finder.uniqueName("T", ++numTrackSegments); 5133 5134 // create object 5135 newTrack = new TrackSegment(name, beginTrack, beginHitPointType, 5136 foundTrack, foundHitPointType, 5137 leToolBarPanel.mainlineTrack.isSelected(), this); 5138 5139 TrackSegmentView tsv = new TrackSegmentView( 5140 newTrack, 5141 this 5142 ); 5143 addLayoutTrack(newTrack, tsv); 5144 5145 setDirty(); 5146 5147 // link to connected objects 5148 setLink(beginTrack, beginHitPointType, newTrack, HitPointType.TRACK); 5149 setLink(foundTrack, foundHitPointType, newTrack, HitPointType.TRACK); 5150 5151 // check on layout block 5152 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5153 if (newName == null) { 5154 newName = ""; 5155 } 5156 LayoutBlock b = provideLayoutBlock(newName); 5157 5158 if (b != null) { 5159 newTrack.setLayoutBlock(b); 5160 getLEAuxTools().setBlockConnectivityChanged(); 5161 5162 // check on occupancy sensor 5163 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5164 if (sensorName == null) { 5165 sensorName = ""; 5166 } 5167 5168 if (!sensorName.isEmpty()) { 5169 if (!validateSensor(sensorName, b, this)) { 5170 b.setOccupancySensorName(""); 5171 } else { 5172 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5173 } 5174 } 5175 newTrack.updateBlockInfo(); 5176 } 5177 } 5178 5179 /** 5180 * Add a Level Crossing 5181 */ 5182 public void addLevelXing() { 5183 // get unique name 5184 String name = finder.uniqueName("X", ++numLevelXings); 5185 5186 // create object 5187 LevelXing o = new LevelXing(name, this); 5188 LevelXingView ov = new LevelXingView(o, currentPoint, this); 5189 addLayoutTrack(o, ov); 5190 5191 setDirty(); 5192 5193 // check on layout block 5194 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5195 if (newName == null) { 5196 newName = ""; 5197 } 5198 LayoutBlock b = provideLayoutBlock(newName); 5199 5200 if (b != null) { 5201 o.setLayoutBlockAC(b); 5202 o.setLayoutBlockBD(b); 5203 5204 // check on occupancy sensor 5205 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5206 if (sensorName == null) { 5207 sensorName = ""; 5208 } 5209 5210 if (!sensorName.isEmpty()) { 5211 if (!validateSensor(sensorName, b, this)) { 5212 b.setOccupancySensorName(""); 5213 } else { 5214 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5215 } 5216 } 5217 } 5218 } 5219 5220 /** 5221 * Add a LayoutSlip 5222 * 5223 * @param type the slip type 5224 */ 5225 public void addLayoutSlip(LayoutTurnout.TurnoutType type) { 5226 // get the rotation entry 5227 double rot = 0.0; 5228 String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim(); 5229 5230 if (s.isEmpty()) { 5231 rot = 0.0; 5232 } else { 5233 try { 5234 rot = Double.parseDouble(s); 5235 } catch (NumberFormatException e) { 5236 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " " 5237 + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5238 5239 return; 5240 } 5241 } 5242 5243 // get unique name 5244 String name = finder.uniqueName("SL", ++numLayoutSlips); 5245 5246 // create object 5247 LayoutSlip o; 5248 LayoutSlipView ov; 5249 5250 switch (type) { 5251 case DOUBLE_SLIP: 5252 LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this); 5253 o = lds; 5254 ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this); 5255 break; 5256 case SINGLE_SLIP: 5257 LayoutSingleSlip lss = new LayoutSingleSlip(name, this); 5258 o = lss; 5259 ov = new LayoutSingleSlipView(lss, currentPoint, rot, this); 5260 break; 5261 default: 5262 log.error("can't create slip {} with type {}", name, type); 5263 return; // without creating 5264 } 5265 5266 addLayoutTrack(o, ov); 5267 5268 setDirty(); 5269 5270 // check on layout block 5271 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5272 if (newName == null) { 5273 newName = ""; 5274 } 5275 LayoutBlock b = provideLayoutBlock(newName); 5276 5277 if (b != null) { 5278 ov.setLayoutBlock(b); 5279 5280 // check on occupancy sensor 5281 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5282 if (sensorName == null) { 5283 sensorName = ""; 5284 } 5285 5286 if (!sensorName.isEmpty()) { 5287 if (!validateSensor(sensorName, b, this)) { 5288 b.setOccupancySensorName(""); 5289 } else { 5290 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5291 } 5292 } 5293 } 5294 5295 String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName(); 5296 if (turnoutName == null) { 5297 turnoutName = ""; 5298 } 5299 5300 if (validatePhysicalTurnout(turnoutName, this)) { 5301 // turnout is valid and unique. 5302 o.setTurnout(turnoutName); 5303 5304 if (o.getTurnout().getSystemName().equals(turnoutName)) { 5305 leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout()); 5306 } 5307 } else { 5308 o.setTurnout(""); 5309 leToolBarPanel.turnoutNameComboBox.setSelectedItem(null); 5310 leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1); 5311 } 5312 turnoutName = leToolBarPanel.extraTurnoutNameComboBox.getSelectedItemDisplayName(); 5313 if (turnoutName == null) { 5314 turnoutName = ""; 5315 } 5316 5317 if (validatePhysicalTurnout(turnoutName, this)) { 5318 // turnout is valid and unique. 5319 o.setTurnoutB(turnoutName); 5320 5321 if (o.getTurnoutB().getSystemName().equals(turnoutName)) { 5322 leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(o.getTurnoutB()); 5323 } 5324 } else { 5325 o.setTurnoutB(""); 5326 leToolBarPanel.extraTurnoutNameComboBox.setSelectedItem(null); 5327 leToolBarPanel.extraTurnoutNameComboBox.setSelectedIndex(-1); 5328 } 5329 } 5330 5331 /** 5332 * Add a Layout Turnout 5333 * 5334 * @param type the turnout type 5335 */ 5336 public void addLayoutTurnout(LayoutTurnout.TurnoutType type) { 5337 // get the rotation entry 5338 double rot = 0.0; 5339 String s = leToolBarPanel.rotationComboBox.getEditor().getItem().toString().trim(); 5340 5341 if (s.isEmpty()) { 5342 rot = 0.0; 5343 } else { 5344 try { 5345 rot = Double.parseDouble(s); 5346 } catch (NumberFormatException e) { 5347 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error3") + " " 5348 + e, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5349 5350 return; 5351 } 5352 } 5353 5354 // get unique name 5355 String name = finder.uniqueName("TO", ++numLayoutTurnouts); 5356 5357 // create object - check all types, although not clear all actually reach here 5358 LayoutTurnout o; 5359 LayoutTurnoutView ov; 5360 5361 switch (type) { 5362 5363 case RH_TURNOUT: 5364 LayoutRHTurnout lrht = new LayoutRHTurnout(name, this); 5365 o = lrht; 5366 ov = new LayoutRHTurnoutView(lrht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5367 break; 5368 case LH_TURNOUT: 5369 LayoutLHTurnout llht = new LayoutLHTurnout(name, this); 5370 o = llht; 5371 ov = new LayoutLHTurnoutView(llht, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5372 break; 5373 case WYE_TURNOUT: 5374 LayoutWye lw = new LayoutWye(name, this); 5375 o = lw; 5376 ov = new LayoutWyeView(lw, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5377 break; 5378 case DOUBLE_XOVER: 5379 LayoutDoubleXOver ldx = new LayoutDoubleXOver(name, this); 5380 o = ldx; 5381 ov = new LayoutDoubleXOverView(ldx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5382 break; 5383 case RH_XOVER: 5384 LayoutRHXOver lrx = new LayoutRHXOver(name, this); 5385 o = lrx; 5386 ov = new LayoutRHXOverView(lrx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5387 break; 5388 case LH_XOVER: 5389 LayoutLHXOver llx = new LayoutLHXOver(name, this); 5390 o = llx; 5391 ov = new LayoutLHXOverView(llx, currentPoint, rot, gContext.getXScale(), gContext.getYScale(), this); 5392 break; 5393 5394 case DOUBLE_SLIP: 5395 LayoutDoubleSlip lds = new LayoutDoubleSlip(name, this); 5396 o = lds; 5397 ov = new LayoutDoubleSlipView(lds, currentPoint, rot, this); 5398 log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name); 5399 break; 5400 case SINGLE_SLIP: 5401 LayoutSingleSlip lss = new LayoutSingleSlip(name, this); 5402 o = lss; 5403 ov = new LayoutSingleSlipView(lss, currentPoint, rot, this); 5404 log.error("Found SINGLE_SLIP in addLayoutTurnout for element {}", name); 5405 break; 5406 5407 default: 5408 log.error("can't create LayoutTrack {} with type {}", name, type); 5409 return; // without creating 5410 } 5411 5412 addLayoutTrack(o, ov); 5413 5414 setDirty(); 5415 5416 // check on layout block 5417 String newName = leToolBarPanel.blockIDComboBox.getSelectedItemDisplayName(); 5418 if (newName == null) { 5419 newName = ""; 5420 } 5421 LayoutBlock b = provideLayoutBlock(newName); 5422 5423 if (b != null) { 5424 ov.setLayoutBlock(b); 5425 5426 // check on occupancy sensor 5427 String sensorName = leToolBarPanel.blockSensorComboBox.getSelectedItemDisplayName(); 5428 if (sensorName == null) { 5429 sensorName = ""; 5430 } 5431 5432 if (!sensorName.isEmpty()) { 5433 if (!validateSensor(sensorName, b, this)) { 5434 b.setOccupancySensorName(""); 5435 } else { 5436 leToolBarPanel.blockSensorComboBox.setSelectedItem(b.getOccupancySensor()); 5437 } 5438 } 5439 } 5440 5441 // set default continuing route Turnout State 5442 o.setContinuingSense(Turnout.CLOSED); 5443 5444 // check on a physical turnout 5445 String turnoutName = leToolBarPanel.turnoutNameComboBox.getSelectedItemDisplayName(); 5446 if (turnoutName == null) { 5447 turnoutName = ""; 5448 } 5449 5450 if (validatePhysicalTurnout(turnoutName, this)) { 5451 // turnout is valid and unique. 5452 o.setTurnout(turnoutName); 5453 5454 if (o.getTurnout().getSystemName().equals(turnoutName)) { 5455 leToolBarPanel.turnoutNameComboBox.setSelectedItem(o.getTurnout()); 5456 } 5457 } else { 5458 o.setTurnout(""); 5459 leToolBarPanel.turnoutNameComboBox.setSelectedItem(null); 5460 leToolBarPanel.turnoutNameComboBox.setSelectedIndex(-1); 5461 } 5462 } 5463 5464 /** 5465 * Validates that a physical turnout exists and is unique among Layout 5466 * Turnouts Returns true if valid turnout was entered, false otherwise 5467 * 5468 * @param inTurnoutName the (system or user) name of the turnout 5469 * @param inOpenPane the pane over which to show dialogs (null to 5470 * suppress dialogs) 5471 * @return true if valid 5472 */ 5473 public boolean validatePhysicalTurnout( 5474 @Nonnull String inTurnoutName, 5475 @CheckForNull Component inOpenPane) { 5476 // check if turnout name was entered 5477 if (inTurnoutName.isEmpty()) { 5478 // no turnout entered 5479 return false; 5480 } 5481 5482 // check that the unique turnout name corresponds to a defined physical turnout 5483 Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(inTurnoutName); 5484 if (t == null) { 5485 // There is no turnout corresponding to this name 5486 if (inOpenPane != null) { 5487 JmriJOptionPane.showMessageDialog(inOpenPane, 5488 MessageFormat.format(Bundle.getMessage("Error8"), inTurnoutName), 5489 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5490 } 5491 return false; 5492 } 5493 5494 log.debug("validatePhysicalTurnout('{}')", inTurnoutName); 5495 boolean result = true; // assume success (optimist!) 5496 5497 // ensure that this turnout is unique among Layout Turnouts in this Layout 5498 for (LayoutTurnout lt : getLayoutTurnouts()) { 5499 t = lt.getTurnout(); 5500 if (t != null) { 5501 String sname = t.getSystemName(); 5502 String uname = t.getUserName(); 5503 log.debug("{}: Turnout tested '{}' and '{}'.", lt.getName(), sname, uname); 5504 if ((sname.equals(inTurnoutName)) 5505 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5506 result = false; 5507 break; 5508 } 5509 } 5510 5511 // Only check for the second turnout if the type is a double cross over 5512 // otherwise the second turnout is used to throw an additional turnout at 5513 // the same time. 5514 if (lt.isTurnoutTypeXover()) { 5515 t = lt.getSecondTurnout(); 5516 if (t != null) { 5517 String sname = t.getSystemName(); 5518 String uname = t.getUserName(); 5519 log.debug("{}: 2nd Turnout tested '{}' and '{}'.", lt.getName(), sname, uname); 5520 if ((sname.equals(inTurnoutName)) 5521 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5522 result = false; 5523 break; 5524 } 5525 } 5526 } 5527 } 5528 5529 if (result) { // only need to test slips if we haven't failed yet... 5530 // ensure that this turnout is unique among Layout slips in this Layout 5531 for (LayoutSlip sl : getLayoutSlips()) { 5532 t = sl.getTurnout(); 5533 if (t != null) { 5534 String sname = t.getSystemName(); 5535 String uname = t.getUserName(); 5536 log.debug("{}: slip Turnout tested '{}' and '{}'.", sl.getName(), sname, uname); 5537 if ((sname.equals(inTurnoutName)) 5538 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5539 result = false; 5540 break; 5541 } 5542 } 5543 5544 t = sl.getTurnoutB(); 5545 if (t != null) { 5546 String sname = t.getSystemName(); 5547 String uname = t.getUserName(); 5548 log.debug("{}: slip Turnout B tested '{}' and '{}'.", sl.getName(), sname, uname); 5549 if ((sname.equals(inTurnoutName)) 5550 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5551 result = false; 5552 break; 5553 } 5554 } 5555 } 5556 } 5557 5558 if (result) { // only need to test Turntable turnouts if we haven't failed yet... 5559 // ensure that this turntable turnout is unique among turnouts in this Layout 5560 for (LayoutTurntable tt : getLayoutTurntables()) { 5561 for (LayoutTurntable.RayTrack ray : tt.getRayTrackList()) { 5562 t = ray.getTurnout(); 5563 if (t != null) { 5564 String sname = t.getSystemName(); 5565 String uname = t.getUserName(); 5566 log.debug("{}: Turntable turnout tested '{}' and '{}'.", ray.getTurnoutName(), sname, uname); 5567 if ((sname.equals(inTurnoutName)) 5568 || ((uname != null) && (uname.equals(inTurnoutName)))) { 5569 result = false; 5570 break; 5571 } 5572 } 5573 } 5574 } 5575 } 5576 5577 if (!result && (inOpenPane != null)) { 5578 JmriJOptionPane.showMessageDialog(inOpenPane, 5579 MessageFormat.format(Bundle.getMessage("Error4"), inTurnoutName), 5580 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5581 } 5582 return result; 5583 } 5584 5585 /** 5586 * link the 'from' object and type to the 'to' object and type 5587 * 5588 * @param fromObject the object to link from 5589 * @param fromPointType the object type to link from 5590 * @param toObject the object to link to 5591 * @param toPointType the object type to link to 5592 */ 5593 public void setLink(@Nonnull LayoutTrack fromObject, HitPointType fromPointType, 5594 @Nonnull LayoutTrack toObject, HitPointType toPointType) { 5595 switch (fromPointType) { 5596 case POS_POINT: { 5597 if ((toPointType == HitPointType.TRACK) && (fromObject instanceof PositionablePoint)) { 5598 ((PositionablePoint) fromObject).setTrackConnection((TrackSegment) toObject); 5599 } else { 5600 log.error("Attempt to link a non-TRACK connection ('{}')to a Positionable Point ('{}')", 5601 toObject.getName(), fromObject.getName()); 5602 } 5603 break; 5604 } 5605 5606 case TURNOUT_A: 5607 case TURNOUT_B: 5608 case TURNOUT_C: 5609 case TURNOUT_D: 5610 case SLIP_A: 5611 case SLIP_B: 5612 case SLIP_C: 5613 case SLIP_D: 5614 case LEVEL_XING_A: 5615 case LEVEL_XING_B: 5616 case LEVEL_XING_C: 5617 case LEVEL_XING_D: { 5618 try { 5619 fromObject.setConnection(fromPointType, toObject, toPointType); 5620 } catch (JmriException e) { 5621 // ignore (log.error in setConnection method) 5622 } 5623 break; 5624 } 5625 5626 case TRACK: { 5627 // should never happen, Track Segment links are set in ctor 5628 log.error("Illegal request to set a Track Segment link"); 5629 break; 5630 } 5631 5632 default: { 5633 if (HitPointType.isTurntableRayHitType(fromPointType) && (fromObject instanceof LayoutTurntable)) { 5634 if (toObject instanceof TrackSegment) { 5635 ((LayoutTurntable) fromObject).setRayConnect((TrackSegment) toObject, 5636 fromPointType.turntableTrackIndex()); 5637 } else { 5638 log.warn("setLink found expected toObject type {} with fromPointType {} fromObject type {}", 5639 toObject.getClass(), fromPointType, fromObject.getClass(), new Exception("traceback")); 5640 } 5641 } else { 5642 log.warn("setLink found expected fromObject type {} with fromPointType {} toObject type {}", 5643 fromObject.getClass(), fromPointType, toObject.getClass(), new Exception("traceback")); 5644 } 5645 break; 5646 } 5647 } 5648 } 5649 5650 /** 5651 * Return a layout block with the entered name, creating a new one if 5652 * needed. Note that the entered name becomes the user name of the 5653 * LayoutBlock, and a system name is automatically created by 5654 * LayoutBlockManager if needed. 5655 * <p> 5656 * If the block name is a system name, then the user will have to supply a 5657 * user name for the block. 5658 * <p> 5659 * Some, but not all, errors pop a Swing error dialog in addition to 5660 * logging. 5661 * 5662 * @param inBlockName the entered name 5663 * @return the provided LayoutBlock 5664 */ 5665 public LayoutBlock provideLayoutBlock(@Nonnull String inBlockName) { 5666 LayoutBlock result = null; // assume failure (pessimist!) 5667 LayoutBlock newBlk = null; // assume failure (pessimist!) 5668 5669 if (inBlockName.isEmpty()) { 5670 // nothing entered, try autoAssign 5671 if (autoAssignBlocks) { 5672 newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(); 5673 if (null == newBlk) { 5674 log.error("provideLayoutBlock: Failure to auto-assign for empty LayoutBlock name"); 5675 } 5676 } else { 5677 log.debug("provideLayoutBlock: no name given and not assigning auto block names"); 5678 } 5679 } else { 5680 // check if this Layout Block already exists 5681 result = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(inBlockName); 5682 if (result == null) { //(no) 5683 // The combo box name can be either a block system name or a block user name 5684 Block checkBlock = InstanceManager.getDefault(BlockManager.class).getBlock(inBlockName); 5685 if (checkBlock == null) { 5686 log.error("provideLayoutBlock: The block name '{}' does not return a block.", inBlockName); 5687 } else { 5688 String checkUserName = checkBlock.getUserName(); 5689 if (checkUserName != null && checkUserName.equals(inBlockName)) { 5690 // Go ahead and use the name for the layout block 5691 newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, inBlockName); 5692 if (newBlk == null) { 5693 log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}'.", inBlockName); 5694 } 5695 } else { 5696 // Appears to be a system name, request a user name 5697 String blkUserName = (String)JmriJOptionPane.showInputDialog(getTargetFrame(), 5698 Bundle.getMessage("BlkUserNameMsg"), 5699 Bundle.getMessage("BlkUserNameTitle"), 5700 JmriJOptionPane.PLAIN_MESSAGE, null, null, ""); 5701 if (blkUserName != null && !blkUserName.isEmpty()) { 5702 // Verify the user name 5703 Block checkDuplicate = InstanceManager.getDefault(BlockManager.class).getByUserName(blkUserName); 5704 if (checkDuplicate != null) { 5705 JmriJOptionPane.showMessageDialog(getTargetFrame(), 5706 Bundle.getMessage("BlkUserNameInUse", blkUserName), 5707 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 5708 } else { 5709 // OK to use as a block user name 5710 checkBlock.setUserName(blkUserName); 5711 newBlk = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, blkUserName); 5712 if (newBlk == null) { 5713 log.error("provideLayoutBlock: Failure to create new LayoutBlock '{}' with a new user name.", blkUserName); 5714 } 5715 } 5716 } 5717 } 5718 } 5719 } 5720 } 5721 5722 // if we created a new block 5723 if (newBlk != null) { 5724 // initialize the new block 5725 // log.debug("provideLayoutBlock :: Init new block {}", inBlockName); 5726 newBlk.initializeLayoutBlock(); 5727 newBlk.initializeLayoutBlockRouting(); 5728 newBlk.setBlockTrackColor(defaultTrackColor); 5729 newBlk.setBlockOccupiedColor(defaultOccupiedTrackColor); 5730 newBlk.setBlockExtraColor(defaultAlternativeTrackColor); 5731 result = newBlk; 5732 } 5733 5734 if (result != null) { 5735 // set both new and previously existing block 5736 result.addLayoutEditor(this); 5737 result.incrementUse(); 5738 setDirty(); 5739 } 5740 return result; 5741 } 5742 5743 /** 5744 * Validates that the supplied occupancy sensor name corresponds to an 5745 * existing sensor and is unique among all blocks. If valid, returns true 5746 * and sets the block sensor name in the block. Else returns false, and does 5747 * nothing to the block. 5748 * 5749 * @param sensorName the sensor name to validate 5750 * @param blk the LayoutBlock in which to set it 5751 * @param openFrame the frame (Component) it is in 5752 * @return true if sensor is valid 5753 */ 5754 public boolean validateSensor( 5755 @Nonnull String sensorName, 5756 @Nonnull LayoutBlock blk, 5757 @Nonnull Component openFrame) { 5758 boolean result = false; // assume failure (pessimist!) 5759 5760 // check if anything entered 5761 if (!sensorName.isEmpty()) { 5762 // get a validated sensor corresponding to this name and assigned to block 5763 if (blk.getOccupancySensorName().equals(sensorName)) { 5764 result = true; 5765 } else { 5766 Sensor s = blk.validateSensor(sensorName, openFrame); 5767 result = (s != null); // if sensor returned result is true. 5768 } 5769 } 5770 return result; 5771 } 5772 5773 /** 5774 * Return a layout block with the given name if one exists. Registers this 5775 * LayoutEditor with the layout block. This method is designed to be used 5776 * when a panel is loaded. The calling method must handle whether the use 5777 * count should be incremented. 5778 * 5779 * @param blockID the given name 5780 * @return null if blockID does not already exist 5781 */ 5782 public LayoutBlock getLayoutBlock(@Nonnull String blockID) { 5783 // check if this Layout Block already exists 5784 LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(blockID); 5785 if (blk == null) { 5786 log.error("LayoutBlock '{}' not found when panel loaded", blockID); 5787 return null; 5788 } 5789 blk.addLayoutEditor(this); 5790 return blk; 5791 } 5792 5793 /** 5794 * Remove object from all Layout Editor temporary lists of items not part of 5795 * track schematic 5796 * 5797 * @param s the object to remove 5798 * @return true if found 5799 */ 5800 private boolean remove(@Nonnull Object s) { 5801 boolean found = false; 5802 5803 if (backgroundImage.contains(s)) { 5804 backgroundImage.remove(s); 5805 found = true; 5806 } 5807 if (memoryLabelList.contains(s)) { 5808 memoryLabelList.remove(s); 5809 found = true; 5810 } 5811 if (globalVariableLabelList.contains(s)) { 5812 globalVariableLabelList.remove(s); 5813 found = true; 5814 } 5815 if (blockContentsLabelList.contains(s)) { 5816 blockContentsLabelList.remove(s); 5817 found = true; 5818 } 5819 if (multiSensors.contains(s)) { 5820 multiSensors.remove(s); 5821 found = true; 5822 } 5823 if (clocks.contains(s)) { 5824 clocks.remove(s); 5825 found = true; 5826 } 5827 if (labelImage.contains(s)) { 5828 labelImage.remove(s); 5829 found = true; 5830 } 5831 5832 if (sensorImage.contains(s) || sensorList.contains(s)) { 5833 Sensor sensor = ((SensorIcon) s).getSensor(); 5834 if (sensor != null) { 5835 if (removeAttachedBean((sensor))) { 5836 sensorImage.remove(s); 5837 sensorList.remove(s); 5838 found = true; 5839 } else { 5840 return false; 5841 } 5842 } 5843 } 5844 5845 if (signalHeadImage.contains(s) || signalList.contains(s)) { 5846 SignalHead head = ((SignalHeadIcon) s).getSignalHead(); 5847 if (head != null) { 5848 if (removeAttachedBean((head))) { 5849 signalHeadImage.remove(s); 5850 signalList.remove(s); 5851 found = true; 5852 } else { 5853 return false; 5854 } 5855 } 5856 } 5857 5858 if (signalMastList.contains(s)) { 5859 SignalMast mast = ((SignalMastIcon) s).getSignalMast(); 5860 if (mast != null) { 5861 if (removeAttachedBean((mast))) { 5862 signalMastList.remove(s); 5863 found = true; 5864 } else { 5865 return false; 5866 } 5867 } 5868 } 5869 5870 super.removeFromContents((Positionable) s); 5871 5872 if (found) { 5873 setDirty(); 5874 redrawPanel(); 5875 } 5876 return found; 5877 } 5878 5879 @Override 5880 public boolean removeFromContents(@Nonnull Positionable l) { 5881 return remove(l); 5882 } 5883 5884 private String findBeanUsage(@Nonnull NamedBean bean) { 5885 PositionablePoint pe; 5886 PositionablePoint pw; 5887 LayoutTurnout lt; 5888 LevelXing lx; 5889 LayoutSlip ls; 5890 boolean found = false; 5891 StringBuilder sb = new StringBuilder(); 5892 String msgKey = "DeleteReference"; // NOI18N 5893 String beanKey = "None"; // NOI18N 5894 String beanValue = bean.getDisplayName(); 5895 5896 if (bean instanceof SignalMast) { 5897 beanKey = "BeanNameSignalMast"; // NOI18N 5898 5899 if (InstanceManager.getDefault(SignalMastLogicManager.class).isSignalMastUsed((SignalMast) bean)) { 5900 SignalMastLogic sml = InstanceManager.getDefault( 5901 SignalMastLogicManager.class).getSignalMastLogic((SignalMast) bean); 5902 if ((sml != null) && sml.useLayoutEditor(sml.getDestinationList().get(0))) { 5903 msgKey = "DeleteSmlReference"; // NOI18N 5904 } 5905 } 5906 } else if (bean instanceof Sensor) { 5907 beanKey = "BeanNameSensor"; // NOI18N 5908 } else if (bean instanceof SignalHead) { 5909 beanKey = "BeanNameSignalHead"; // NOI18N 5910 } 5911 if (!beanKey.equals("None")) { // NOI18N 5912 sb.append(Bundle.getMessage(msgKey, Bundle.getMessage(beanKey), beanValue)); 5913 } 5914 5915 if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) { 5916 TrackSegment t1 = pw.getConnect1(); 5917 TrackSegment t2 = pw.getConnect2(); 5918 if (t1 != null) { 5919 if (t2 != null) { 5920 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 5921 sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName())); // NOI18N 5922 } else { 5923 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 5924 } 5925 } 5926 found = true; 5927 } 5928 5929 if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) { 5930 TrackSegment t1 = pe.getConnect1(); 5931 TrackSegment t2 = pe.getConnect2(); 5932 5933 if (t1 != null) { 5934 if (t2 != null) { 5935 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 5936 sb.append(Bundle.getMessage("DeleteAtPoint2", t2.getBlockName())); // NOI18N 5937 } else { 5938 sb.append(Bundle.getMessage("DeleteAtPoint1", t1.getBlockName())); // NOI18N 5939 } 5940 } 5941 found = true; 5942 } 5943 5944 if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) { 5945 sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("BeanNameTurnout"), lt.getTurnoutName())); // NOI18N 5946 found = true; 5947 } 5948 5949 if ((lx = finder.findLevelXingByBean(bean)) != null) { 5950 sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("LevelCrossing"), lx.getId())); // NOI18N 5951 found = true; 5952 } 5953 5954 if ((ls = finder.findLayoutSlipByBean(bean)) != null) { 5955 sb.append(Bundle.getMessage("DeleteAtOther", Bundle.getMessage("Slip"), ls.getTurnoutName())); // NOI18N 5956 found = true; 5957 } 5958 5959 if (!found) { 5960 return null; 5961 } 5962 return sb.toString(); 5963 } 5964 5965 /** 5966 * NX Sensors, Signal Heads and Signal Masts can be attached to positional 5967 * points, turnouts and level crossings. If an attachment exists, present an 5968 * option to cancel the remove action, remove the attachement or retain the 5969 * attachment. 5970 * 5971 * @param bean The named bean to be removed. 5972 * @return true if OK to remove the related icon. 5973 */ 5974 private boolean removeAttachedBean(@Nonnull NamedBean bean) { 5975 String usage = findBeanUsage(bean); 5976 5977 if (usage != null) { 5978 usage = String.format("<html>%s</html>", usage); 5979 int selectedValue = JmriJOptionPane.showOptionDialog(this, 5980 usage, Bundle.getMessage("WarningTitle"), 5981 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 5982 new Object[]{Bundle.getMessage("ButtonYes"), 5983 Bundle.getMessage("ButtonNo"), 5984 Bundle.getMessage("ButtonCancel")}, 5985 Bundle.getMessage("ButtonYes")); 5986 5987 if (selectedValue == 1 ) { // array pos 1, No 5988 return true; // return leaving the references in place but allow the icon to be deleted. 5989 } 5990 // array pos 2, cancel or Dialog closed 5991 if (selectedValue == 2 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) { 5992 return false; // do not delete the item 5993 } 5994 if (bean instanceof Sensor) { 5995 // Additional actions for NX sensor pairs 5996 return getLETools().removeSensorAssignment((Sensor) bean); 5997 } else { 5998 removeBeanRefs(bean); 5999 } 6000 } 6001 return true; 6002 } 6003 6004 private void removeBeanRefs(@Nonnull NamedBean bean) { 6005 PositionablePoint pe; 6006 PositionablePoint pw; 6007 LayoutTurnout lt; 6008 LevelXing lx; 6009 LayoutSlip ls; 6010 6011 if ((pw = finder.findPositionablePointByWestBoundBean(bean)) != null) { 6012 pw.removeBeanReference(bean); 6013 } 6014 6015 if ((pe = finder.findPositionablePointByEastBoundBean(bean)) != null) { 6016 pe.removeBeanReference(bean); 6017 } 6018 6019 if ((lt = finder.findLayoutTurnoutByBean(bean)) != null) { 6020 lt.removeBeanReference(bean); 6021 } 6022 6023 if ((lx = finder.findLevelXingByBean(bean)) != null) { 6024 lx.removeBeanReference(bean); 6025 } 6026 6027 if ((ls = finder.findLayoutSlipByBean(bean)) != null) { 6028 ls.removeBeanReference(bean); 6029 } 6030 } 6031 6032 private boolean noWarnPositionablePoint = false; 6033 6034 /** 6035 * Remove a PositionablePoint -- an Anchor or an End Bumper. 6036 * 6037 * @param o the PositionablePoint to remove 6038 * @return true if removed 6039 */ 6040 public boolean removePositionablePoint(@Nonnull PositionablePoint o) { 6041 // First verify with the user that this is really wanted, only show message if there is a bit of track connected 6042 if ((o.getConnect1() != null) || (o.getConnect2() != null)) { 6043 if (!noWarnPositionablePoint) { 6044 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6045 Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"), 6046 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6047 new Object[]{Bundle.getMessage("ButtonYes"), 6048 Bundle.getMessage("ButtonNo"), 6049 Bundle.getMessage("ButtonYesPlus")}, 6050 Bundle.getMessage("ButtonNo")); 6051 6052 // array position 1, ButtonNo , or Dialog Closed. 6053 if (selectedValue == 1 || selectedValue == JmriJOptionPane.CLOSED_OPTION ) { 6054 return false; // return without creating if "No" response 6055 } 6056 6057 if (selectedValue == 2) { // array position 2, ButtonYesPlus 6058 // Suppress future warnings, and continue 6059 noWarnPositionablePoint = true; 6060 } 6061 } 6062 6063 // remove from selection information 6064 if (selectedObject == o) { 6065 selectedObject = null; 6066 } 6067 6068 if (prevSelectedObject == o) { 6069 prevSelectedObject = null; 6070 } 6071 6072 // remove connections if any 6073 TrackSegment t1 = o.getConnect1(); 6074 TrackSegment t2 = o.getConnect2(); 6075 6076 if (t1 != null) { 6077 removeTrackSegment(t1); 6078 } 6079 6080 if (t2 != null) { 6081 removeTrackSegment(t2); 6082 } 6083 6084 // delete from array 6085 } 6086 6087 return removeLayoutTrackAndRedraw(o); 6088 } 6089 6090 private boolean noWarnLayoutTurnout = false; 6091 6092 /** 6093 * Remove a LayoutTurnout 6094 * 6095 * @param o the LayoutTurnout to remove 6096 * @return true if removed 6097 */ 6098 public boolean removeLayoutTurnout(@Nonnull LayoutTurnout o) { 6099 // First verify with the user that this is really wanted 6100 if (!noWarnLayoutTurnout) { 6101 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6102 Bundle.getMessage("Question1r"), Bundle.getMessage("WarningTitle"), 6103 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6104 new Object[]{Bundle.getMessage("ButtonYes"), 6105 Bundle.getMessage("ButtonNo"), 6106 Bundle.getMessage("ButtonYesPlus")}, 6107 Bundle.getMessage("ButtonNo")); 6108 6109 // return without removing if array position 1 "No" response or Dialog closed 6110 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6111 return false; 6112 } 6113 6114 if (selectedValue == 2 ) { // ButtonYesPlus in array position 2 6115 // Suppress future warnings, and continue 6116 noWarnLayoutTurnout = true; 6117 } 6118 } 6119 6120 // remove from selection information 6121 if (selectedObject == o) { 6122 selectedObject = null; 6123 } 6124 6125 if (prevSelectedObject == o) { 6126 prevSelectedObject = null; 6127 } 6128 6129 // remove connections if any 6130 TrackSegment t = (TrackSegment) o.getConnectA(); 6131 6132 if (t != null) { 6133 substituteAnchor(getLayoutTurnoutView(o).getCoordsA(), o, t); 6134 } 6135 t = (TrackSegment) o.getConnectB(); 6136 6137 if (t != null) { 6138 substituteAnchor(getLayoutTurnoutView(o).getCoordsB(), o, t); 6139 } 6140 t = (TrackSegment) o.getConnectC(); 6141 6142 if (t != null) { 6143 substituteAnchor(getLayoutTurnoutView(o).getCoordsC(), o, t); 6144 } 6145 t = (TrackSegment) o.getConnectD(); 6146 6147 if (t != null) { 6148 substituteAnchor(getLayoutTurnoutView(o).getCoordsD(), o, t); 6149 } 6150 6151 // decrement Block use count(s) 6152 LayoutBlock b = o.getLayoutBlock(); 6153 6154 if (b != null) { 6155 b.decrementUse(); 6156 } 6157 6158 if (o.isTurnoutTypeXover() || o.isTurnoutTypeSlip()) { 6159 LayoutBlock b2 = o.getLayoutBlockB(); 6160 6161 if ((b2 != null) && (b2 != b)) { 6162 b2.decrementUse(); 6163 } 6164 LayoutBlock b3 = o.getLayoutBlockC(); 6165 6166 if ((b3 != null) && (b3 != b) && (b3 != b2)) { 6167 b3.decrementUse(); 6168 } 6169 LayoutBlock b4 = o.getLayoutBlockD(); 6170 6171 if ((b4 != null) && (b4 != b) 6172 && (b4 != b2) && (b4 != b3)) { 6173 b4.decrementUse(); 6174 } 6175 } 6176 6177 return removeLayoutTrackAndRedraw(o); 6178 } 6179 6180 private void substituteAnchor(@Nonnull Point2D loc, 6181 @Nonnull LayoutTrack o, @Nonnull TrackSegment t) { 6182 PositionablePoint p = addAnchor(loc); 6183 6184 if (t.getConnect1() == o) { 6185 t.setNewConnect1(p, HitPointType.POS_POINT); 6186 } 6187 6188 if (t.getConnect2() == o) { 6189 t.setNewConnect2(p, HitPointType.POS_POINT); 6190 } 6191 p.setTrackConnection(t); 6192 } 6193 6194 private boolean noWarnLevelXing = false; 6195 6196 /** 6197 * Remove a Level Crossing 6198 * 6199 * @param o the LevelXing to remove 6200 * @return true if removed 6201 */ 6202 public boolean removeLevelXing(@Nonnull LevelXing o) { 6203 // First verify with the user that this is really wanted 6204 if (!noWarnLevelXing) { 6205 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6206 Bundle.getMessage("Question3r"), Bundle.getMessage("WarningTitle"), 6207 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6208 new Object[]{Bundle.getMessage("ButtonYes"), 6209 Bundle.getMessage("ButtonNo"), 6210 Bundle.getMessage("ButtonYesPlus")}, 6211 Bundle.getMessage("ButtonNo")); 6212 6213 // array position 1 Button No, or Dialog closed. 6214 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6215 return false; 6216 } 6217 6218 if (selectedValue == 2 ) { // array position 2 ButtonYesPlus 6219 // Suppress future warnings, and continue 6220 noWarnLevelXing = true; 6221 } 6222 } 6223 6224 // remove from selection information 6225 if (selectedObject == o) { 6226 selectedObject = null; 6227 } 6228 6229 if (prevSelectedObject == o) { 6230 prevSelectedObject = null; 6231 } 6232 6233 // remove connections if any 6234 LevelXingView ov = getLevelXingView(o); 6235 6236 TrackSegment t = (TrackSegment) o.getConnectA(); 6237 if (t != null) { 6238 substituteAnchor(ov.getCoordsA(), o, t); 6239 } 6240 t = (TrackSegment) o.getConnectB(); 6241 6242 if (t != null) { 6243 substituteAnchor(ov.getCoordsB(), o, t); 6244 } 6245 t = (TrackSegment) o.getConnectC(); 6246 6247 if (t != null) { 6248 substituteAnchor(ov.getCoordsC(), o, t); 6249 } 6250 t = (TrackSegment) o.getConnectD(); 6251 6252 if (t != null) { 6253 substituteAnchor(ov.getCoordsD(), o, t); 6254 } 6255 6256 // decrement block use count if any blocks in use 6257 LayoutBlock lb = o.getLayoutBlockAC(); 6258 6259 if (lb != null) { 6260 lb.decrementUse(); 6261 } 6262 LayoutBlock lbx = o.getLayoutBlockBD(); 6263 6264 if ((lbx != null) && (lb != null) && (lbx != lb)) { 6265 lb.decrementUse(); 6266 } 6267 6268 return removeLayoutTrackAndRedraw(o); 6269 } 6270 6271 private boolean noWarnSlip = false; 6272 6273 /** 6274 * Remove a slip 6275 * 6276 * @param o the LayoutSlip to remove 6277 * @return true if removed 6278 */ 6279 public boolean removeLayoutSlip(@Nonnull LayoutTurnout o) { 6280 if (!(o instanceof LayoutSlip)) { 6281 return false; 6282 } 6283 6284 // First verify with the user that this is really wanted 6285 if (!noWarnSlip) { 6286 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6287 Bundle.getMessage("Question5r"), Bundle.getMessage("WarningTitle"), 6288 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6289 new Object[]{Bundle.getMessage("ButtonYes"), 6290 Bundle.getMessage("ButtonNo"), 6291 Bundle.getMessage("ButtonYesPlus")}, 6292 Bundle.getMessage("ButtonNo")); 6293 6294 // return without removing if array position 1 "No" response or Dialog closed 6295 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6296 return false; 6297 } 6298 6299 if (selectedValue == 2 ) { // ButtonYesPlus in array position 2 6300 // Suppress future warnings, and continue 6301 noWarnSlip = true; 6302 } 6303 } 6304 6305 LayoutTurnoutView ov = getLayoutTurnoutView(o); 6306 6307 // remove from selection information 6308 if (selectedObject == o) { 6309 selectedObject = null; 6310 } 6311 6312 if (prevSelectedObject == o) { 6313 prevSelectedObject = null; 6314 } 6315 6316 // remove connections if any 6317 TrackSegment t = (TrackSegment) o.getConnectA(); 6318 6319 if (t != null) { 6320 substituteAnchor(ov.getCoordsA(), o, t); 6321 } 6322 t = (TrackSegment) o.getConnectB(); 6323 6324 if (t != null) { 6325 substituteAnchor(ov.getCoordsB(), o, t); 6326 } 6327 t = (TrackSegment) o.getConnectC(); 6328 6329 if (t != null) { 6330 substituteAnchor(ov.getCoordsC(), o, t); 6331 } 6332 t = (TrackSegment) o.getConnectD(); 6333 6334 if (t != null) { 6335 substituteAnchor(ov.getCoordsD(), o, t); 6336 } 6337 6338 // decrement block use count if any blocks in use 6339 LayoutBlock lb = o.getLayoutBlock(); 6340 6341 if (lb != null) { 6342 lb.decrementUse(); 6343 } 6344 6345 return removeLayoutTrackAndRedraw(o); 6346 } 6347 6348 private boolean noWarnTurntable = false; 6349 6350 /** 6351 * Remove a Layout Turntable 6352 * 6353 * @param o the LayoutTurntable to remove 6354 * @return true if removed 6355 */ 6356 public boolean removeTurntable(@Nonnull LayoutTurntable o) { 6357 // First verify with the user that this is really wanted 6358 if (!noWarnTurntable) { 6359 int selectedValue = JmriJOptionPane.showOptionDialog(this, 6360 Bundle.getMessage("Question4r"), Bundle.getMessage("WarningTitle"), 6361 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, 6362 new Object[]{Bundle.getMessage("ButtonYes"), 6363 Bundle.getMessage("ButtonNo"), 6364 Bundle.getMessage("ButtonYesPlus")}, 6365 Bundle.getMessage("ButtonNo")); 6366 6367 // return without removing if array position 1 "No" response or Dialog closed 6368 if (selectedValue == 1 || selectedValue==JmriJOptionPane.CLOSED_OPTION ) { 6369 return false; 6370 } 6371 6372 if (selectedValue == 2 ) { // ButtonYesPlus in array position 2 6373 // Suppress future warnings, and continue 6374 noWarnTurntable = true; 6375 } 6376 } 6377 6378 // remove from selection information 6379 if (selectedObject == o) { 6380 selectedObject = null; 6381 } 6382 6383 if (prevSelectedObject == o) { 6384 prevSelectedObject = null; 6385 } 6386 6387 // remove connections if any 6388 LayoutTurntableView ov = getLayoutTurntableView(o); 6389 for (int j = 0; j < o.getNumberRays(); j++) { 6390 TrackSegment t = ov.getRayConnectOrdered(j); 6391 6392 if (t != null) { 6393 substituteAnchor(ov.getRayCoordsIndexed(j), o, t); 6394 } 6395 } 6396 6397 return removeLayoutTrackAndRedraw(o); 6398 } 6399 6400 /** 6401 * Remove a Track Segment 6402 * 6403 * @param o the TrackSegment to remove 6404 */ 6405 public void removeTrackSegment(@Nonnull TrackSegment o) { 6406 // save affected blocks 6407 LayoutBlock block1 = null; 6408 LayoutBlock block2 = null; 6409 LayoutBlock block = o.getLayoutBlock(); 6410 6411 // remove any connections 6412 HitPointType type = o.getType1(); 6413 6414 if (type == HitPointType.POS_POINT) { 6415 PositionablePoint p = (PositionablePoint) (o.getConnect1()); 6416 6417 if (p != null) { 6418 p.removeTrackConnection(o); 6419 6420 if (p.getConnect1() != null) { 6421 block1 = p.getConnect1().getLayoutBlock(); 6422 } else if (p.getConnect2() != null) { 6423 block1 = p.getConnect2().getLayoutBlock(); 6424 } 6425 } 6426 } else { 6427 block1 = getAffectedBlock(o.getConnect1(), type); 6428 disconnect(o.getConnect1(), type); 6429 } 6430 type = o.getType2(); 6431 6432 if (type == HitPointType.POS_POINT) { 6433 PositionablePoint p = (PositionablePoint) (o.getConnect2()); 6434 6435 if (p != null) { 6436 p.removeTrackConnection(o); 6437 6438 if (p.getConnect1() != null) { 6439 block2 = p.getConnect1().getLayoutBlock(); 6440 } else if (p.getConnect2() != null) { 6441 block2 = p.getConnect2().getLayoutBlock(); 6442 } 6443 } 6444 } else { 6445 block2 = getAffectedBlock(o.getConnect2(), type); 6446 disconnect(o.getConnect2(), type); 6447 } 6448 6449 // delete from array 6450 removeLayoutTrack(o); 6451 6452 // update affected blocks 6453 if (block != null) { 6454 // decrement Block use count 6455 block.decrementUse(); 6456 getLEAuxTools().setBlockConnectivityChanged(); 6457 block.updatePaths(); 6458 } 6459 6460 if ((block1 != null) && (block1 != block)) { 6461 block1.updatePaths(); 6462 } 6463 6464 if ((block2 != null) && (block2 != block) && (block2 != block1)) { 6465 block2.updatePaths(); 6466 } 6467 6468 // 6469 setDirty(); 6470 redrawPanel(); 6471 } 6472 6473 private void disconnect(@Nonnull LayoutTrack o, HitPointType type) { 6474 switch (type) { 6475 case TURNOUT_A: 6476 case TURNOUT_B: 6477 case TURNOUT_C: 6478 case TURNOUT_D: 6479 case SLIP_A: 6480 case SLIP_B: 6481 case SLIP_C: 6482 case SLIP_D: 6483 case LEVEL_XING_A: 6484 case LEVEL_XING_B: 6485 case LEVEL_XING_C: 6486 case LEVEL_XING_D: { 6487 try { 6488 o.setConnection(type, null, HitPointType.NONE); 6489 } catch (JmriException e) { 6490 // ignore (log.error in setConnection method) 6491 } 6492 break; 6493 } 6494 6495 default: { 6496 if (HitPointType.isTurntableRayHitType(type)) { 6497 ((LayoutTurntable) o).setRayConnect(null, type.turntableTrackIndex()); 6498 } 6499 break; 6500 } 6501 } 6502 } 6503 6504 /** 6505 * Depending on the given type, and the real class of the given LayoutTrack, 6506 * determine the connected LayoutTrack. This provides a variable-indirect 6507 * form of e.g. trk.getLayoutBlockC() for example. Perhaps "Connected Block" 6508 * captures the idea better, but that method name is being used for 6509 * something else. 6510 * 6511 * 6512 * @param track The track who's connected blocks are being examined 6513 * @param type This point to check for connected blocks, i.e. TURNOUT_B 6514 * @return The block at a particular point on the track object, or null if 6515 * none. 6516 */ 6517 // Temporary - this should certainly be a LayoutTrack method. 6518 public LayoutBlock getAffectedBlock(@Nonnull LayoutTrack track, HitPointType type) { 6519 LayoutBlock result = null; 6520 6521 switch (type) { 6522 case TURNOUT_A: 6523 case SLIP_A: { 6524 if (track instanceof LayoutTurnout) { 6525 LayoutTurnout lt = (LayoutTurnout) track; 6526 result = lt.getLayoutBlock(); 6527 } 6528 break; 6529 } 6530 6531 case TURNOUT_B: 6532 case SLIP_B: { 6533 if (track instanceof LayoutTurnout) { 6534 LayoutTurnout lt = (LayoutTurnout) track; 6535 result = lt.getLayoutBlockB(); 6536 } 6537 break; 6538 } 6539 6540 case TURNOUT_C: 6541 case SLIP_C: { 6542 if (track instanceof LayoutTurnout) { 6543 LayoutTurnout lt = (LayoutTurnout) track; 6544 result = lt.getLayoutBlockC(); 6545 } 6546 break; 6547 } 6548 6549 case TURNOUT_D: 6550 case SLIP_D: { 6551 if (track instanceof LayoutTurnout) { 6552 LayoutTurnout lt = (LayoutTurnout) track; 6553 result = lt.getLayoutBlockD(); 6554 } 6555 break; 6556 } 6557 6558 case LEVEL_XING_A: 6559 case LEVEL_XING_C: { 6560 if (track instanceof LevelXing) { 6561 LevelXing lx = (LevelXing) track; 6562 result = lx.getLayoutBlockAC(); 6563 } 6564 break; 6565 } 6566 6567 case LEVEL_XING_B: 6568 case LEVEL_XING_D: { 6569 if (track instanceof LevelXing) { 6570 LevelXing lx = (LevelXing) track; 6571 result = lx.getLayoutBlockBD(); 6572 } 6573 break; 6574 } 6575 6576 case TRACK: { 6577 if (track instanceof TrackSegment) { 6578 TrackSegment ts = (TrackSegment) track; 6579 result = ts.getLayoutBlock(); 6580 } 6581 break; 6582 } 6583 default: { 6584 log.warn("Unhandled track type: {}", type); 6585 break; 6586 } 6587 } 6588 return result; 6589 } 6590 6591 /** 6592 * Add a sensor indicator to the Draw Panel 6593 */ 6594 void addSensor() { 6595 String newName = leToolBarPanel.sensorComboBox.getSelectedItemDisplayName(); 6596 if (newName == null) { 6597 newName = ""; 6598 } 6599 6600 if (newName.isEmpty()) { 6601 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"), 6602 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6603 return; 6604 } 6605 SensorIcon l = new SensorIcon(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif", 6606 "resources/icons/smallschematics/tracksegments/circuit-error.gif"), this); 6607 6608 l.setIcon("SensorStateActive", leToolBarPanel.sensorIconEditor.getIcon(0)); 6609 l.setIcon("SensorStateInactive", leToolBarPanel.sensorIconEditor.getIcon(1)); 6610 l.setIcon("BeanStateInconsistent", leToolBarPanel.sensorIconEditor.getIcon(2)); 6611 l.setIcon("BeanStateUnknown", leToolBarPanel.sensorIconEditor.getIcon(3)); 6612 l.setSensor(newName); 6613 l.setDisplayLevel(Editor.SENSORS); 6614 6615 leToolBarPanel.sensorComboBox.setSelectedItem(l.getSensor()); 6616 setNextLocation(l); 6617 try { 6618 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6619 } catch (Positionable.DuplicateIdException e) { 6620 // This should never happen 6621 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6622 } 6623 } 6624 6625 public void putSensor(@Nonnull SensorIcon l) { 6626 l.updateSize(); 6627 l.setDisplayLevel(Editor.SENSORS); 6628 try { 6629 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6630 } catch (Positionable.DuplicateIdException e) { 6631 // This should never happen 6632 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6633 } 6634 } 6635 6636 /** 6637 * Add a signal head to the Panel 6638 */ 6639 void addSignalHead() { 6640 // check for valid signal head entry 6641 String newName = leToolBarPanel.signalHeadComboBox.getSelectedItemDisplayName(); 6642 if (newName == null) { 6643 newName = ""; 6644 } 6645 SignalHead mHead = null; 6646 6647 if (!newName.isEmpty()) { 6648 mHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(newName); 6649 6650 /*if (mHead == null) 6651 mHead = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(newName); 6652 else */ 6653 leToolBarPanel.signalHeadComboBox.setSelectedItem(mHead); 6654 } 6655 6656 if (mHead == null) { 6657 // There is no signal head corresponding to this name 6658 JmriJOptionPane.showMessageDialog(this, 6659 MessageFormat.format(Bundle.getMessage("Error9"), newName), 6660 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6661 return; 6662 } 6663 6664 // create and set up signal icon 6665 SignalHeadIcon l = new SignalHeadIcon(this); 6666 l.setSignalHead(newName); 6667 l.setIcon("SignalHeadStateRed", leToolBarPanel.signalIconEditor.getIcon(0)); 6668 l.setIcon("SignalHeadStateFlashingRed", leToolBarPanel.signalIconEditor.getIcon(1)); 6669 l.setIcon("SignalHeadStateYellow", leToolBarPanel.signalIconEditor.getIcon(2)); 6670 l.setIcon("SignalHeadStateFlashingYellow", leToolBarPanel.signalIconEditor.getIcon(3)); 6671 l.setIcon("SignalHeadStateGreen", leToolBarPanel.signalIconEditor.getIcon(4)); 6672 l.setIcon("SignalHeadStateFlashingGreen", leToolBarPanel.signalIconEditor.getIcon(5)); 6673 l.setIcon("SignalHeadStateDark", leToolBarPanel.signalIconEditor.getIcon(6)); 6674 l.setIcon("SignalHeadStateHeld", leToolBarPanel.signalIconEditor.getIcon(7)); 6675 l.setIcon("SignalHeadStateLunar", leToolBarPanel.signalIconEditor.getIcon(8)); 6676 l.setIcon("SignalHeadStateFlashingLunar", leToolBarPanel.signalIconEditor.getIcon(9)); 6677 unionToPanelBounds(l.getBounds()); 6678 setNextLocation(l); 6679 setDirty(); 6680 putSignal(l); 6681 } 6682 6683 public void putSignal(@Nonnull SignalHeadIcon l) { 6684 l.updateSize(); 6685 l.setDisplayLevel(Editor.SIGNALS); 6686 try { 6687 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6688 } catch (Positionable.DuplicateIdException e) { 6689 // This should never happen 6690 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6691 } 6692 } 6693 6694 @CheckForNull 6695 SignalHead getSignalHead(@Nonnull String name) { 6696 SignalHead sh = InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(name); 6697 6698 if (sh == null) { 6699 sh = InstanceManager.getDefault(SignalHeadManager.class).getByUserName(name); 6700 } 6701 6702 if (sh == null) { 6703 log.warn("did not find a SignalHead named {}", name); 6704 } 6705 return sh; 6706 } 6707 6708 public boolean containsSignalHead(@CheckForNull SignalHead head) { 6709 if (head != null) { 6710 for (SignalHeadIcon h : signalList) { 6711 if (h.getSignalHead() == head) { 6712 return true; 6713 } 6714 } 6715 } 6716 return false; 6717 } 6718 6719 public void removeSignalHead(@CheckForNull SignalHead head) { 6720 if (head != null) { 6721 for (SignalHeadIcon h : signalList) { 6722 if (h.getSignalHead() == head) { 6723 signalList.remove(h); 6724 h.remove(); 6725 h.dispose(); 6726 setDirty(); 6727 redrawPanel(); 6728 break; 6729 } 6730 } 6731 } 6732 } 6733 6734 void addSignalMast() { 6735 // check for valid signal head entry 6736 String newName = leToolBarPanel.signalMastComboBox.getSelectedItemDisplayName(); 6737 if (newName == null) { 6738 newName = ""; 6739 } 6740 SignalMast mMast = null; 6741 6742 if (!newName.isEmpty()) { 6743 mMast = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(newName); 6744 leToolBarPanel.signalMastComboBox.setSelectedItem(mMast); 6745 } 6746 6747 if (mMast == null) { 6748 // There is no signal head corresponding to this name 6749 JmriJOptionPane.showMessageDialog(this, 6750 MessageFormat.format(Bundle.getMessage("Error9"), newName), 6751 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6752 6753 return; 6754 } 6755 6756 // create and set up signal icon 6757 SignalMastIcon l = new SignalMastIcon(this); 6758 l.setSignalMast(newName); 6759 unionToPanelBounds(l.getBounds()); 6760 setNextLocation(l); 6761 setDirty(); 6762 putSignalMast(l); 6763 } 6764 6765 public void putSignalMast(@Nonnull SignalMastIcon l) { 6766 l.updateSize(); 6767 l.setDisplayLevel(Editor.SIGNALS); 6768 try { 6769 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6770 } catch (Positionable.DuplicateIdException e) { 6771 // This should never happen 6772 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6773 } 6774 } 6775 6776 SignalMast getSignalMast(@Nonnull String name) { 6777 SignalMast sh = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name); 6778 6779 if (sh == null) { 6780 sh = InstanceManager.getDefault(SignalMastManager.class).getByUserName(name); 6781 } 6782 6783 if (sh == null) { 6784 log.warn("did not find a SignalMast named {}", name); 6785 } 6786 return sh; 6787 } 6788 6789 public boolean containsSignalMast(@Nonnull SignalMast mast) { 6790 for (SignalMastIcon h : signalMastList) { 6791 if (h.getSignalMast() == mast) { 6792 return true; 6793 } 6794 } 6795 return false; 6796 } 6797 6798 /** 6799 * Add a label to the Draw Panel 6800 */ 6801 void addLabel() { 6802 String labelText = leToolBarPanel.textLabelTextField.getText(); 6803 labelText = (labelText != null) ? labelText.trim() : ""; 6804 6805 if (labelText.isEmpty()) { 6806 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11"), 6807 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6808 return; 6809 } 6810 PositionableLabel l = super.addLabel(labelText); 6811 unionToPanelBounds(l.getBounds()); 6812 setDirty(); 6813 l.setForeground(defaultTextColor); 6814 } 6815 6816 @Override 6817 public void putItem(@Nonnull Positionable l) throws Positionable.DuplicateIdException { 6818 super.putItem(l); 6819 6820 if (l instanceof SensorIcon) { 6821 sensorImage.add((SensorIcon) l); 6822 sensorList.add((SensorIcon) l); 6823 } else if (l instanceof LocoIcon) { 6824 markerImage.add((LocoIcon) l); 6825 } else if (l instanceof SignalHeadIcon) { 6826 signalHeadImage.add((SignalHeadIcon) l); 6827 signalList.add((SignalHeadIcon) l); 6828 } else if (l instanceof SignalMastIcon) { 6829 signalMastList.add((SignalMastIcon) l); 6830 } else if (l instanceof MemoryIcon) { 6831 memoryLabelList.add((MemoryIcon) l); 6832 } else if (l instanceof GlobalVariableIcon) { 6833 globalVariableLabelList.add((GlobalVariableIcon) l); 6834 } else if (l instanceof BlockContentsIcon) { 6835 blockContentsLabelList.add((BlockContentsIcon) l); 6836 } else if (l instanceof AnalogClock2Display) { 6837 clocks.add((AnalogClock2Display) l); 6838 } else if (l instanceof MultiSensorIcon) { 6839 multiSensors.add((MultiSensorIcon) l); 6840 } 6841 6842 if (l instanceof PositionableLabel) { 6843 if (((PositionableLabel) l).isBackground()) { 6844 backgroundImage.add((PositionableLabel) l); 6845 } else { 6846 labelImage.add((PositionableLabel) l); 6847 } 6848 } 6849 unionToPanelBounds(l.getBounds(new Rectangle())); 6850 setDirty(); 6851 } 6852 6853 /** 6854 * Add a memory label to the Draw Panel 6855 */ 6856 void addMemory() { 6857 String memoryName = leToolBarPanel.textMemoryComboBox.getSelectedItemDisplayName(); 6858 if (memoryName == null) { 6859 memoryName = ""; 6860 } 6861 6862 if (memoryName.isEmpty()) { 6863 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11a"), 6864 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6865 return; 6866 } 6867 MemoryIcon l = new MemoryIcon(" ", this); 6868 l.setMemory(memoryName); 6869 Memory xMemory = l.getMemory(); 6870 6871 if (xMemory != null) { 6872 String uname = xMemory.getDisplayName(); 6873 if (!uname.equals(memoryName)) { 6874 // put the system name in the memory field 6875 leToolBarPanel.textMemoryComboBox.setSelectedItem(xMemory); 6876 } 6877 } 6878 setNextLocation(l); 6879 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 6880 l.setDisplayLevel(Editor.LABELS); 6881 l.setForeground(defaultTextColor); 6882 unionToPanelBounds(l.getBounds()); 6883 try { 6884 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6885 } catch (Positionable.DuplicateIdException e) { 6886 // This should never happen 6887 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6888 } 6889 } 6890 6891 void addGlobalVariable() { 6892 String globalVariableName = leToolBarPanel.textGlobalVariableComboBox.getSelectedItemDisplayName(); 6893 if (globalVariableName == null) { 6894 globalVariableName = ""; 6895 } 6896 6897 if (globalVariableName.isEmpty()) { 6898 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11c"), 6899 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6900 return; 6901 } 6902 GlobalVariableIcon l = new GlobalVariableIcon(" ", this); 6903 l.setGlobalVariable(globalVariableName); 6904 GlobalVariable xGlobalVariable = l.getGlobalVariable(); 6905 6906 if (xGlobalVariable != null) { 6907 String uname = xGlobalVariable.getDisplayName(); 6908 if (!uname.equals(globalVariableName)) { 6909 // put the system name in the memory field 6910 leToolBarPanel.textGlobalVariableComboBox.setSelectedItem(xGlobalVariable); 6911 } 6912 } 6913 setNextLocation(l); 6914 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 6915 l.setDisplayLevel(Editor.LABELS); 6916 l.setForeground(defaultTextColor); 6917 unionToPanelBounds(l.getBounds()); 6918 try { 6919 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6920 } catch (Positionable.DuplicateIdException e) { 6921 // This should never happen 6922 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6923 } 6924 } 6925 6926 void addBlockContents() { 6927 String newName = leToolBarPanel.blockContentsComboBox.getSelectedItemDisplayName(); 6928 if (newName == null) { 6929 newName = ""; 6930 } 6931 6932 if (newName.isEmpty()) { 6933 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11b"), 6934 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 6935 return; 6936 } 6937 BlockContentsIcon l = new BlockContentsIcon(" ", this); 6938 l.setBlock(newName); 6939 Block xMemory = l.getBlock(); 6940 6941 if (xMemory != null) { 6942 String uname = xMemory.getDisplayName(); 6943 if (!uname.equals(newName)) { 6944 // put the system name in the memory field 6945 leToolBarPanel.blockContentsComboBox.setSelectedItem(xMemory); 6946 } 6947 } 6948 setNextLocation(l); 6949 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 6950 l.setDisplayLevel(Editor.LABELS); 6951 l.setForeground(defaultTextColor); 6952 try { 6953 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6954 } catch (Positionable.DuplicateIdException e) { 6955 // This should never happen 6956 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6957 } 6958 } 6959 6960 /** 6961 * Add a Reporter Icon to the panel. 6962 * 6963 * @param reporter the reporter icon to add. 6964 * @param xx the horizontal location. 6965 * @param yy the vertical location. 6966 */ 6967 public void addReporter(@Nonnull Reporter reporter, int xx, int yy) { 6968 ReporterIcon l = new ReporterIcon(this); 6969 l.setReporter(reporter); 6970 l.setLocation(xx, yy); 6971 l.setSize(l.getPreferredSize().width, l.getPreferredSize().height); 6972 l.setDisplayLevel(Editor.LABELS); 6973 unionToPanelBounds(l.getBounds()); 6974 try { 6975 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6976 } catch (Positionable.DuplicateIdException e) { 6977 // This should never happen 6978 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6979 } 6980 } 6981 6982 /** 6983 * Add an icon to the target 6984 */ 6985 void addIcon() { 6986 PositionableLabel l = new PositionableLabel(leToolBarPanel.iconEditor.getIcon(0), this); 6987 setNextLocation(l); 6988 l.setDisplayLevel(Editor.ICONS); 6989 unionToPanelBounds(l.getBounds()); 6990 l.updateSize(); 6991 try { 6992 putItem(l); // note: this calls unionToPanelBounds & setDirty() 6993 } catch (Positionable.DuplicateIdException e) { 6994 // This should never happen 6995 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 6996 } 6997 } 6998 6999 /** 7000 * Add a LogixNG icon to the target 7001 */ 7002 void addLogixNGIcon() { 7003 LogixNGIcon l = new LogixNGIcon(leToolBarPanel.logixngEditor.getIcon(0), this); 7004 setNextLocation(l); 7005 l.setDisplayLevel(Editor.ICONS); 7006 unionToPanelBounds(l.getBounds()); 7007 l.updateSize(); 7008 try { 7009 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7010 } catch (Positionable.DuplicateIdException e) { 7011 // This should never happen 7012 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7013 } 7014 } 7015 7016 /** 7017 * Add a LogixNG icon to the target 7018 */ 7019 void addAudioIcon() { 7020 String audioName = leToolBarPanel.textAudioComboBox.getSelectedItemDisplayName(); 7021 if (audioName == null) { 7022 audioName = ""; 7023 } 7024 7025 if (audioName.isEmpty()) { 7026 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11d"), 7027 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 7028 return; 7029 } 7030 7031 AudioIcon l = new AudioIcon(leToolBarPanel.audioEditor.getIcon(0), this); 7032 l.setAudio(audioName); 7033 Audio xAudio = l.getAudio(); 7034 7035 if (xAudio != null) { 7036 String uname = xAudio.getDisplayName(); 7037 if (!uname.equals(audioName)) { 7038 // put the system name in the memory field 7039 leToolBarPanel.textAudioComboBox.setSelectedItem(xAudio); 7040 } 7041 } 7042 7043 setNextLocation(l); 7044 l.setDisplayLevel(Editor.ICONS); 7045 unionToPanelBounds(l.getBounds()); 7046 l.updateSize(); 7047 try { 7048 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7049 } catch (Positionable.DuplicateIdException e) { 7050 // This should never happen 7051 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7052 } 7053 } 7054 7055 /** 7056 * Add a loco marker to the target 7057 */ 7058 @Override 7059 public LocoIcon addLocoIcon(@Nonnull String name) { 7060 LocoIcon l = new LocoIcon(this); 7061 Point2D pt = windowCenter(); 7062 if (selectionActive) { 7063 pt = MathUtil.midPoint(getSelectionRect()); 7064 } 7065 l.setLocation((int) pt.getX(), (int) pt.getY()); 7066 putLocoIcon(l, name); 7067 l.setPositionable(true); 7068 unionToPanelBounds(l.getBounds()); 7069 return l; 7070 } 7071 7072 @Override 7073 public void putLocoIcon(@Nonnull LocoIcon l, @Nonnull String name) { 7074 super.putLocoIcon(l, name); 7075 markerImage.add(l); 7076 unionToPanelBounds(l.getBounds()); 7077 } 7078 7079 private JFileChooser inputFileChooser = null; 7080 7081 /** 7082 * Add a background image 7083 */ 7084 public void addBackground() { 7085 if (inputFileChooser == null) { 7086 inputFileChooser = new jmri.util.swing.JmriJFileChooser( 7087 String.format("%s%sresources%sicons", 7088 System.getProperty("user.dir"), 7089 File.separator, 7090 File.separator)); 7091 7092 inputFileChooser.setFileFilter(new FileNameExtensionFilter("Graphics Files", "gif", "jpg", "png")); 7093 } 7094 inputFileChooser.rescanCurrentDirectory(); 7095 7096 int retVal = inputFileChooser.showOpenDialog(this); 7097 7098 if (retVal != JFileChooser.APPROVE_OPTION) { 7099 return; // give up if no file selected 7100 } 7101 7102 // NamedIcon icon = new NamedIcon(inputFileChooser.getSelectedFile().getPath(), 7103 // inputFileChooser.getSelectedFile().getPath()); 7104 String name = inputFileChooser.getSelectedFile().getPath(); 7105 7106 // convert to portable path 7107 name = FileUtil.getPortableFilename(name); 7108 7109 // setup icon 7110 PositionableLabel o = super.setUpBackground(name); 7111 backgroundImage.add(o); 7112 unionToPanelBounds(o.getBounds()); 7113 setDirty(); 7114 } 7115 7116 // there is no way to call this; could that 7117 // private boolean remove(@Nonnull Object s) 7118 // is being used instead. 7119 // 7120 ///** 7121 // * Remove a background image from the list of background images 7122 // * 7123 // * @param b PositionableLabel to remove 7124 // */ 7125 //private void removeBackground(@Nonnull PositionableLabel b) { 7126 // if (backgroundImage.contains(b)) { 7127 // backgroundImage.remove(b); 7128 // setDirty(); 7129 // } 7130 //} 7131 /** 7132 * add a layout shape to the list of layout shapes 7133 * 7134 * @param p Point2D where the shape should be 7135 * @return the LayoutShape 7136 */ 7137 @Nonnull 7138 private LayoutShape addLayoutShape(@Nonnull Point2D p) { 7139 // get unique name 7140 String name = finder.uniqueName("S", getLayoutShapes().size() + 1); 7141 7142 // create object 7143 LayoutShape o = new LayoutShape(name, p, this); 7144 layoutShapes.add(o); 7145 unionToPanelBounds(o.getBounds()); 7146 setDirty(); 7147 return o; 7148 } 7149 7150 /** 7151 * Remove a layout shape from the list of layout shapes 7152 * 7153 * @param s the LayoutShape to add 7154 * @return true if added 7155 */ 7156 public boolean removeLayoutShape(@Nonnull LayoutShape s) { 7157 boolean result = false; 7158 if (layoutShapes.contains(s)) { 7159 layoutShapes.remove(s); 7160 setDirty(); 7161 result = true; 7162 redrawPanel(); 7163 } 7164 return result; 7165 } 7166 7167 /** 7168 * Invoke a window to allow you to add a MultiSensor indicator to the target 7169 */ 7170 private int multiLocX; 7171 private int multiLocY; 7172 7173 void startMultiSensor() { 7174 multiLocX = xLoc; 7175 multiLocY = yLoc; 7176 7177 if (leToolBarPanel.multiSensorFrame == null) { 7178 // create a common edit frame 7179 leToolBarPanel.multiSensorFrame = new MultiSensorIconFrame(this); 7180 leToolBarPanel.multiSensorFrame.initComponents(); 7181 leToolBarPanel.multiSensorFrame.pack(); 7182 } 7183 leToolBarPanel.multiSensorFrame.setVisible(true); 7184 } 7185 7186 // Invoked when window has new multi-sensor ready 7187 public void addMultiSensor(@Nonnull MultiSensorIcon l) { 7188 l.setLocation(multiLocX, multiLocY); 7189 try { 7190 putItem(l); // note: this calls unionToPanelBounds & setDirty() 7191 } catch (Positionable.DuplicateIdException e) { 7192 // This should never happen 7193 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 7194 } 7195 leToolBarPanel.multiSensorFrame.dispose(); 7196 leToolBarPanel.multiSensorFrame = null; 7197 } 7198 7199 /** 7200 * Set object location and size for icon and label object as it is created. 7201 * Size comes from the preferredSize; location comes from the fields where 7202 * the user can spec it. 7203 * 7204 * @param obj the positionable object. 7205 */ 7206 @Override 7207 public void setNextLocation(@Nonnull Positionable obj) { 7208 obj.setLocation(xLoc, yLoc); 7209 } 7210 7211 // 7212 // singleton (one per-LayoutEditor) accessors 7213 // 7214 private ConnectivityUtil conTools = null; 7215 7216 @Nonnull 7217 public ConnectivityUtil getConnectivityUtil() { 7218 if (conTools == null) { 7219 conTools = new ConnectivityUtil(this); 7220 } 7221 return conTools; 7222 } 7223 7224 private LayoutEditorTools tools = null; 7225 7226 @Nonnull 7227 public LayoutEditorTools getLETools() { 7228 if (tools == null) { 7229 tools = new LayoutEditorTools(this); 7230 } 7231 return tools; 7232 } 7233 7234 private LayoutEditorAuxTools auxTools = null; 7235 7236 @Override 7237 @Nonnull 7238 public LayoutEditorAuxTools getLEAuxTools() { 7239 if (auxTools == null) { 7240 auxTools = new LayoutEditorAuxTools(this); 7241 } 7242 return auxTools; 7243 } 7244 7245 private LayoutEditorChecks layoutEditorChecks = null; 7246 7247 @Nonnull 7248 public LayoutEditorChecks getLEChecks() { 7249 if (layoutEditorChecks == null) { 7250 layoutEditorChecks = new LayoutEditorChecks(this); 7251 } 7252 return layoutEditorChecks; 7253 } 7254 7255 /** 7256 * Invoked by DeletePanel menu item Validate user intent before deleting 7257 */ 7258 @Override 7259 public boolean deletePanel() { 7260 if (canDeletePanel()) { 7261 // verify deletion 7262 if (!super.deletePanel()) { 7263 return false; // return without deleting if "No" response 7264 } 7265 clearLayoutTracks(); 7266 return true; 7267 } 7268 return false; 7269 } 7270 7271 /** 7272 * Check for conditions that prevent a delete. 7273 * <ul> 7274 * <li>The panel has active edge connector links</li> 7275 * <li>The panel is used by EntryExit</li> 7276 * </ul> 7277 * @return true if ok to delete 7278 */ 7279 public boolean canDeletePanel() { 7280 var messages = new ArrayList<String>(); 7281 7282 var points = getPositionablePoints(); 7283 for (PositionablePoint point : points) { 7284 if (point.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) { 7285 var panelName = point.getLinkedEditorName(); 7286 if (!panelName.isEmpty()) { 7287 messages.add(Bundle.getMessage("ActiveEdgeConnector", point.getId(), point.getLinkedEditorName())); 7288 } 7289 } 7290 } 7291 7292 var entryExitPairs = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class); 7293 if (!entryExitPairs.getNxSource(this).isEmpty()) { 7294 messages.add(Bundle.getMessage("ActiveEntryExit")); 7295 } 7296 7297 if (!messages.isEmpty()) { 7298 StringBuilder msg = new StringBuilder(Bundle.getMessage("PanelRelationshipsError")); 7299 for (String message : messages) { 7300 msg.append(message); 7301 } 7302 JmriJOptionPane.showMessageDialog(null, 7303 msg.toString(), 7304 Bundle.getMessage("ErrorTitle"), // NOI18N 7305 JmriJOptionPane.ERROR_MESSAGE); 7306 } 7307 7308 return messages.isEmpty(); 7309 } 7310 7311 /** 7312 * Control whether target panel items are editable. Does this by invoking 7313 * the {@link Editor#setAllEditable} function of the parent class. This also 7314 * controls the relevant pop-up menu items (which are the primary way that 7315 * items are edited). 7316 * 7317 * @param editable true for editable. 7318 */ 7319 @Override 7320 public void setAllEditable(boolean editable) { 7321 int restoreScroll = _scrollState; 7322 7323 super.setAllEditable(editable); 7324 7325 if (toolBarSide.equals(ToolBarSide.eFLOAT)) { 7326 if (editable) { 7327 createfloatingEditToolBoxFrame(); 7328 createFloatingHelpPanel(); 7329 } else { 7330 deletefloatingEditToolBoxFrame(); 7331 } 7332 } else { 7333 editToolBarContainerPanel.setVisible(editable); 7334 } 7335 setShowHidden(editable); 7336 7337 if (editable) { 7338 setScroll(Editor.SCROLL_BOTH); 7339 _scrollState = restoreScroll; 7340 } else { 7341 setScroll(_scrollState); 7342 } 7343 7344 // these may not be set up yet... 7345 if (helpBarPanel != null) { 7346 if (toolBarSide.equals(ToolBarSide.eFLOAT)) { 7347 if (floatEditHelpPanel != null) { 7348 floatEditHelpPanel.setVisible(isEditable() && getShowHelpBar()); 7349 } 7350 } else { 7351 helpBarPanel.setVisible(editable && getShowHelpBar()); 7352 } 7353 } 7354 awaitingIconChange = false; 7355 editModeCheckBoxMenuItem.setSelected(editable); 7356 redrawPanel(); 7357 } 7358 7359 /** 7360 * Control whether panel items are positionable. Markers are always 7361 * positionable. 7362 * 7363 * @param state true for positionable. 7364 */ 7365 @Override 7366 public void setAllPositionable(boolean state) { 7367 super.setAllPositionable(state); 7368 7369 markerImage.forEach((p) -> p.setPositionable(true)); 7370 } 7371 7372 /** 7373 * Control whether target panel items are controlling layout items. Does 7374 * this by invoke the {@link Positionable#setControlling} function of each 7375 * item on the target panel. This also controls the relevant pop-up menu 7376 * items. 7377 * 7378 * @param state true for controlling. 7379 */ 7380 public void setTurnoutAnimation(boolean state) { 7381 if (animationCheckBoxMenuItem.isSelected() != state) { 7382 animationCheckBoxMenuItem.setSelected(state); 7383 } 7384 7385 if (animatingLayout != state) { 7386 animatingLayout = state; 7387 redrawPanel(); 7388 } 7389 } 7390 7391 public boolean isAnimating() { 7392 return animatingLayout; 7393 } 7394 7395 public boolean getScroll() { 7396 // deprecated but kept to allow opening files 7397 // on version 2.5.1 and earlier 7398 return _scrollState != Editor.SCROLL_NONE; 7399 } 7400 7401// public Color getDefaultBackgroundColor() { 7402// return defaultBackgroundColor; 7403// } 7404 public String getDefaultTrackColor() { 7405 return ColorUtil.colorToColorName(defaultTrackColor); 7406 } 7407 7408 /** 7409 * 7410 * Getter defaultTrackColor. 7411 * 7412 * @return block default color as Color 7413 */ 7414 @Nonnull 7415 public Color getDefaultTrackColorColor() { 7416 return defaultTrackColor; 7417 } 7418 7419 @Nonnull 7420 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7421 public String getDefaultOccupiedTrackColor() { 7422 return ColorUtil.colorToColorName(defaultOccupiedTrackColor); 7423 } 7424 7425 /** 7426 * 7427 * Getter defaultOccupiedTrackColor. 7428 * 7429 * @return block default occupied color as Color 7430 */ 7431 @Nonnull 7432 public Color getDefaultOccupiedTrackColorColor() { 7433 return defaultOccupiedTrackColor; 7434 } 7435 7436 @Nonnull 7437 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7438 public String getDefaultAlternativeTrackColor() { 7439 return ColorUtil.colorToColorName(defaultAlternativeTrackColor); 7440 } 7441 7442 /** 7443 * 7444 * Getter defaultAlternativeTrackColor. 7445 * 7446 * @return block default alternative color as Color 7447 */ 7448 @Nonnull 7449 public Color getDefaultAlternativeTrackColorColor() { 7450 return defaultAlternativeTrackColor; 7451 } 7452 7453 @Nonnull 7454 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7455 public String getDefaultTextColor() { 7456 return ColorUtil.colorToColorName(defaultTextColor); 7457 } 7458 7459 @Nonnull 7460 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7461 public String getTurnoutCircleColor() { 7462 return ColorUtil.colorToColorName(turnoutCircleColor); 7463 } 7464 7465 @Nonnull 7466 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "coloToColorName only returns null if null passed to it") 7467 public String getTurnoutCircleThrownColor() { 7468 return ColorUtil.colorToColorName(turnoutCircleThrownColor); 7469 } 7470 7471 public boolean isTurnoutFillControlCircles() { 7472 return turnoutFillControlCircles; 7473 } 7474 7475 public int getTurnoutCircleSize() { 7476 return turnoutCircleSize; 7477 } 7478 7479 public boolean isTurnoutDrawUnselectedLeg() { 7480 return turnoutDrawUnselectedLeg; 7481 } 7482 7483 public String getLayoutName() { 7484 return layoutName; 7485 } 7486 7487 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7488 public boolean getShowHelpBar() { 7489 return showHelpBar; 7490 } 7491 7492 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7493 public boolean getDrawGrid() { 7494 return drawGrid; 7495 } 7496 7497 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7498 public boolean getSnapOnAdd() { 7499 return snapToGridOnAdd; 7500 } 7501 7502 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7503 public boolean getSnapOnMove() { 7504 return snapToGridOnMove; 7505 } 7506 7507 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7508 public boolean getAntialiasingOn() { 7509 return antialiasingOn; 7510 } 7511 7512 public boolean isDrawLayoutTracksLabel() { 7513 return drawLayoutTracksLabel; 7514 } 7515 7516 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7517 public boolean getHighlightSelectedBlock() { 7518 return highlightSelectedBlockFlag; 7519 } 7520 7521 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7522 public boolean getTurnoutCircles() { 7523 return turnoutCirclesWithoutEditMode; 7524 } 7525 7526 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7527 public boolean getTooltipsNotEdit() { 7528 return tooltipsWithoutEditMode; 7529 } 7530 7531 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7532 public boolean getTooltipsInEdit() { 7533 return tooltipsInEditMode; 7534 } 7535 7536 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 7537 public boolean getAutoBlockAssignment() { 7538 return autoAssignBlocks; 7539 } 7540 7541 public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight) { 7542 setLayoutDimensions(windowWidth, windowHeight, windowX, windowY, panelWidth, panelHeight, false); 7543 } 7544 7545 public void setLayoutDimensions(int windowWidth, int windowHeight, int windowX, int windowY, int panelWidth, int panelHeight, boolean merge) { 7546 7547 gContext.setUpperLeftX(windowX); 7548 gContext.setUpperLeftY(windowY); 7549 setLocation(gContext.getUpperLeftX(), gContext.getUpperLeftY()); 7550 7551 gContext.setWindowWidth(windowWidth); 7552 gContext.setWindowHeight(windowHeight); 7553 setSize(windowWidth, windowHeight); 7554 7555 Rectangle2D panelBounds = new Rectangle2D.Double(0.0, 0.0, panelWidth, panelHeight); 7556 7557 if (merge) { 7558 panelBounds.add(calculateMinimumLayoutBounds()); 7559 } 7560 setPanelBounds(panelBounds); 7561 } 7562 7563 @Nonnull 7564 public Rectangle2D getPanelBounds() { 7565 return new Rectangle2D.Double(0.0, 0.0, gContext.getLayoutWidth(), gContext.getLayoutHeight()); 7566 } 7567 7568 public void setPanelBounds(@Nonnull Rectangle2D newBounds) { 7569 // don't let origin go negative 7570 newBounds = newBounds.createIntersection(MathUtil.zeroToInfinityRectangle2D); 7571 7572 if (!getPanelBounds().equals(newBounds)) { 7573 gContext.setLayoutWidth((int) newBounds.getWidth()); 7574 gContext.setLayoutHeight((int) newBounds.getHeight()); 7575 resetTargetSize(); 7576 } 7577 log.debug("setPanelBounds(({})", newBounds); 7578 } 7579 7580 private void resetTargetSize() { 7581 int newTargetWidth = (int) (gContext.getLayoutWidth() * getZoom()); 7582 int newTargetHeight = (int) (gContext.getLayoutHeight() * getZoom()); 7583 7584 Dimension targetPanelSize = getTargetPanelSize(); 7585 int oldTargetWidth = (int) targetPanelSize.getWidth(); 7586 int oldTargetHeight = (int) targetPanelSize.getHeight(); 7587 7588 if ((newTargetWidth != oldTargetWidth) || (newTargetHeight != oldTargetHeight)) { 7589 setTargetPanelSize(newTargetWidth, newTargetHeight); 7590 adjustScrollBars(); 7591 } 7592 } 7593 7594 // this will grow the panel bounds based on items added to the layout 7595 @Nonnull 7596 public Rectangle2D unionToPanelBounds(@Nonnull Rectangle2D bounds) { 7597 Rectangle2D result = getPanelBounds(); 7598 7599 // make room to expand 7600 Rectangle2D b = MathUtil.inset(bounds, gContext.getGridSize() * gContext.getGridSize2nd() / -2.0); 7601 7602 // don't let origin go negative 7603 b = b.createIntersection(MathUtil.zeroToInfinityRectangle2D); 7604 7605 result.add(b); 7606 7607 setPanelBounds(result); 7608 return result; 7609 } 7610 7611 /** 7612 * @param color value to set the default track color to. 7613 */ 7614 public void setDefaultTrackColor(@Nonnull Color color) { 7615 defaultTrackColor = color; 7616 JmriColorChooser.addRecentColor(color); 7617 } 7618 7619 /** 7620 * @param color value to set the default occupied track color to. 7621 */ 7622 public void setDefaultOccupiedTrackColor(@Nonnull Color color) { 7623 defaultOccupiedTrackColor = color; 7624 JmriColorChooser.addRecentColor(color); 7625 } 7626 7627 /** 7628 * @param color value to set the default alternate track color to. 7629 */ 7630 public void setDefaultAlternativeTrackColor(@Nonnull Color color) { 7631 defaultAlternativeTrackColor = color; 7632 JmriColorChooser.addRecentColor(color); 7633 } 7634 7635 /** 7636 * @param color new color for turnout circle. 7637 */ 7638 public void setTurnoutCircleColor(@CheckForNull Color color) { 7639 if (color == null) { 7640 turnoutCircleColor = getDefaultTrackColorColor(); 7641 } else { 7642 turnoutCircleColor = color; 7643 JmriColorChooser.addRecentColor(color); 7644 } 7645 } 7646 7647 /** 7648 * @param color new color for turnout circle. 7649 */ 7650 public void setTurnoutCircleThrownColor(@CheckForNull Color color) { 7651 if (color == null) { 7652 turnoutCircleThrownColor = getDefaultTrackColorColor(); 7653 } else { 7654 turnoutCircleThrownColor = color; 7655 JmriColorChooser.addRecentColor(color); 7656 } 7657 } 7658 7659 /** 7660 * Should only be invoked on the GUI (Swing) thread. 7661 * 7662 * @param state true to fill in turnout control circles, else false. 7663 */ 7664 @InvokeOnGuiThread 7665 public void setTurnoutFillControlCircles(boolean state) { 7666 if (turnoutFillControlCircles != state) { 7667 turnoutFillControlCircles = state; 7668 turnoutFillControlCirclesCheckBoxMenuItem.setSelected(turnoutFillControlCircles); 7669 } 7670 } 7671 7672 public void setTurnoutCircleSize(int size) { 7673 // this is an int 7674 turnoutCircleSize = size; 7675 7676 // these are doubles 7677 circleRadius = SIZE * size; 7678 circleDiameter = 2.0 * circleRadius; 7679 7680 setOptionMenuTurnoutCircleSize(); 7681 } 7682 7683 /** 7684 * Should only be invoked on the GUI (Swing) thread. 7685 * 7686 * @param state true to draw unselected legs, else false. 7687 */ 7688 @InvokeOnGuiThread 7689 public void setTurnoutDrawUnselectedLeg(boolean state) { 7690 if (turnoutDrawUnselectedLeg != state) { 7691 turnoutDrawUnselectedLeg = state; 7692 turnoutDrawUnselectedLegCheckBoxMenuItem.setSelected(turnoutDrawUnselectedLeg); 7693 } 7694 } 7695 7696 /** 7697 * @param color value to set the default text color to. 7698 */ 7699 public void setDefaultTextColor(@Nonnull Color color) { 7700 defaultTextColor = color; 7701 JmriColorChooser.addRecentColor(color); 7702 } 7703 7704 /** 7705 * @param color value to set the panel background to. 7706 */ 7707 public void setDefaultBackgroundColor(@Nonnull Color color) { 7708 defaultBackgroundColor = color; 7709 JmriColorChooser.addRecentColor(color); 7710 } 7711 7712 public void setLayoutName(@Nonnull String name) { 7713 layoutName = name; 7714 } 7715 7716 /** 7717 * Should only be invoked on the GUI (Swing) thread. 7718 * 7719 * @param state true to show the help bar, else false. 7720 */ 7721 @InvokeOnGuiThread // due to the setSelected call on a possibly-visible item 7722 public void setShowHelpBar(boolean state) { 7723 if (showHelpBar != state) { 7724 showHelpBar = state; 7725 7726 // these may not be set up yet... 7727 if (showHelpCheckBoxMenuItem != null) { 7728 showHelpCheckBoxMenuItem.setSelected(showHelpBar); 7729 } 7730 7731 if (toolBarSide.equals(ToolBarSide.eFLOAT)) { 7732 if (floatEditHelpPanel != null) { 7733 floatEditHelpPanel.setVisible(isEditable() && showHelpBar); 7734 } 7735 } else { 7736 if (helpBarPanel != null) { 7737 helpBarPanel.setVisible(isEditable() && showHelpBar); 7738 7739 } 7740 } 7741 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".showHelpBar", showHelpBar)); 7742 } 7743 } 7744 7745 /** 7746 * Should only be invoked on the GUI (Swing) thread. 7747 * 7748 * @param state true to show the draw grid, else false. 7749 */ 7750 @InvokeOnGuiThread 7751 public void setDrawGrid(boolean state) { 7752 if (drawGrid != state) { 7753 drawGrid = state; 7754 showGridCheckBoxMenuItem.setSelected(drawGrid); 7755 } 7756 } 7757 7758 /** 7759 * Should only be invoked on the GUI (Swing) thread. 7760 * 7761 * @param state true to set snap to grid on add, else false. 7762 */ 7763 @InvokeOnGuiThread 7764 public void setSnapOnAdd(boolean state) { 7765 if (snapToGridOnAdd != state) { 7766 snapToGridOnAdd = state; 7767 snapToGridOnAddCheckBoxMenuItem.setSelected(snapToGridOnAdd); 7768 } 7769 } 7770 7771 /** 7772 * Should only be invoked on the GUI (Swing) thread. 7773 * 7774 * @param state true to set snap on move, else false. 7775 */ 7776 @InvokeOnGuiThread 7777 public void setSnapOnMove(boolean state) { 7778 if (snapToGridOnMove != state) { 7779 snapToGridOnMove = state; 7780 snapToGridOnMoveCheckBoxMenuItem.setSelected(snapToGridOnMove); 7781 } 7782 } 7783 7784 /** 7785 * Should only be invoked on the GUI (Swing) thread. 7786 * 7787 * @param state true to set anti-aliasing flag on, else false. 7788 */ 7789 @InvokeOnGuiThread 7790 public void setAntialiasingOn(boolean state) { 7791 if (antialiasingOn != state) { 7792 antialiasingOn = state; 7793 7794 // this may not be set up yet... 7795 if (antialiasingOnCheckBoxMenuItem != null) { 7796 antialiasingOnCheckBoxMenuItem.setSelected(antialiasingOn); 7797 7798 } 7799 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".antialiasingOn", antialiasingOn)); 7800 } 7801 } 7802 7803 /** 7804 * 7805 * @param state true to set anti-aliasing flag on, else false. 7806 */ 7807 public void setDrawLayoutTracksLabel(boolean state) { 7808 if (drawLayoutTracksLabel != state) { 7809 drawLayoutTracksLabel = state; 7810 7811 // this may not be set up yet... 7812 if (drawLayoutTracksLabelCheckBoxMenuItem != null) { 7813 drawLayoutTracksLabelCheckBoxMenuItem.setSelected(drawLayoutTracksLabel); 7814 7815 } 7816 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".drawLayoutTracksLabel", drawLayoutTracksLabel)); 7817 } 7818 } 7819 7820 // enable/disable using the "Extra" color to highlight the selected block 7821 public void setHighlightSelectedBlock(boolean state) { 7822 if (highlightSelectedBlockFlag != state) { 7823 highlightSelectedBlockFlag = state; 7824 7825 // this may not be set up yet... 7826 if (leToolBarPanel.highlightBlockCheckBox != null) { 7827 leToolBarPanel.highlightBlockCheckBox.setSelected(highlightSelectedBlockFlag); 7828 7829 } 7830 7831 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> prefsMgr.setSimplePreferenceState(getWindowFrameRef() + ".highlightSelectedBlock", highlightSelectedBlockFlag)); 7832 7833 // thread this so it won't break the AppVeyor checks 7834 ThreadingUtil.newThread(() -> { 7835 if (highlightSelectedBlockFlag) { 7836 // use the "Extra" color to highlight the selected block 7837 if (!highlightBlockInComboBox(leToolBarPanel.blockIDComboBox)) { 7838 highlightBlockInComboBox(leToolBarPanel.blockContentsComboBox); 7839 } 7840 } else { 7841 // undo using the "Extra" color to highlight the selected block 7842 Block block = leToolBarPanel.blockIDComboBox.getSelectedItem(); 7843 highlightBlock(null); 7844 leToolBarPanel.blockIDComboBox.setSelectedItem(block); 7845 } 7846 }).start(); 7847 } 7848 } 7849 7850 // 7851 // highlight the block selected by the specified combo Box 7852 // 7853 public boolean highlightBlockInComboBox(@Nonnull NamedBeanComboBox<Block> inComboBox) { 7854 return highlightBlock(inComboBox.getSelectedItem()); 7855 } 7856 7857 /** 7858 * highlight the specified block 7859 * 7860 * @param inBlock the block 7861 * @return true if block was highlighted 7862 */ 7863 public boolean highlightBlock(@CheckForNull Block inBlock) { 7864 boolean result = false; // assume failure (pessimist!) 7865 7866 if (leToolBarPanel.blockIDComboBox.getSelectedItem() != inBlock) { 7867 leToolBarPanel.blockIDComboBox.setSelectedItem(inBlock); 7868 } 7869 7870 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class 7871 ); 7872 Set<Block> l = leToolBarPanel.blockIDComboBox.getManager().getNamedBeanSet(); 7873 for (Block b : l) { 7874 LayoutBlock lb = lbm.getLayoutBlock(b); 7875 if (lb != null) { 7876 boolean enable = ((inBlock != null) && b.equals(inBlock)); 7877 lb.setUseExtraColor(enable); 7878 result |= enable; 7879 } 7880 } 7881 return result; 7882 } 7883 7884 /** 7885 * highlight the specified layout block 7886 * 7887 * @param inLayoutBlock the layout block 7888 * @return true if layout block was highlighted 7889 */ 7890 public boolean highlightLayoutBlock(@Nonnull LayoutBlock inLayoutBlock) { 7891 return highlightBlock(inLayoutBlock.getBlock()); 7892 } 7893 7894 public void setTurnoutCircles(boolean state) { 7895 if (turnoutCirclesWithoutEditMode != state) { 7896 turnoutCirclesWithoutEditMode = state; 7897 if (turnoutCirclesOnCheckBoxMenuItem != null) { 7898 turnoutCirclesOnCheckBoxMenuItem.setSelected(turnoutCirclesWithoutEditMode); 7899 } 7900 } 7901 } 7902 7903 public void setAutoBlockAssignment(boolean boo) { 7904 if (autoAssignBlocks != boo) { 7905 autoAssignBlocks = boo; 7906 if (autoAssignBlocksCheckBoxMenuItem != null) { 7907 autoAssignBlocksCheckBoxMenuItem.setSelected(autoAssignBlocks); 7908 } 7909 } 7910 } 7911 7912 public void setTooltipsNotEdit(boolean state) { 7913 if (tooltipsWithoutEditMode != state) { 7914 tooltipsWithoutEditMode = state; 7915 setTooltipSubMenu(); 7916 setTooltipsAlwaysOrNever(); 7917 } 7918 } 7919 7920 public void setTooltipsInEdit(boolean state) { 7921 if (tooltipsInEditMode != state) { 7922 tooltipsInEditMode = state; 7923 setTooltipSubMenu(); 7924 setTooltipsAlwaysOrNever(); 7925 } 7926 } 7927 7928 private void setTooltipsAlwaysOrNever() { 7929 tooltipsAlwaysOrNever = ((tooltipsInEditMode && tooltipsWithoutEditMode) || 7930 (!tooltipsInEditMode && !tooltipsWithoutEditMode)); 7931 } 7932 7933 private void setTooltipSubMenu() { 7934 if (tooltipNoneMenuItem != null) { 7935 tooltipNoneMenuItem.setSelected((!tooltipsInEditMode) && (!tooltipsWithoutEditMode)); 7936 tooltipAlwaysMenuItem.setSelected((tooltipsInEditMode) && (tooltipsWithoutEditMode)); 7937 tooltipInEditMenuItem.setSelected((tooltipsInEditMode) && (!tooltipsWithoutEditMode)); 7938 tooltipNotInEditMenuItem.setSelected((!tooltipsInEditMode) && (tooltipsWithoutEditMode)); 7939 } 7940 } 7941 7942 // accessor routines for turnout size parameters 7943 public void setTurnoutBX(double bx) { 7944 turnoutBX = bx; 7945 setDirty(); 7946 } 7947 7948 public double getTurnoutBX() { 7949 return turnoutBX; 7950 } 7951 7952 public void setTurnoutCX(double cx) { 7953 turnoutCX = cx; 7954 setDirty(); 7955 } 7956 7957 public double getTurnoutCX() { 7958 return turnoutCX; 7959 } 7960 7961 public void setTurnoutWid(double wid) { 7962 turnoutWid = wid; 7963 setDirty(); 7964 } 7965 7966 public double getTurnoutWid() { 7967 return turnoutWid; 7968 } 7969 7970 public void setXOverLong(double lg) { 7971 xOverLong = lg; 7972 setDirty(); 7973 } 7974 7975 public double getXOverLong() { 7976 return xOverLong; 7977 } 7978 7979 public void setXOverHWid(double hwid) { 7980 xOverHWid = hwid; 7981 setDirty(); 7982 } 7983 7984 public double getXOverHWid() { 7985 return xOverHWid; 7986 } 7987 7988 public void setXOverShort(double sh) { 7989 xOverShort = sh; 7990 setDirty(); 7991 } 7992 7993 public double getXOverShort() { 7994 return xOverShort; 7995 } 7996 7997 // reset turnout sizes to program defaults 7998 private void resetTurnoutSize() { 7999 turnoutBX = LayoutTurnout.turnoutBXDefault; 8000 turnoutCX = LayoutTurnout.turnoutCXDefault; 8001 turnoutWid = LayoutTurnout.turnoutWidDefault; 8002 xOverLong = LayoutTurnout.xOverLongDefault; 8003 xOverHWid = LayoutTurnout.xOverHWidDefault; 8004 xOverShort = LayoutTurnout.xOverShortDefault; 8005 setDirty(); 8006 } 8007 8008 public void setDirectTurnoutControl(boolean boo) { 8009 useDirectTurnoutControl = boo; 8010 useDirectTurnoutControlCheckBoxMenuItem.setSelected(useDirectTurnoutControl); 8011 } 8012 8013 // TODO: Java standard pattern for boolean getters is "isShowHelpBar()" 8014 public boolean getDirectTurnoutControl() { 8015 return useDirectTurnoutControl; 8016 } 8017 8018 // final initialization routine for loading a LayoutEditor 8019 public void setConnections() { 8020 getLayoutTracks().forEach((lt) -> lt.setObjects(this)); 8021 getLEAuxTools().initializeBlockConnectivity(); 8022 log.debug("Initializing Block Connectivity for {}", getLayoutName()); 8023 8024 // reset the panel changed bit 8025 resetDirty(); 8026 } 8027 8028 // these are convenience methods to return rectangles 8029 // to use when (hit point-in-rect testing 8030 // 8031 // compute the control point rect at inPoint 8032 public @Nonnull 8033 Rectangle2D layoutEditorControlRectAt(@Nonnull Point2D inPoint) { 8034 return new Rectangle2D.Double(inPoint.getX() - SIZE, 8035 inPoint.getY() - SIZE, SIZE2, SIZE2); 8036 } 8037 8038 // compute the turnout circle control rect at inPoint 8039 public @Nonnull 8040 Rectangle2D layoutEditorControlCircleRectAt(@Nonnull Point2D inPoint) { 8041 return new Rectangle2D.Double(inPoint.getX() - circleRadius, 8042 inPoint.getY() - circleRadius, circleDiameter, circleDiameter); 8043 } 8044 8045 /** 8046 * Special internal class to allow drawing of layout to a JLayeredPane This 8047 * is the 'target' pane where the layout is displayed 8048 */ 8049 @Override 8050 public void paintTargetPanel(@Nonnull Graphics g) { 8051 // Nothing to do here 8052 // All drawing has been moved into LayoutEditorComponent 8053 // which calls draw. 8054 // This is so the layout is drawn at level three 8055 // (above or below the Positionables) 8056 } 8057 8058 // get selection rectangle 8059 @Nonnull 8060 public Rectangle2D getSelectionRect() { 8061 double selX = Math.min(selectionX, selectionX + selectionWidth); 8062 double selY = Math.min(selectionY, selectionY + selectionHeight); 8063 return new Rectangle2D.Double(selX, selY, 8064 Math.abs(selectionWidth), Math.abs(selectionHeight)); 8065 } 8066 8067 // set selection rectangle 8068 public void setSelectionRect(@Nonnull Rectangle2D selectionRect) { 8069 // selectionRect = selectionRect.createIntersection(MathUtil.zeroToInfinityRectangle2D); 8070 selectionX = selectionRect.getX(); 8071 selectionY = selectionRect.getY(); 8072 selectionWidth = selectionRect.getWidth(); 8073 selectionHeight = selectionRect.getHeight(); 8074 8075 // There's already code in the super class (Editor) to draw 8076 // the selection rect... We just have to set _selectRect 8077 _selectRect = MathUtil.rectangle2DToRectangle(selectionRect); 8078 8079 selectionRect = MathUtil.scale(selectionRect, getZoom()); 8080 8081 JComponent targetPanel = getTargetPanel(); 8082 Rectangle targetRect = targetPanel.getVisibleRect(); 8083 // this will make it the size of the targetRect 8084 // (effectively centering it onscreen) 8085 Rectangle2D selRect2D = MathUtil.inset(selectionRect, 8086 (selectionRect.getWidth() - targetRect.getWidth()) / 2.0, 8087 (selectionRect.getHeight() - targetRect.getHeight()) / 2.0); 8088 // don't let the origin go negative 8089 selRect2D = selRect2D.createIntersection(MathUtil.zeroToInfinityRectangle2D); 8090 Rectangle selRect = MathUtil.rectangle2DToRectangle(selRect2D); 8091 if (!targetRect.contains(selRect)) { 8092 targetPanel.scrollRectToVisible(selRect); 8093 } 8094 8095 clearSelectionGroups(); 8096 selectionActive = true; 8097 createSelectionGroups(); 8098 // redrawPanel(); // createSelectionGroups already calls this 8099 } 8100 8101 public void setSelectRect(Rectangle rectangle) { 8102 _selectRect = rectangle; 8103 } 8104 8105 /* 8106 // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class 8107 public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<LayoutTrack> layoutTrackClass) { 8108 return getLayoutTracks().stream() 8109 .filter(item -> item instanceof PositionablePoint) 8110 .filter(layoutTrackClass::isInstance) 8111 //.map(layoutTrackClass::cast) // TODO: Do we need this? if not dead-code-strip 8112 .collect(Collectors.toList()); 8113 } 8114 8115 // TODO: This compiles but I can't get the syntax correct to pass the array of (sub-)classes 8116 public List<LayoutTrack> getLayoutTracksOfClasses(@Nonnull List<Class<? extends LayoutTrack>> layoutTrackClasses) { 8117 return getLayoutTracks().stream() 8118 .filter(o -> layoutTrackClasses.contains(o.getClass())) 8119 .collect(Collectors.toList()); 8120 } 8121 8122 // TODO: This compiles but I can't get the syntax correct to pass the (sub-)class 8123 public List<LayoutTrack> getLayoutTracksOfClass(@Nonnull Class<? extends LayoutTrack> layoutTrackClass) { 8124 return getLayoutTracksOfClasses(new ArrayList<>(Arrays.asList(layoutTrackClass))); 8125 } 8126 8127 public List<PositionablePoint> getPositionablePoints() { 8128 return getLayoutTracksOfClass(PositionablePoint); 8129 } 8130 */ 8131 @Override 8132 public @Nonnull 8133 Stream<LayoutTrack> getLayoutTracksOfClass(Class<? extends LayoutTrack> layoutTrackClass) { 8134 return getLayoutTracks().stream() 8135 .filter(layoutTrackClass::isInstance) 8136 .map(layoutTrackClass::cast); 8137 } 8138 8139 @Override 8140 public @Nonnull 8141 Stream<LayoutTrackView> getLayoutTrackViewsOfClass(Class<? extends LayoutTrackView> layoutTrackViewClass) { 8142 return getLayoutTrackViews().stream() 8143 .filter(layoutTrackViewClass::isInstance) 8144 .map(layoutTrackViewClass::cast); 8145 } 8146 8147 @Override 8148 public @Nonnull 8149 List<PositionablePointView> getPositionablePointViews() { 8150 return getLayoutTrackViewsOfClass(PositionablePointView.class) 8151 .map(PositionablePointView.class::cast) 8152 .collect(Collectors.toCollection(ArrayList::new)); 8153 } 8154 8155 @Override 8156 public @Nonnull 8157 List<PositionablePoint> getPositionablePoints() { 8158 return getLayoutTracksOfClass(PositionablePoint.class) 8159 .map(PositionablePoint.class::cast) 8160 .collect(Collectors.toCollection(ArrayList::new)); 8161 } 8162 8163 public @Nonnull 8164 List<LayoutSlipView> getLayoutSlipViews() { 8165 return getLayoutTrackViewsOfClass(LayoutSlipView.class) 8166 .map(LayoutSlipView.class::cast) 8167 .collect(Collectors.toCollection(ArrayList::new)); 8168 } 8169 8170 @Override 8171 public @Nonnull 8172 List<LayoutSlip> getLayoutSlips() { 8173 return getLayoutTracksOfClass(LayoutSlip.class) 8174 .map(LayoutSlip.class::cast) 8175 .collect(Collectors.toCollection(ArrayList::new)); 8176 } 8177 8178 @Override 8179 public @Nonnull 8180 List<TrackSegmentView> getTrackSegmentViews() { 8181 return getLayoutTrackViewsOfClass(TrackSegmentView.class) 8182 .map(TrackSegmentView.class::cast) 8183 .collect(Collectors.toCollection(ArrayList::new)); 8184 } 8185 8186 @Override 8187 public @Nonnull 8188 List<TrackSegment> getTrackSegments() { 8189 return getLayoutTracksOfClass(TrackSegment.class) 8190 .map(TrackSegment.class::cast) 8191 .collect(Collectors.toCollection(ArrayList::new)); 8192 } 8193 8194 public @Nonnull 8195 List<LayoutTurnoutView> getLayoutTurnoutViews() { // this specifically does not include slips 8196 return getLayoutTrackViews().stream() // next line excludes LayoutSlips 8197 .filter((o) -> (!(o instanceof LayoutSlipView) && (o instanceof LayoutTurnoutView))) 8198 .map(LayoutTurnoutView.class::cast) 8199 .collect(Collectors.toCollection(ArrayList::new)); 8200 } 8201 8202 @Override 8203 public @Nonnull 8204 List<LayoutTurnout> getLayoutTurnouts() { // this specifically does not include slips 8205 return getLayoutTracks().stream() // next line excludes LayoutSlips 8206 .filter((o) -> (!(o instanceof LayoutSlip) && (o instanceof LayoutTurnout))) 8207 .map(LayoutTurnout.class::cast) 8208 .collect(Collectors.toCollection(ArrayList::new)); 8209 } 8210 8211 @Override 8212 public @Nonnull 8213 List<LayoutTurntable> getLayoutTurntables() { 8214 return getLayoutTracksOfClass(LayoutTurntable.class) 8215 .map(LayoutTurntable.class::cast) 8216 .collect(Collectors.toCollection(ArrayList::new)); 8217 } 8218 8219 public @Nonnull 8220 List<LayoutTurntableView> getLayoutTurntableViews() { 8221 return getLayoutTrackViewsOfClass(LayoutTurntableView.class) 8222 .map(LayoutTurntableView.class::cast) 8223 .collect(Collectors.toCollection(ArrayList::new)); 8224 } 8225 8226 @Override 8227 public @Nonnull 8228 List<LevelXing> getLevelXings() { 8229 return getLayoutTracksOfClass(LevelXing.class) 8230 .map(LevelXing.class::cast) 8231 .collect(Collectors.toCollection(ArrayList::new)); 8232 } 8233 8234 @Override 8235 public @Nonnull 8236 List<LevelXingView> getLevelXingViews() { 8237 return getLayoutTrackViewsOfClass(LevelXingView.class) 8238 .map(LevelXingView.class::cast) 8239 .collect(Collectors.toCollection(ArrayList::new)); 8240 } 8241 8242 /** 8243 * Read-only access to the list of LayoutTrack family objects. The returned 8244 * list will throw UnsupportedOperationException if you attempt to modify 8245 * it. 8246 * 8247 * @return unmodifiable copy of layout track list. 8248 */ 8249 @Override 8250 @Nonnull 8251 final public List<LayoutTrack> getLayoutTracks() { 8252 return Collections.unmodifiableList(layoutTrackList); 8253 } 8254 8255 public @Nonnull 8256 List<LayoutTurnoutView> getLayoutTurnoutAndSlipViews() { 8257 return getLayoutTrackViewsOfClass(LayoutTurnoutView.class 8258 ) 8259 .map(LayoutTurnoutView.class::cast) 8260 .collect(Collectors.toCollection(ArrayList::new)); 8261 } 8262 8263 @Override 8264 public @Nonnull 8265 List<LayoutTurnout> getLayoutTurnoutsAndSlips() { 8266 return getLayoutTracksOfClass(LayoutTurnout.class 8267 ) 8268 .map(LayoutTurnout.class::cast) 8269 .collect(Collectors.toCollection(ArrayList::new)); 8270 } 8271 8272 /** 8273 * Read-only access to the list of LayoutTrackView family objects. The 8274 * returned list will throw UnsupportedOperationException if you attempt to 8275 * modify it. 8276 * 8277 * @return unmodifiable copy of track views. 8278 */ 8279 @Override 8280 @Nonnull 8281 final public List<LayoutTrackView> getLayoutTrackViews() { 8282 return Collections.unmodifiableList(layoutTrackViewList); 8283 } 8284 8285 private final List<LayoutTrack> layoutTrackList = new ArrayList<>(); 8286 private final List<LayoutTrackView> layoutTrackViewList = new ArrayList<>(); 8287 private final Map<LayoutTrack, LayoutTrackView> trkToView = new HashMap<>(); 8288 private final Map<LayoutTrackView, LayoutTrack> viewToTrk = new HashMap<>(); 8289 8290 // temporary 8291 @Override 8292 final public LayoutTrackView getLayoutTrackView(LayoutTrack trk) { 8293 LayoutTrackView lv = trkToView.get(trk); 8294 if (lv == null) { 8295 log.warn("No View found for {} class {}", trk, trk.getClass()); 8296 throw new IllegalArgumentException("No View found: " + trk.getClass()); 8297 } 8298 return lv; 8299 } 8300 8301 // temporary 8302 @Override 8303 final public LevelXingView getLevelXingView(LevelXing xing) { 8304 LayoutTrackView lv = trkToView.get(xing); 8305 if (lv == null) { 8306 log.warn("No View found for {} class {}", xing, xing.getClass()); 8307 throw new IllegalArgumentException("No View found: " + xing.getClass()); 8308 } 8309 if (lv instanceof LevelXingView) { 8310 return (LevelXingView) lv; 8311 } else { 8312 log.error("wrong type {} {} found {}", xing, xing.getClass(), lv); 8313 } 8314 throw new IllegalArgumentException("Wrong type: " + xing.getClass()); 8315 } 8316 8317 // temporary 8318 @Override 8319 final public LayoutTurnoutView getLayoutTurnoutView(LayoutTurnout to) { 8320 LayoutTrackView lv = trkToView.get(to); 8321 if (lv == null) { 8322 log.warn("No View found for {} class {}", to, to.getClass()); 8323 throw new IllegalArgumentException("No View found: " + to); 8324 } 8325 if (lv instanceof LayoutTurnoutView) { 8326 return (LayoutTurnoutView) lv; 8327 } else { 8328 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8329 } 8330 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8331 } 8332 8333 // temporary 8334 @Override 8335 final public LayoutTurntableView getLayoutTurntableView(LayoutTurntable to) { 8336 LayoutTrackView lv = trkToView.get(to); 8337 if (lv == null) { 8338 log.warn("No View found for {} class {}", to, to.getClass()); 8339 throw new IllegalArgumentException("No matching View found: " + to); 8340 } 8341 if (lv instanceof LayoutTurntableView) { 8342 return (LayoutTurntableView) lv; 8343 } else { 8344 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8345 } 8346 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8347 } 8348 8349 // temporary 8350 final public LayoutSlipView getLayoutSlipView(LayoutSlip to) { 8351 LayoutTrackView lv = trkToView.get(to); 8352 if (lv == null) { 8353 log.warn("No View found for {} class {}", to, to.getClass()); 8354 throw new IllegalArgumentException("No matching View found: " + to); 8355 } 8356 if (lv instanceof LayoutSlipView) { 8357 return (LayoutSlipView) lv; 8358 } else { 8359 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8360 } 8361 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8362 } 8363 8364 // temporary 8365 @Override 8366 final public TrackSegmentView getTrackSegmentView(TrackSegment to) { 8367 LayoutTrackView lv = trkToView.get(to); 8368 if (lv == null) { 8369 log.warn("No View found for {} class {}", to, to.getClass()); 8370 throw new IllegalArgumentException("No matching View found: " + to); 8371 } 8372 if (lv instanceof TrackSegmentView) { 8373 return (TrackSegmentView) lv; 8374 } else { 8375 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8376 } 8377 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8378 } 8379 8380 // temporary 8381 @Override 8382 final public PositionablePointView getPositionablePointView(PositionablePoint to) { 8383 LayoutTrackView lv = trkToView.get(to); 8384 if (lv == null) { 8385 log.warn("No View found for {} class {}", to, to.getClass()); 8386 throw new IllegalArgumentException("No matching View found: " + to); 8387 } 8388 if (lv instanceof PositionablePointView) { 8389 return (PositionablePointView) lv; 8390 } else { 8391 log.error("wrong type {} {} found {}", to, to.getClass(), lv); 8392 } 8393 throw new IllegalArgumentException("Wrong type: " + to.getClass()); 8394 } 8395 8396 /** 8397 * Add a LayoutTrack and LayoutTrackView to the list of LayoutTrack family 8398 * objects. 8399 * 8400 * @param trk the layout track to add. 8401 */ 8402 @Override 8403 final public void addLayoutTrack(@Nonnull LayoutTrack trk, @Nonnull LayoutTrackView v) { 8404 log.trace("addLayoutTrack {}", trk); 8405 if (layoutTrackList.contains(trk)) { 8406 log.warn("LayoutTrack {} already being maintained", trk.getName()); 8407 } 8408 8409 layoutTrackList.add(trk); 8410 layoutTrackViewList.add(v); 8411 trkToView.put(trk, v); 8412 viewToTrk.put(v, trk); 8413 8414 unionToPanelBounds(v.getBounds()); // temporary - this should probably _not_ be in the topological part 8415 8416 } 8417 8418 /** 8419 * If item present, delete from the list of LayoutTracks and force a dirty 8420 * redraw. 8421 * 8422 * @param trk the layout track to remove and redraw. 8423 * @return true is item was deleted and a redraw done. 8424 */ 8425 final public boolean removeLayoutTrackAndRedraw(@Nonnull LayoutTrack trk) { 8426 log.trace("removeLayoutTrackAndRedraw {}", trk); 8427 if (layoutTrackList.contains(trk)) { 8428 removeLayoutTrack(trk); 8429 setDirty(); 8430 redrawPanel(); 8431 log.trace("removeLayoutTrackAndRedraw present {}", trk); 8432 return true; 8433 } 8434 log.trace("removeLayoutTrackAndRedraw absent {}", trk); 8435 return false; 8436 } 8437 8438 /** 8439 * If item present, delete from the list of LayoutTracks and force a dirty 8440 * redraw. 8441 * 8442 * @param trk the layout track to remove. 8443 */ 8444 @Override 8445 final public void removeLayoutTrack(@Nonnull LayoutTrack trk) { 8446 log.trace("removeLayoutTrack {}", trk); 8447 layoutTrackList.remove(trk); 8448 LayoutTrackView v = trkToView.get(trk); 8449 layoutTrackViewList.remove(v); 8450 trkToView.remove(trk); 8451 viewToTrk.remove(v); 8452 } 8453 8454 /** 8455 * Clear the list of layout tracks. Not intended for general use. 8456 * <p> 8457 */ 8458 private void clearLayoutTracks() { 8459 layoutTrackList.clear(); 8460 layoutTrackViewList.clear(); 8461 trkToView.clear(); 8462 viewToTrk.clear(); 8463 } 8464 8465 @Override 8466 public @Nonnull 8467 List<LayoutShape> getLayoutShapes() { 8468 return layoutShapes; 8469 } 8470 8471 public void sortLayoutShapesByLevel() { 8472 layoutShapes.sort((lhs, rhs) -> { 8473 // -1 == less than, 0 == equal, +1 == greater than 8474 return Integer.signum(lhs.getLevel() - rhs.getLevel()); 8475 }); 8476 } 8477 8478 /** 8479 * {@inheritDoc} 8480 * <p> 8481 * This implementation is temporary, using the on-screen points from the 8482 * LayoutTrackViews via @{link LayoutEditor#getCoords}. 8483 */ 8484 @Override 8485 public int computeDirection(LayoutTrack trk1, HitPointType h1, LayoutTrack trk2, HitPointType h2) { 8486 return Path.computeDirection( 8487 getCoords(trk1, h1), 8488 getCoords(trk2, h2) 8489 ); 8490 } 8491 8492 @Override 8493 public int computeDirectionToCenter(@Nonnull LayoutTrack trk1, @Nonnull HitPointType h1, @Nonnull PositionablePoint p) { 8494 return Path.computeDirection( 8495 getCoords(trk1, h1), 8496 getPositionablePointView(p).getCoordsCenter() 8497 ); 8498 } 8499 8500 @Override 8501 public int computeDirectionFromCenter(@Nonnull PositionablePoint p, @Nonnull LayoutTrack trk1, @Nonnull HitPointType h1) { 8502 return Path.computeDirection( 8503 getPositionablePointView(p).getCoordsCenter(), 8504 getCoords(trk1, h1) 8505 ); 8506 } 8507 8508 @Override 8509 public boolean showAlignPopup(@Nonnull Positionable l) { 8510 return false; 8511 } 8512 8513 @Override 8514 public void showToolTip( 8515 @Nonnull Positionable selection, 8516 @Nonnull JmriMouseEvent event) { 8517 ToolTip tip = selection.getToolTip(); 8518 tip.setLocation(selection.getX() + selection.getWidth() / 2, selection.getY() + selection.getHeight()); 8519 setToolTip(tip); 8520 } 8521 8522 @Override 8523 public void addToPopUpMenu( 8524 @Nonnull NamedBean nb, 8525 @Nonnull JMenuItem item, 8526 int menu) { 8527 if ((nb == null) || (item == null)) { 8528 return; 8529 } 8530 8531 List<?> theList = null; 8532 8533 if (nb instanceof Sensor) { 8534 theList = sensorList; 8535 } else if (nb instanceof SignalHead) { 8536 theList = signalList; 8537 } else if (nb instanceof SignalMast) { 8538 theList = signalMastList; 8539 } else if (nb instanceof Block) { 8540 theList = blockContentsLabelList; 8541 } else if (nb instanceof Memory) { 8542 theList = memoryLabelList; 8543 } else if (nb instanceof GlobalVariable) { 8544 theList = globalVariableLabelList; 8545 } 8546 if (theList != null) { 8547 for (Object o : theList) { 8548 PositionableLabel si = (PositionableLabel) o; 8549 if ((si.getNamedBean() == nb) && (si.getPopupUtility() != null)) { 8550 if (menu != Editor.VIEWPOPUPONLY) { 8551 si.getPopupUtility().addEditPopUpMenu(item); 8552 } 8553 if (menu != Editor.EDITPOPUPONLY) { 8554 si.getPopupUtility().addViewPopUpMenu(item); 8555 } 8556 } 8557 } 8558 } else if (nb instanceof Turnout) { 8559 for (LayoutTurnoutView ltv : getLayoutTurnoutAndSlipViews()) { 8560 if (ltv.getTurnout().equals(nb)) { 8561 if (menu != Editor.VIEWPOPUPONLY) { 8562 ltv.addEditPopUpMenu(item); 8563 } 8564 if (menu != Editor.EDITPOPUPONLY) { 8565 ltv.addViewPopUpMenu(item); 8566 } 8567 } 8568 } 8569 } 8570 } 8571 8572 @Override 8573 public @Nonnull 8574 String toString() { 8575 return String.format("LayoutEditor: %s", getLayoutName()); 8576 } 8577 8578 @Override 8579 public void vetoableChange( 8580 @Nonnull PropertyChangeEvent evt) 8581 throws PropertyVetoException { 8582 NamedBean nb = (NamedBean) evt.getOldValue(); 8583 8584 if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N 8585 StringBuilder message = new StringBuilder(); 8586 message.append(Bundle.getMessage("VetoInUseLayoutEditorHeader", toString())); // NOI18N 8587 message.append("<ul>"); 8588 boolean found = false; 8589 8590 if (nb instanceof SignalHead) { 8591 if (containsSignalHead((SignalHead) nb)) { 8592 found = true; 8593 message.append("<li>"); 8594 message.append(Bundle.getMessage("VetoSignalHeadIconFound")); 8595 message.append("</li>"); 8596 } 8597 LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb); 8598 8599 if (lt != null) { 8600 message.append("<li>"); 8601 message.append(Bundle.getMessage("VetoSignalHeadAssignedToTurnout", lt.getTurnoutName())); 8602 message.append("</li>"); 8603 } 8604 PositionablePoint p = finder.findPositionablePointByBean(nb); 8605 8606 if (p != null) { 8607 message.append("<li>"); 8608 // Need to expand to get the names of blocks 8609 message.append(Bundle.getMessage("VetoSignalHeadAssignedToPoint")); 8610 message.append("</li>"); 8611 } 8612 LevelXing lx = finder.findLevelXingByBean(nb); 8613 8614 if (lx != null) { 8615 message.append("<li>"); 8616 // Need to expand to get the names of blocks 8617 message.append(Bundle.getMessage("VetoSignalHeadAssignedToLevelXing")); 8618 message.append("</li>"); 8619 } 8620 LayoutSlip ls = finder.findLayoutSlipByBean(nb); 8621 8622 if (ls != null) { 8623 message.append("<li>"); 8624 message.append(Bundle.getMessage("VetoSignalHeadAssignedToLayoutSlip", ls.getTurnoutName())); 8625 message.append("</li>"); 8626 } 8627 } else if (nb instanceof Turnout) { 8628 LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb); 8629 8630 if (lt != null) { 8631 found = true; 8632 message.append("<li>"); 8633 message.append(Bundle.getMessage("VetoTurnoutIconFound")); 8634 message.append("</li>"); 8635 } 8636 8637 for (LayoutTurnout t : getLayoutTurnouts()) { 8638 if (t.getLinkedTurnoutName() != null) { 8639 String uname = nb.getUserName(); 8640 8641 if (nb.getSystemName().equals(t.getLinkedTurnoutName()) 8642 || ((uname != null) && uname.equals(t.getLinkedTurnoutName()))) { 8643 found = true; 8644 message.append("<li>"); 8645 message.append(Bundle.getMessage("VetoLinkedTurnout", t.getTurnoutName())); 8646 message.append("</li>"); 8647 } 8648 } 8649 8650 if (nb.equals(t.getSecondTurnout())) { 8651 found = true; 8652 message.append("<li>"); 8653 message.append(Bundle.getMessage("VetoSecondTurnout", t.getTurnoutName())); 8654 message.append("</li>"); 8655 } 8656 } 8657 LayoutSlip ls = finder.findLayoutSlipByBean(nb); 8658 8659 if (ls != null) { 8660 found = true; 8661 message.append("<li>"); 8662 message.append(Bundle.getMessage("VetoSlipIconFound", ls.getDisplayName())); 8663 message.append("</li>"); 8664 } 8665 8666 for (LayoutTurntable lx : getLayoutTurntables()) { 8667 if (lx.isTurnoutControlled()) { 8668 for (int i = 0; i < lx.getNumberRays(); i++) { 8669 if (nb.equals(lx.getRayTurnout(i))) { 8670 found = true; 8671 message.append("<li>"); 8672 message.append(Bundle.getMessage("VetoRayTurntableControl", lx.getId())); 8673 message.append("</li>"); 8674 break; 8675 } 8676 } 8677 } 8678 } 8679 } 8680 8681 if (nb instanceof SignalMast) { 8682 if (containsSignalMast((SignalMast) nb)) { 8683 message.append("<li>"); 8684 message.append("As an Icon"); 8685 message.append("</li>"); 8686 found = true; 8687 } 8688 String foundelsewhere = findBeanUsage(nb); 8689 8690 if (foundelsewhere != null) { 8691 message.append(foundelsewhere); 8692 found = true; 8693 } 8694 } 8695 8696 if (nb instanceof Sensor) { 8697 int count = 0; 8698 8699 for (SensorIcon si : sensorList) { 8700 if (nb.equals(si.getNamedBean())) { 8701 count++; 8702 found = true; 8703 } 8704 } 8705 8706 if (count > 0) { 8707 message.append("<li>"); 8708 message.append(String.format("As an Icon %s times", count)); 8709 message.append("</li>"); 8710 } 8711 String foundelsewhere = findBeanUsage(nb); 8712 8713 if (foundelsewhere != null) { 8714 message.append(foundelsewhere); 8715 found = true; 8716 } 8717 } 8718 8719 if (nb instanceof Memory) { 8720 for (MemoryIcon si : memoryLabelList) { 8721 if (nb.equals(si.getMemory())) { 8722 found = true; 8723 message.append("<li>"); 8724 message.append(Bundle.getMessage("VetoMemoryIconFound")); 8725 message.append("</li>"); 8726 } 8727 } 8728 } 8729 8730 if (nb instanceof GlobalVariable) { 8731 for (GlobalVariableIcon si : globalVariableLabelList) { 8732 if (nb.equals(si.getGlobalVariable())) { 8733 found = true; 8734 message.append("<li>"); 8735 message.append(Bundle.getMessage("VetoGlobalVariableIconFound")); 8736 message.append("</li>"); 8737 } 8738 } 8739 } 8740 8741 if (found) { 8742 message.append("</ul>"); 8743 message.append(Bundle.getMessage("VetoReferencesWillBeRemoved")); // NOI18N 8744 throw new PropertyVetoException(message.toString(), evt); 8745 } 8746 } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N 8747 if (nb instanceof SignalHead) { 8748 removeSignalHead((SignalHead) nb); 8749 removeBeanRefs(nb); 8750 } 8751 8752 if (nb instanceof Turnout) { 8753 LayoutTurnout lt = finder.findLayoutTurnoutByBean(nb); 8754 8755 if (lt != null) { 8756 lt.setTurnout(""); 8757 } 8758 8759 for (LayoutTurnout t : getLayoutTurnouts()) { 8760 if (t.getLinkedTurnoutName() != null) { 8761 if (t.getLinkedTurnoutName().equals(nb.getSystemName()) 8762 || ((nb.getUserName() != null) && t.getLinkedTurnoutName().equals(nb.getUserName()))) { 8763 t.setLinkedTurnoutName(""); 8764 } 8765 } 8766 8767 if (nb.equals(t.getSecondTurnout())) { 8768 t.setSecondTurnout(""); 8769 } 8770 } 8771 8772 for (LayoutSlip sl : getLayoutSlips()) { 8773 if (nb.equals(sl.getTurnout())) { 8774 sl.setTurnout(""); 8775 } 8776 8777 if (nb.equals(sl.getTurnoutB())) { 8778 sl.setTurnoutB(""); 8779 } 8780 } 8781 8782 for (LayoutTurntable lx : getLayoutTurntables()) { 8783 if (lx.isTurnoutControlled()) { 8784 for (int i = 0; i < lx.getNumberRays(); i++) { 8785 if (nb.equals(lx.getRayTurnout(i))) { 8786 lx.setRayTurnout(i, null, NamedBean.UNKNOWN); 8787 } 8788 } 8789 } 8790 } 8791 } 8792 8793 if (nb instanceof SignalMast) { 8794 removeBeanRefs(nb); 8795 8796 if (containsSignalMast((SignalMast) nb)) { 8797 Iterator<SignalMastIcon> icon = signalMastList.iterator(); 8798 8799 while (icon.hasNext()) { 8800 SignalMastIcon i = icon.next(); 8801 8802 if (i.getSignalMast().equals(nb)) { 8803 icon.remove(); 8804 super.removeFromContents(i); 8805 } 8806 } 8807 setDirty(); 8808 redrawPanel(); 8809 } 8810 } 8811 8812 if (nb instanceof Sensor) { 8813 removeBeanRefs(nb); 8814 Iterator<SensorIcon> icon = sensorImage.iterator(); 8815 8816 while (icon.hasNext()) { 8817 SensorIcon i = icon.next(); 8818 8819 if (nb.equals(i.getSensor())) { 8820 icon.remove(); 8821 super.removeFromContents(i); 8822 } 8823 } 8824 setDirty(); 8825 redrawPanel(); 8826 } 8827 8828 if (nb instanceof Memory) { 8829 Iterator<MemoryIcon> icon = memoryLabelList.iterator(); 8830 8831 while (icon.hasNext()) { 8832 MemoryIcon i = icon.next(); 8833 8834 if (nb.equals(i.getMemory())) { 8835 icon.remove(); 8836 super.removeFromContents(i); 8837 } 8838 } 8839 } 8840 8841 if (nb instanceof GlobalVariable) { 8842 Iterator<GlobalVariableIcon> icon = globalVariableLabelList.iterator(); 8843 8844 while (icon.hasNext()) { 8845 GlobalVariableIcon i = icon.next(); 8846 8847 if (nb.equals(i.getGlobalVariable())) { 8848 icon.remove(); 8849 super.removeFromContents(i); 8850 } 8851 } 8852 } 8853 } 8854 } 8855 8856// private void rename(String inFrom, String inTo) { 8857// 8858// } 8859 @Override 8860 public void dispose() { 8861 if (leToolBarPanel.sensorFrame != null) { 8862 leToolBarPanel.sensorFrame.dispose(); 8863 leToolBarPanel.sensorFrame = null; 8864 } 8865 if (leToolBarPanel.signalFrame != null) { 8866 leToolBarPanel.signalFrame.dispose(); 8867 leToolBarPanel.signalFrame = null; 8868 } 8869 if (leToolBarPanel.iconFrame != null) { 8870 leToolBarPanel.iconFrame.dispose(); 8871 leToolBarPanel.iconFrame = null; 8872 } 8873 super.dispose(); 8874 8875 } 8876 8877 // package protected 8878 class TurnoutComboBoxPopupMenuListener implements PopupMenuListener { 8879 8880 private final NamedBeanComboBox<Turnout> comboBox; 8881 private final List<Turnout> currentTurnouts; 8882 8883 public TurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) { 8884 this.comboBox = comboBox; 8885 this.currentTurnouts = currentTurnouts; 8886 } 8887 8888 @Override 8889 public void popupMenuWillBecomeVisible(PopupMenuEvent event) { 8890 // This method is called before the popup menu becomes visible. 8891 log.debug("PopupMenuWillBecomeVisible"); 8892 Set<Turnout> l = new HashSet<>(); 8893 comboBox.getManager().getNamedBeanSet().forEach((turnout) -> { 8894 if (!currentTurnouts.contains(turnout)) { 8895 if (!validatePhysicalTurnout(turnout.getDisplayName(), null)) { 8896 l.add(turnout); 8897 } 8898 } 8899 }); 8900 comboBox.setExcludedItems(l); 8901 } 8902 8903 @Override 8904 public void popupMenuWillBecomeInvisible(PopupMenuEvent event) { 8905 // This method is called before the popup menu becomes invisible 8906 log.debug("PopupMenuWillBecomeInvisible"); 8907 } 8908 8909 @Override 8910 public void popupMenuCanceled(PopupMenuEvent event) { 8911 // This method is called when the popup menu is canceled 8912 log.debug("PopupMenuCanceled"); 8913 } 8914 } 8915 8916 /** 8917 * Create a listener that will exclude turnouts that are present in the 8918 * current panel. 8919 * 8920 * @param comboBox The NamedBeanComboBox that contains the turnout list. 8921 * @return A PopupMenuListener 8922 */ 8923 public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox) { 8924 return new TurnoutComboBoxPopupMenuListener(comboBox, new ArrayList<>()); 8925 } 8926 8927 /** 8928 * Create a listener that will exclude turnouts that are present in the 8929 * current panel. The list of current turnouts are not excluded. 8930 * 8931 * @param comboBox The NamedBeanComboBox that contains the turnout 8932 * list. 8933 * @param currentTurnouts The turnouts to be left in the turnout list. 8934 * @return A PopupMenuListener 8935 */ 8936 public TurnoutComboBoxPopupMenuListener newTurnoutComboBoxPopupMenuListener(NamedBeanComboBox<Turnout> comboBox, List<Turnout> currentTurnouts) { 8937 return new TurnoutComboBoxPopupMenuListener(comboBox, currentTurnouts); 8938 } 8939 8940 List<NamedBeanUsageReport> usageReport; 8941 8942 @Override 8943 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 8944 usageReport = new ArrayList<>(); 8945 if (bean != null) { 8946 usageReport = super.getUsageReport(bean); 8947 8948 // LE Specific checks 8949 // Turnouts 8950 findTurnoutUsage(bean); 8951 8952 // Check A, EB, EC for sensors, masts, heads 8953 findPositionalUsage(bean); 8954 8955 // Level Crossings 8956 findXingWhereUsed(bean); 8957 8958 // Track segments 8959 findSegmentWhereUsed(bean); 8960 } 8961 return usageReport; 8962 } 8963 8964 void findTurnoutUsage(NamedBean bean) { 8965 for (LayoutTurnout turnout : getLayoutTurnoutsAndSlips()) { 8966 String data = getUsageData(turnout); 8967 8968 if (bean.equals(turnout.getTurnout())) { 8969 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout", data)); 8970 } 8971 if (bean.equals(turnout.getSecondTurnout())) { 8972 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnout2", data)); 8973 } 8974 8975 if (isLBLockUsed(bean, turnout.getLayoutBlock())) { 8976 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 8977 } 8978 if (turnout.hasEnteringDoubleTrack()) { 8979 if (isLBLockUsed(bean, turnout.getLayoutBlockB())) { 8980 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 8981 } 8982 if (isLBLockUsed(bean, turnout.getLayoutBlockC())) { 8983 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 8984 } 8985 if (isLBLockUsed(bean, turnout.getLayoutBlockD())) { 8986 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutBlock", data)); 8987 } 8988 } 8989 8990 if (bean.equals(turnout.getSensorA())) { 8991 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 8992 } 8993 if (bean.equals(turnout.getSensorB())) { 8994 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 8995 } 8996 if (bean.equals(turnout.getSensorC())) { 8997 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 8998 } 8999 if (bean.equals(turnout.getSensorD())) { 9000 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSensor", data)); 9001 } 9002 9003 if (bean.equals(turnout.getSignalAMast())) { 9004 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9005 } 9006 if (bean.equals(turnout.getSignalBMast())) { 9007 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9008 } 9009 if (bean.equals(turnout.getSignalCMast())) { 9010 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9011 } 9012 if (bean.equals(turnout.getSignalDMast())) { 9013 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalMast", data)); 9014 } 9015 9016 if (bean.equals(turnout.getSignalA1())) { 9017 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9018 } 9019 if (bean.equals(turnout.getSignalA2())) { 9020 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9021 } 9022 if (bean.equals(turnout.getSignalA3())) { 9023 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9024 } 9025 if (bean.equals(turnout.getSignalB1())) { 9026 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9027 } 9028 if (bean.equals(turnout.getSignalB2())) { 9029 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9030 } 9031 if (bean.equals(turnout.getSignalC1())) { 9032 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9033 } 9034 if (bean.equals(turnout.getSignalC2())) { 9035 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9036 } 9037 if (bean.equals(turnout.getSignalD1())) { 9038 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9039 } 9040 if (bean.equals(turnout.getSignalD2())) { 9041 usageReport.add(new NamedBeanUsageReport("LayoutEditorTurnoutSignalHead", data)); 9042 } 9043 } 9044 } 9045 9046 void findPositionalUsage(NamedBean bean) { 9047 for (PositionablePoint point : getPositionablePoints()) { 9048 String data = getUsageData(point); 9049 if (bean.equals(point.getEastBoundSensor())) { 9050 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data)); 9051 } 9052 if (bean.equals(point.getWestBoundSensor())) { 9053 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSensor", data)); 9054 } 9055 if (bean.equals(point.getEastBoundSignalHead())) { 9056 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data)); 9057 } 9058 if (bean.equals(point.getWestBoundSignalHead())) { 9059 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalHead", data)); 9060 } 9061 if (bean.equals(point.getEastBoundSignalMast())) { 9062 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data)); 9063 } 9064 if (bean.equals(point.getWestBoundSignalMast())) { 9065 usageReport.add(new NamedBeanUsageReport("LayoutEditorPointSignalMast", data)); 9066 } 9067 } 9068 } 9069 9070 void findSegmentWhereUsed(NamedBean bean) { 9071 for (TrackSegment segment : getTrackSegments()) { 9072 if (isLBLockUsed(bean, segment.getLayoutBlock())) { 9073 String data = getUsageData(segment); 9074 usageReport.add(new NamedBeanUsageReport("LayoutEditorSegmentBlock", data)); 9075 } 9076 } 9077 } 9078 9079 void findXingWhereUsed(NamedBean bean) { 9080 for (LevelXing xing : getLevelXings()) { 9081 String data = getUsageData(xing); 9082 if (isLBLockUsed(bean, xing.getLayoutBlockAC())) { 9083 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data)); 9084 } 9085 if (isLBLockUsed(bean, xing.getLayoutBlockBD())) { 9086 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingBlock", data)); 9087 } 9088 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTA)) { 9089 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9090 } 9091 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTB)) { 9092 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9093 } 9094 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTC)) { 9095 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9096 } 9097 if (isUsedInXing(bean, xing, LevelXing.Geometry.POINTD)) { 9098 usageReport.add(new NamedBeanUsageReport("LayoutEditorXingOther", data)); 9099 } 9100 } 9101 } 9102 9103 String getUsageData(LayoutTrack track) { 9104 LayoutTrackView trackView = getLayoutTrackView(track); 9105 Point2D point = trackView.getCoordsCenter(); 9106 if (trackView instanceof TrackSegmentView) { 9107 TrackSegmentView segmentView = (TrackSegmentView) trackView; 9108 point = new Point2D.Double(segmentView.getCentreSegX(), segmentView.getCentreSegY()); 9109 } 9110 return String.format("%s :: x=%d, y=%d", 9111 track.getClass().getSimpleName(), 9112 Math.round(point.getX()), 9113 Math.round(point.getY())); 9114 } 9115 9116 boolean isLBLockUsed(NamedBean bean, LayoutBlock lblock) { 9117 boolean result = false; 9118 if (lblock != null) { 9119 if (bean.equals(lblock.getBlock())) { 9120 result = true; 9121 } 9122 } 9123 return result; 9124 } 9125 9126 boolean isUsedInXing(NamedBean bean, LevelXing xing, LevelXing.Geometry point) { 9127 boolean result = false; 9128 if (bean.equals(xing.getSensor(point))) { 9129 result = true; 9130 } 9131 if (bean.equals(xing.getSignalHead(point))) { 9132 result = true; 9133 } 9134 if (bean.equals(xing.getSignalMast(point))) { 9135 result = true; 9136 } 9137 return result; 9138 } 9139 9140 // initialize logging 9141 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditor.class); 9142}