001package jmri.jmrit.display.layoutEditor; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.awt.geom.*; 006import java.beans.PropertyVetoException; 007import java.util.*; 008import java.util.List; 009 010import javax.annotation.*; 011import javax.swing.*; 012 013import jmri.*; 014import jmri.jmrit.logixng.*; 015import jmri.jmrit.logixng.tools.swing.DeleteBean; 016import jmri.jmrit.logixng.tools.swing.LogixNGEditor; 017import jmri.util.*; 018import jmri.util.swing.JmriJOptionPane; 019import jmri.util.swing.JmriMouseEvent; 020 021/** 022 * MVC View component abstract base for the LayoutTrack hierarchy. 023 * <p> 024 * This contains the display information, including screen geometry, for a 025 * LayoutEditor panel. The geometry/connectivity information is held in 026 * {@link LayoutTrack} subclasses. 027 * <ul> 028 * <li>Position(s) of the screen icons and its parts, typically the center; 029 * scaling and translation; size and bounds 030 * <li>Line colors 031 * <li>Flipped status; drawing details like bezier curve points 032 * <li>Various decorations: arrows, tunnels, bridges 033 * <li>Hidden status 034 * </ul> 035 * 036 * @author Bob Jacobsen Copyright (c) 2020 037 * 038 */ 039abstract public class LayoutTrackView implements InlineLogixNG { 040 041 /** 042 * Constructor method. 043 * 044 * @param track the layout track to view 045 * @param layoutEditor the panel in which to place the view 046 */ 047 public LayoutTrackView(@Nonnull LayoutTrack track, @Nonnull LayoutEditor layoutEditor) { 048 this.layoutTrack = track; 049 this.layoutEditor = layoutEditor; 050 } 051 052 /** 053 * constructor method 054 * 055 * @param track the track to view 056 * @param c display location 057 * @param layoutEditor for reference to tools 058 */ 059 public LayoutTrackView(@Nonnull LayoutTrack track, @Nonnull Point2D c, @Nonnull LayoutEditor layoutEditor) { 060 this.layoutTrack = track; 061 this.layoutEditor = layoutEditor; 062 this.center = c; 063 } 064 065 final private LayoutTrack layoutTrack; 066 067 final protected LayoutEditor layoutEditor; 068 069 private LogixNG _logixNG; 070 private String _logixNG_SystemName; 071 072 private boolean _inEditInlineLogixNGMode = false; 073 private LogixNGEditor _inlineLogixNGEdit; 074 075 // Accessor Methods 076 077 @Nonnull 078 final public String getId() { // temporary Id vs name; is one for the View? 079 return layoutTrack.getId(); 080 } 081 082 @Nonnull 083 final public String getName() { 084 return layoutTrack.getName(); 085 } 086 087 @Nonnull 088 @CheckReturnValue 089 public LayoutEditor getLayoutEditor() { 090 return layoutEditor; 091 } 092 093 final protected void setIdent(@Nonnull String ident) { 094 layoutTrack.setIdent(ident); 095 } 096 097 // temporary accessor? Or is this a long term thing? 098 // @Nonnull temporary until we gigure out if can be null or not 099 public LayoutTrack getLayoutTrack() { 100 return layoutTrack; 101 } 102 103 /** 104 * Set center coordinates 105 * 106 * @return The center coordinates 107 */ 108 public Point2D getCoordsCenter() { // should be final for efficiency, temporary not to allow redirction overrides. 109 return center; 110 } 111 112 /** 113 * Set center coordinates. 114 * <p> 115 * Some subtypes may reimplement this is "center" is a more complicated 116 * idea, i.e. for Bezier curves 117 * 118 * @param p the coordinates to set 119 */ 120 public void setCoordsCenter(@Nonnull Point2D p) { // temporary = want to make protected after migration 121 center = p; 122 } 123 124 private Point2D center = new Point2D.Double(50.0, 50.0); 125 126 /** 127 * @return true if this track segment has decorations 128 */ 129 public boolean hasDecorations() { 130 return false; 131 } 132 133 /** 134 * Get current decorations 135 * 136 * @return the decorations 137 */ 138 public Map<String, String> getDecorations() { 139 return decorations; 140 } 141 142 /** 143 * Set new decorations 144 * 145 * This is a complete replacement of the decorations, not an appending. 146 * 147 * @param decorations A map from strings ("arrow", "bridge", "bumper",..) to 148 * specific value strings ("single", "entry;right", ), 149 * perhaps including multiple values separated by 150 * semicolons. 151 */ 152 public void setDecorations(@Nonnull Map<String, String> decorations) { 153 this.decorations = decorations; 154 } 155 protected Map<String, String> decorations = null; 156 157 /** 158 * convenience method for accessing... 159 * 160 * @return the layout editor's toolbar panel 161 */ 162 @Nonnull 163 final public LayoutEditorToolBarPanel getLayoutEditorToolBarPanel() { 164 return layoutEditor.getLayoutEditorToolBarPanel(); 165 } 166 167 // these are convenience methods to return circles & rectangle used to draw onscreen 168 // 169 // compute the control point rect at inPoint; use the turnout circle size 170 final public Ellipse2D trackEditControlCircleAt(@Nonnull Point2D inPoint) { 171 return trackControlCircleAt(inPoint); 172 } 173 174 // compute the turnout circle at inPoint (used for drawing) 175 final public Ellipse2D trackControlCircleAt(@Nonnull Point2D inPoint) { 176 return new Ellipse2D.Double(inPoint.getX() - layoutEditor.circleRadius, 177 inPoint.getY() - layoutEditor.circleRadius, 178 layoutEditor.circleDiameter, layoutEditor.circleDiameter); 179 } 180 181 // compute the turnout circle control rect at inPoint 182 final public Rectangle2D trackControlCircleRectAt(@Nonnull Point2D inPoint) { 183 return new Rectangle2D.Double(inPoint.getX() - layoutEditor.circleRadius, 184 inPoint.getY() - layoutEditor.circleRadius, 185 layoutEditor.circleDiameter, layoutEditor.circleDiameter); 186 } 187 188 final protected Color getColorForTrackBlock( 189 @CheckForNull LayoutBlock layoutBlock, boolean forceBlockTrackColor) { 190 Color result = ColorUtil.CLEAR; // transparent 191 if (layoutBlock != null) { 192 if (forceBlockTrackColor) { 193 result = layoutBlock.getBlockTrackColor(); 194 } else { 195 result = layoutBlock.getBlockColor(); 196 } 197 } 198 return result; 199 } 200 201 // optional parameter forceTrack = false 202 final protected Color getColorForTrackBlock(@CheckForNull LayoutBlock lb) { 203 return getColorForTrackBlock(lb, false); 204 } 205 206 final protected Color setColorForTrackBlock(Graphics2D g2, 207 @CheckForNull LayoutBlock layoutBlock, boolean forceBlockTrackColor) { 208 Color result = getColorForTrackBlock(layoutBlock, forceBlockTrackColor); 209 g2.setColor(result); 210 return result; 211 } 212 213 // optional parameter forceTrack = false 214 final protected Color setColorForTrackBlock(Graphics2D g2, @CheckForNull LayoutBlock lb) { 215 return setColorForTrackBlock(g2, lb, false); 216 } 217 218 /** 219 * draw one line (Ballast, ties, center or 3rd rail, block lines) 220 * 221 * @param g2 the graphics context 222 * @param isMain true if drawing mainlines 223 * @param isBlock true if drawing block lines 224 */ 225 abstract protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock); 226 227 /** 228 * draw two lines (rails) 229 * 230 * @param g2 the graphics context 231 * @param isMain true if drawing mainlines 232 * @param railDisplacement the offset from center to draw the lines 233 */ 234 abstract protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement); 235 236 /** 237 * draw hidden track 238 * 239 * @param g2 the graphics context 240 */ 241 // abstract protected void drawHidden(Graphics2D g2); 242 // note: placeholder until I get this implemented in all sub-classes 243 // TODO: replace with abstract declaration (above) 244 final protected void drawHidden(Graphics2D g2) { 245 // nothing to do here... move along... 246 } 247 248 /** 249 * draw the text for this layout track 250 * @param g 251 * note: currently can't override (final); change this if you need to 252 */ 253 final protected void drawLayoutTrackText(Graphics2D g) { 254 // get the center coordinates 255 int x = (int) center.getX(), y = (int) center.getY(); 256 257 // get the name of this track 258 String name = getName(); 259 260 // get the FontMetrics 261 FontMetrics metrics = g.getFontMetrics(g.getFont()); 262 263 // determine the X coordinate for the text 264 x -= metrics.stringWidth(name) / 2; 265 266 // determine the Y coordinate for the text 267 y += metrics.getHeight() / 2; 268 269 // (note we add the ascent, as in java 2d 0 is top of the screen) 270 //y += (int) metrics.getAscent(); 271 272 g.drawString(name, x, y); 273 } 274 275 /** 276 * Load a file for a specific arrow ending. 277 * 278 * @param n The arrow type as a number 279 * @param arrowsCountMenu menu containing the arrows to set visible 280 * selection 281 * @return An item for the arrow menu 282 */ 283 public JCheckBoxMenuItem loadArrowImageToJCBItem(int n, JMenu arrowsCountMenu) { 284 ImageIcon imageIcon = new ImageIcon(FileUtil.findURL("program:resources/icons/decorations/ArrowStyle" + n + ".png")); 285 JCheckBoxMenuItem jcbmi = new JCheckBoxMenuItem(imageIcon); 286 arrowsCountMenu.add(jcbmi); 287 jcbmi.setToolTipText(Bundle.getMessage("DecorationStyleMenuToolTip")); 288 // can't set selected here because the ActionListener has to be set first 289 return jcbmi; 290 } 291 protected static final int NUM_ARROW_TYPES = 6; 292 293 /** 294 * highlight unconnected connections 295 * 296 * @param g2 the graphics context 297 * @param specificType the specific connection to draw (or NONE for all) 298 */ 299 abstract protected void highlightUnconnected(Graphics2D g2, HitPointType specificType); 300 301 // optional parameter specificType = NONE 302 final protected void highlightUnconnected(Graphics2D g2) { 303 highlightUnconnected(g2, HitPointType.NONE); 304 } 305 306 /** 307 * draw the edit controls 308 * 309 * @param g2 the graphics context 310 */ 311 abstract protected void drawEditControls(Graphics2D g2); 312 313 /** 314 * Draw the turnout controls 315 * 316 * @param g2 the graphics context 317 */ 318 abstract protected void drawTurnoutControls(Graphics2D g2); 319 320 /** 321 * Draw track decorations 322 * 323 * @param g2 the graphics context 324 */ 325 abstract protected void drawDecorations(Graphics2D g2); 326 327 /** 328 * Get the hidden state of the track element. 329 * 330 * @return true if hidden; false otherwise 331 */ 332 final public boolean isHidden() { 333 return hidden; 334 } 335 336 final public void setHidden(boolean hide) { 337 if (hidden != hide) { 338 hidden = hide; 339 if (layoutEditor != null) { 340 layoutEditor.redrawPanel(); 341 } 342 } 343 } 344 345 private boolean hidden = false; 346 347 /* 348 * non-accessor methods 349 */ 350 /** 351 * get turnout state string 352 * 353 * @param turnoutState of the turnout 354 * @return the turnout state string 355 */ 356 final public String getTurnoutStateString(int turnoutState) { 357 String result = ""; 358 if (turnoutState == Turnout.CLOSED) { 359 result = Bundle.getMessage("TurnoutStateClosed"); 360 } else if (turnoutState == Turnout.THROWN) { 361 result = Bundle.getMessage("TurnoutStateThrown"); 362 } else { 363 result = Bundle.getMessage("BeanStateUnknown"); 364 } 365 return result; 366 } 367 368 /** 369 * Check for active block boundaries. 370 * <p> 371 * If any connection point of a layout track object has attached objects, 372 * such as signal masts, signal heads or NX sensors, the layout track object 373 * cannot be deleted. 374 * 375 * @return true if the layout track object can be deleted. 376 */ 377 abstract public boolean canRemove(); 378 379 /** 380 * Display the attached items that prevent removing the layout track item. 381 * 382 * @param itemList A list of the attached heads, masts and/or sensors. 383 * @param typeKey The object type such as Turnout, Level Crossing, etc. 384 */ 385 final public void displayRemoveWarningDialog(List<String> itemList, String typeKey) { 386 itemList.sort(null); 387 StringBuilder msg = new StringBuilder(Bundle.getMessage("MakeLabel", // NOI18N 388 Bundle.getMessage("DeleteTrackItem", Bundle.getMessage(typeKey)))); // NOI18N 389 for (String item : itemList) { 390 msg.append("\n " + item); // NOI18N 391 } 392 JmriJOptionPane.showMessageDialog(layoutEditor, 393 msg.toString(), 394 Bundle.getMessage("WarningTitle"), // NOI18N 395 JmriJOptionPane.WARNING_MESSAGE); 396 } 397 398 /** 399 * scale this LayoutTrack's coordinates by the x and y factors 400 * 401 * @param xFactor the amount to scale X coordinates 402 * @param yFactor the amount to scale Y coordinates 403 */ 404 abstract public void scaleCoords(double xFactor, double yFactor); 405 406 /** 407 * translate this LayoutTrack's coordinates by the x and y factors 408 * 409 * @param xFactor the amount to translate X coordinates 410 * @param yFactor the amount to translate Y coordinates 411 */ 412 abstract public void translateCoords(double xFactor, double yFactor); 413 414 /** 415 * rotate this LayoutTrack's coordinates by angleDEG's 416 * 417 * @param angleDEG the amount to rotate in degrees 418 */ 419 abstract public void rotateCoords(double angleDEG); 420 421 final protected Point2D rotatePoint(@Nonnull Point2D p, double sineRot, double cosineRot) { 422 double cX = center.getX(); 423 double cY = center.getY(); 424 425 double deltaX = p.getX() - cX; 426 double deltaY = p.getY() - cY; 427 428 double x = cX + cosineRot * deltaX - sineRot * deltaY; 429 double y = cY + sineRot * deltaX + cosineRot * deltaY; 430 431 return new Point2D.Double(x, y); 432 } 433 434 /** 435 * find the hit (location) type for a point 436 * 437 * @param hitPoint the point 438 * @param useRectangles whether to use (larger) rectangles or (smaller) 439 * circles for hit testing 440 * @param requireUnconnected whether to only return hit types for free 441 * connections 442 * @return the location type for the point (or NONE) 443 * @since 7.4.3 444 */ 445 abstract protected HitPointType findHitPointType(@Nonnull Point2D hitPoint, 446 boolean useRectangles, 447 boolean requireUnconnected); 448 449 // optional useRectangles & requireUnconnected parameters default to false 450 final protected HitPointType findHitPointType(@Nonnull Point2D p) { 451 return findHitPointType(p, false, false); 452 } 453 454 // optional requireUnconnected parameter defaults to false 455 final protected HitPointType findHitPointType(@Nonnull Point2D p, boolean useRectangles) { 456 return findHitPointType(p, useRectangles, false); 457 } 458 459 /** 460 * return the coordinates for a specified connection type (abstract: should 461 * be overridden by ALL subclasses) 462 * 463 * @param connectionType the connection type 464 * @return the coordinates for the specified connection type 465 */ 466 abstract public Point2D getCoordsForConnectionType(HitPointType connectionType); 467 468 /** 469 * @return the bounds of this track 470 */ 471 abstract public Rectangle2D getBounds(); 472 473 /** 474 * show the popup menu for this layout track 475 * 476 * @param mouseEvent the mouse down event that triggered this popup 477 * @return the popup menu for this layout track 478 */ 479 @Nonnull 480 abstract protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent); 481 482 /** 483 * Att items to the popup menu that's common to all implementing classes. 484 * @param mouseEvent the mouse down event that triggered this popup 485 * @param popup the popup menu 486 */ 487 protected void addCommonPopupItems(@Nonnull JmriMouseEvent mouseEvent, @Nonnull JPopupMenu popup) { 488 popup.addSeparator(); 489 setLogixNGPositionableMenu(popup); 490 layoutEditor.addPopupItems(popup, mouseEvent); 491 } 492 493 @Override 494 public String getNameString() { 495 return getName(); 496 } 497 498 @Override 499 public String getEditorName() { 500 return getLayoutEditor().getName(); 501 } 502 503 @Override 504 public int getX() { 505 return (int) center.getX(); 506 } 507 508 @Override 509 public int getY() { 510 return (int) center.getY(); 511 } 512 513 @Override 514 public String getTypeName() { 515 return layoutTrack.getTypeName(); 516 } 517 518 /** 519 * Check if edit of a conditional is in progress. 520 * 521 * @return true if this is the case, after showing dialog to user 522 */ 523 private boolean checkEditConditionalNG() { 524 if (_inEditInlineLogixNGMode) { 525 // Already editing a LogixNG, ask for completion of that edit 526 JmriJOptionPane.showMessageDialog(null, 527 Bundle.getMessage("Error_InlineLogixNGInEditMode"), // NOI18N 528 Bundle.getMessage("ErrorTitle"), // NOI18N 529 JmriJOptionPane.ERROR_MESSAGE); 530 _inlineLogixNGEdit.bringToFront(); 531 return true; 532 } 533 return false; 534 } 535 536 /** 537 * Add a menu entry to edit Id of the Positionable item 538 * 539 * @param popup the menu to add the entry to 540 */ 541 public void setLogixNGPositionableMenu(JPopupMenu popup) { 542 JMenu logixNG_Menu = new JMenu("LogixNG"); 543 popup.add(logixNG_Menu); 544 545 logixNG_Menu.add(new AbstractAction(Bundle.getMessage("LogixNG_Inline")) { 546 @Override 547 public void actionPerformed(ActionEvent e) { 548 if (checkEditConditionalNG()) return; 549 550 if (getLogixNG() == null) { 551 LogixNG logixNG = InstanceManager.getDefault(LogixNG_Manager.class) 552 .createLogixNG(null, true); 553 logixNG.setInlineLogixNG(LayoutTrackView.this); 554 logixNG.activate(); 555 logixNG.setEnabled(true); 556 logixNG.clearStartup(); 557 setLogixNG(logixNG); 558 } 559 LogixNGEditor logixNGEditor = new LogixNGEditor(null, getLogixNG().getSystemName()); 560 logixNGEditor.addEditorEventListener((HashMap<String, String> data) -> { 561 _inEditInlineLogixNGMode = false; 562 data.forEach((key, value) -> { 563 if (key.equals("Finish")) { // NOI18N 564 _inlineLogixNGEdit = null; 565 _inEditInlineLogixNGMode = false; 566 } else if (key.equals("Delete")) { // NOI18N 567 _inEditInlineLogixNGMode = false; 568 deleteLogixNG(getLogixNG()); 569 } else if (key.equals("chgUname")) { // NOI18N 570 getLogixNG().setUserName(value); 571 } 572 }); 573 if (getLogixNG() != null && getLogixNG().getNumConditionalNGs() == 0) { 574 deleteLogixNG_Internal(getLogixNG()); 575 } 576 }); 577 logixNGEditor.bringToFront(); 578 _inEditInlineLogixNGMode = true; 579 _inlineLogixNGEdit = logixNGEditor; 580 } 581 }); 582 } 583 584 private void deleteLogixNG(LogixNG logixNG) { 585 DeleteBean<LogixNG> deleteBean = new DeleteBean<>( 586 InstanceManager.getDefault(LogixNG_Manager.class)); 587 588 boolean hasChildren = logixNG.getNumConditionalNGs() > 0; 589 590 deleteBean.delete(logixNG, hasChildren, (t)->{deleteLogixNG_Internal(t);}, 591 (t,list)->{logixNG.getListenerRefsIncludingChildren(list);}, 592 jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName()); 593 } 594 595 private void deleteLogixNG_Internal(LogixNG logixNG) { 596 logixNG.setEnabled(false); 597 try { 598 InstanceManager.getDefault(LogixNG_Manager.class).deleteBean(logixNG, "DoDelete"); 599 logixNG.getInlineLogixNG().setLogixNG(null); 600 } catch (PropertyVetoException e) { 601 //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto 602 log.error("{} : Could not Delete.", e.getMessage()); 603 } 604 } 605 606 /** 607 * Get the LogixNG of this Positionable. 608 * @return the LogixNG or null if it has no LogixNG 609 */ 610 @Override 611 public LogixNG getLogixNG() { 612 return _logixNG; 613 } 614 615 /** 616 * Set the LogixNG of this Positionable. 617 * @param logixNG the LogixNG or null if remove the LogixNG from the Positionable 618 */ 619 @Override 620 public void setLogixNG(LogixNG logixNG) { 621 this._logixNG = logixNG; 622 } 623 624 @Override 625 public void setLogixNG_SystemName(String systemName) { 626 this._logixNG_SystemName = systemName; 627 } 628 629 @Override 630 public void setupLogixNG() { 631 _logixNG = InstanceManager.getDefault(LogixNG_Manager.class) 632 .getBySystemName(_logixNG_SystemName); 633 if (_logixNG == null) { 634 throw new RuntimeException(String.format( 635 "LogixNG %s is not found for LayoutTrackView %s in panel %s", 636 _logixNG_SystemName, getName(), layoutEditor.getName())); 637 } 638 _logixNG.setInlineLogixNG(this); 639 } 640 641 /** 642 * show the popup menu for this layout track 643 * 644 * @param where to show the popup 645 * @return the popup menu for this layout track 646 */ 647 @Nonnull 648 final protected JPopupMenu showPopup(Point2D where) { 649 return this.showPopup(new JmriMouseEvent( 650 layoutEditor.getTargetPanel(), // source 651 JmriMouseEvent.MOUSE_CLICKED, // id 652 System.currentTimeMillis(), // when 653 0, // modifiers 654 (int) where.getX(), (int) where.getY(), // where 655 0, // click count 656 true)); // popup trigger 657 658 } 659 660 /** 661 * show the popup menu for this layout track 662 * 663 * @return the popup menu for this layout track 664 */ 665 @Nonnull 666 final protected JPopupMenu showPopup() { 667 Point2D where = MathUtil.multiply(getCoordsCenter(), 668 layoutEditor.getZoom()); 669 return this.showPopup(where); 670 } 671 672 /** 673 * get the LayoutTrack connected at the specified connection type 674 * 675 * @param connectionType where on us to get the connection 676 * @return the LayoutTrack connected at the specified connection type 677 * @throws JmriException - if the connectionType is invalid 678 */ 679 abstract public LayoutTrack getConnection(HitPointType connectionType) throws JmriException; 680 681 /** 682 * set the LayoutTrack connected at the specified connection type 683 * 684 * @param connectionType where on us to set the connection 685 * @param o the LayoutTrack that is to be connected 686 * @param type where on the LayoutTrack we are connected 687 * @throws JmriException - if connectionType or type are invalid 688 */ 689 abstract public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws JmriException; 690 691 /** 692 * abstract method... subclasses should implement _IF_ they need to recheck 693 * their block boundaries 694 */ 695 abstract protected void reCheckBlockBoundary(); 696 697 /** 698 * get the layout connectivity for this track 699 * 700 * @return the list of Layout Connectivity objects 701 */ 702 abstract protected List<LayoutConnectivity> getLayoutConnectivity(); 703 704 /** 705 * return true if this connection type is disconnected 706 * 707 * @param connectionType the connection type to test 708 * @return true if the connection for this connection type is free 709 */ 710 public boolean isDisconnected(HitPointType connectionType) { 711 throw new IllegalArgumentException("should have called in Object instead of View (temporary)"); 712 } 713 714 /** 715 * return a list of the available connections for this layout track 716 * 717 * @return the list of available connections 718 */ 719 // note: used by LayoutEditorChecks.setupCheckUnConnectedTracksMenu() 720 // 721 // This could have just returned a boolean but I thought a list might be 722 // more useful (eventually... not currently being used; we just check to see 723 // if it's not empty.) 724 @Nonnull 725 abstract public List<HitPointType> checkForFreeConnections(); 726 727 /** 728 * determine if all the appropriate blocks have been assigned to this track 729 * 730 * @return true if all appropriate blocks have been assigned 731 */ 732 // note: used by LayoutEditorChecks.setupCheckUnBlockedTracksMenu() 733 // 734 abstract public boolean checkForUnAssignedBlocks(); 735 736 /** 737 * check this track and its neighbors for non-contiguous blocks 738 * <p> 739 * For each (non-null) blocks of this track do: #1) If it's got an entry in 740 * the blockNamesToTrackNameSetMap then #2) If this track is not in one of 741 * the TrackNameSets for this block #3) add a new set (with this 742 * block/track) to blockNamesToTrackNameSetMap and #4) check all the 743 * connections in this block (by calling the 2nd method below) 744 * <p> 745 * Basically, we're maintaining contiguous track sets for each block found 746 * (in blockNamesToTrackNameSetMap) 747 * 748 * @param blockNamesToTrackNameSetMaps hashmap of key:block names to lists 749 * of track name sets for those blocks 750 */ 751 // note: used by LayoutEditorChecks.setupCheckNonContiguousBlocksMenu() 752 // 753 abstract public void checkForNonContiguousBlocks( 754 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetMaps); 755 756 /** 757 * recursive routine to check for all contiguous tracks in this blockName 758 * 759 * @param blockName the block that we're checking for 760 * @param TrackNameSet the set of track names in this block 761 */ 762 abstract public void collectContiguousTracksNamesInBlockNamed( 763 @Nonnull String blockName, 764 @Nonnull Set<String> TrackNameSet); 765 766 /** 767 * Assign all the layout blocks in this track 768 * 769 * @param layoutBlock to this layout block (used by the Tools menu's "Assign 770 * block to selection" item) 771 */ 772 abstract public void setAllLayoutBlocks(LayoutBlock layoutBlock); 773 774 protected boolean removeInlineLogixNG() { 775 LogixNG logixNG = getLogixNG(); 776 777 if (logixNG == null) return true; 778 779 DeleteBean<LogixNG> deleteBean = new DeleteBean<>( 780 InstanceManager.getDefault(LogixNG_Manager.class)); 781 782 boolean hasChildren = logixNG.getNumConditionalNGs() > 0; 783 784 return deleteBean.delete(logixNG, hasChildren, (t)->{deleteLogixNG_Internal(t);}, 785 (t,list)->{logixNG.getListenerRefsIncludingChildren(list);}, 786 jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName(), 787 true); 788 } 789 790 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTrackView.class); 791}