001package jmri.jmrit.display.layoutEditor; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.awt.geom.*; 006import static java.lang.Float.POSITIVE_INFINITY; 007import java.text.MessageFormat; 008import java.util.List; 009import java.util.*; 010 011import javax.annotation.CheckForNull; 012import javax.annotation.Nonnull; 013import javax.swing.*; 014 015import jmri.*; 016import jmri.jmrit.display.layoutEditor.LayoutTraverser.SlotTrack; 017import jmri.util.MathUtil; 018import jmri.util.swing.JmriMouseEvent; 019 020/** 021 * MVC View component for the LayoutTraverser class. 022 * 023 * @author Bob Jacobsen Copyright (c) 2020 024 * @author Dave Sand Copyright (c) 2024 025 */ 026public class LayoutTraverserView extends LayoutTrackView { 027 028 // defined constants 029 // operational instance variables (not saved between sessions) 030 private final jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTraverserEditor editor; 031 032 /** 033 * Constructor method. 034 * @param traverser the layout traverser to create view for. 035 * @param c where to put it 036 * @param layoutEditor what layout editor panel to put it in 037 */ 038 public LayoutTraverserView(@Nonnull LayoutTraverser traverser, 039 @Nonnull Point2D c, 040 @Nonnull LayoutEditor layoutEditor) { 041 super(traverser, c, layoutEditor); 042 this.traverser = traverser; 043 044 editor = new jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTraverserEditor(layoutEditor); 045 } 046 047 private final LayoutTraverser traverser; 048 049 public final LayoutTraverser getTraverser() { return traverser; } 050 051 /** 052 * Get a string that represents this object. This should only be used for 053 * debugging. 054 * 055 * @return the string 056 */ 057 @Override 058 public String toString() { 059 return "LayoutTraverser " + getName(); 060 } 061 062 // 063 // Accessor methods 064 // 065 public double getDeckLength() { return traverser.getDeckLength(); } 066 public int getOrientation() { return traverser.getOrientation(); } 067 public void setOrientation(int o) { traverser.setOrientation(o); } 068 069 /** 070 * @return the layout block name 071 */ 072 @Nonnull 073 public String getBlockName() { 074 return traverser.getBlockName(); 075 } 076 077 /** 078 * @return the layout block 079 */ 080 public LayoutBlock getLayoutBlock() { 081 return traverser.getLayoutBlock(); 082 } 083 084 /** 085 * Set up a LayoutBlock for this LayoutTraverser. 086 * 087 * @param newLayoutBlock the LayoutBlock to set 088 */ 089 public void setLayoutBlock(@CheckForNull LayoutBlock newLayoutBlock) { 090 traverser.setLayoutBlock(newLayoutBlock); 091 } 092 093 /** 094 * Set up a LayoutBlock for this LayoutTraverser. 095 * 096 * @param name the name of the new LayoutBlock 097 */ 098 public void setLayoutBlockByName(@CheckForNull String name) { 099 traverser.setLayoutBlockByName(name); 100 } 101 102 /* 103 * non-accessor methods 104 */ 105 /** 106 * @return the bounds of this traverser. 107 */ 108 @Override 109 public Rectangle2D getBounds() { 110 Point2D center = getCoordsCenter(); 111 double length = getDeckLength(); 112 double width = traverser.getDeckWidth(); 113 if (getOrientation() == LayoutTraverser.HORIZONTAL) { 114 return new Rectangle2D.Double(center.getX() - width / 2, center.getY() - length / 2, width, length); 115 } else { 116 return new Rectangle2D.Double(center.getX() - length / 2, center.getY() - width / 2, length, width); 117 } 118 } 119 120 /** 121 * Get the center of the control point, which is on an edge of the traverser. 122 * @return The center point for the control circles. 123 */ 124 private Point2D getControlPointCenter() { 125 Point2D center = getCoordsCenter(); 126 Rectangle2D bounds = getBounds(); 127 if (getOrientation() == LayoutTraverser.HORIZONTAL) { 128 // Top edge for a horizontal (tall) traverser 129 return new Point2D.Double(center.getX(), bounds.getMinY()); 130 } else { 131 // Left edge for a vertical (wide) traverser 132 return new Point2D.Double(bounds.getMinX(), center.getY()); 133 } 134 } 135 public TrackSegment getSlotConnectIndexed(int index) { 136 return traverser.getSlotConnectIndexed(index); 137 } 138 139 public TrackSegment getSlotConnectOrdered(int i) { 140 return traverser.getSlotConnectOrdered(i); 141 } 142 143 public void setSlotConnect(TrackSegment ts, int index) { 144 traverser.setSlotConnect(ts, index); 145 } 146 147 public List<SlotTrack> getSlotList() { 148 return traverser.getSlotList(); 149 } 150 151 public int getNumberSlots() { 152 return traverser.getNumberSlots(); 153 } 154 155 public int getSlotIndex(int i) { 156 return traverser.getSlotIndex(i); 157 } 158 159 public double getSlotOffsetValue(int i) { 160 return traverser.getSlotOffsetValue(i); 161 } 162 163 public void setSlotTurnout(int index, String turnoutName, int state) { 164 traverser.setSlotTurnout(index, turnoutName, state); 165 } 166 167 public String getSlotTurnoutName(int i) { 168 return traverser.getSlotTurnoutName(i); 169 } 170 171 public Turnout getSlotTurnout(int i) { 172 return traverser.getSlotTurnout(i); 173 } 174 175 public int getSlotTurnoutState(int i) { 176 return traverser.getSlotTurnoutState(i); 177 } 178 179 public boolean isSlotDisabled(int i) { 180 return traverser.isSlotDisabled(i); 181 } 182 183 public void setSlotDisabled(int i, boolean boo) { 184 traverser.setSlotDisabled(i, boo); 185 } 186 187 public boolean isSlotDisabledWhenOccupied(int i) { 188 return traverser.isSlotDisabledWhenOccupied(i); 189 } 190 191 public void setSlotDisabledWhenOccupied(int i, boolean boo) { 192 traverser.setSlotDisabledWhenOccupied(i, boo); 193 } 194 195 public Point2D getSlotCoordsIndexed(int index) { 196 Point2D center = getCoordsCenter(); 197 double deckWidth = traverser.getDeckWidth(); 198 double anchorOffset = traverser.getSlotOffset() * 0.5; 199 for (int i=0; i<traverser.slotList.size(); i++) { 200 SlotTrack st = traverser.slotList.get(i); 201 if (st.getConnectionIndex() == index) { 202 double offset = st.getOffset(); 203 boolean sideA = (i % 2 == 0); 204 if (getOrientation() == LayoutTraverser.HORIZONTAL) { // Tall 205 double y = center.getY() + offset; 206 double x = center.getX() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset); 207 return new Point2D.Double(x, y); 208 } else { // Wide 209 double x = center.getX() + offset; 210 double y = center.getY() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset); 211 return new Point2D.Double(x, y); 212 } 213 } 214 } 215 return MathUtil.zeroPoint2D; 216 } 217 218 public Point2D getSlotCoordsOrdered(int i) { 219 if (i < traverser.slotList.size()) { 220 SlotTrack st = traverser.slotList.get(i); 221 if (st != null) { 222 Point2D center = getCoordsCenter(); 223 double deckWidth = traverser.getDeckWidth(); 224 double anchorOffset = traverser.getSlotOffset() * 0.5; 225 double offset = st.getOffset(); 226 boolean sideA = (i % 2 == 0); 227 228 if (getOrientation() == LayoutTraverser.HORIZONTAL) { // Tall 229 double y = center.getY() + offset; 230 double x = center.getX() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset); 231 return new Point2D.Double(x, y); 232 } else { // Wide 233 double x = center.getX() + offset; 234 double y = center.getY() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset); 235 return new Point2D.Double(x, y); 236 } 237 } 238 } 239 return MathUtil.zeroPoint2D; 240 } 241 242 private Point2D getSlotEdgePointOrdered(int i) { 243 if (i < traverser.slotList.size()) { 244 SlotTrack st = traverser.slotList.get(i); 245 if (st != null) { 246 Point2D center = getCoordsCenter(); 247 double deckWidth = traverser.getDeckWidth(); 248 double offset = st.getOffset(); 249 boolean sideA = (i % 2 == 0); 250 251 if (getOrientation() == LayoutTraverser.HORIZONTAL) { // Tall 252 double y = center.getY() + offset; 253 double x = center.getX() + (sideA ? -deckWidth / 2.0 : deckWidth / 2.0); 254 return new Point2D.Double(x, y); 255 } else { // Wide 256 double x = center.getX() + offset; 257 double y = center.getY() + (sideA ? -deckWidth / 2.0 : deckWidth / 2.0); 258 return new Point2D.Double(x, y); 259 } 260 } 261 } 262 return MathUtil.zeroPoint2D; 263 } 264 265 public void setSlotCoordsIndexed(double x, double y, int index) { 266 for (SlotTrack st : traverser.slotList) { 267 if (st.getConnectionIndex() == index) { 268 if (getOrientation() == LayoutTraverser.HORIZONTAL) { 269 st.setOffset(x - getCoordsCenter().getX()); 270 } else { 271 st.setOffset(y - getCoordsCenter().getY()); 272 } 273 break; 274 } 275 } 276 } 277 278 public void setSlotCoordsIndexed(Point2D point, int index) { 279 setSlotCoordsIndexed(point.getX(), point.getY(), index); 280 } 281 282 @Override 283 public Point2D getCoordsForConnectionType(HitPointType connectionType) { 284 Point2D result = getCoordsCenter(); 285 if (HitPointType.TRAVERSER_CENTER == connectionType) { 286 // This is correct 287 } else if (HitPointType.isTraverserSlotHitType(connectionType)) { 288 result = getSlotCoordsIndexed(connectionType.traverserTrackIndex()); 289 } else { 290 log.error("{}.getCoordsForConnectionType({}); Invalid connection type", 291 getName(), connectionType); // NOI18N 292 } 293 return result; 294 } 295 296 @Override 297 public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException { 298 if (HitPointType.isTraverserSlotHitType(connectionType)) { 299 return getSlotConnectIndexed(connectionType.traverserTrackIndex()); 300 } else { 301 String errString = MessageFormat.format("{0}.getCoordsForConnectionType({1}); Invalid connection type", 302 getName(), connectionType); // NOI18N 303 log.error("will throw {}", errString); // NOI18N 304 throw new jmri.JmriException(errString); 305 } 306 } 307 308 @Override 309 public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException { 310 if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) { 311 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid type", 312 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 313 log.error("will throw {}", errString); // NOI18N 314 throw new jmri.JmriException(errString); 315 } 316 if (HitPointType.isTraverserSlotHitType(connectionType)) { 317 if ((o == null) || (o instanceof TrackSegment)) { 318 setSlotConnect((TrackSegment) o, connectionType.traverserTrackIndex()); 319 } else { 320 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid object: {4}", 321 getName(), connectionType, o.getName(), 322 type, o.getClass().getName()); // NOI18N 323 log.error("will throw {}", errString); // NOI18N 324 throw new jmri.JmriException(errString); 325 } 326 } else { 327 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid connection type", 328 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 329 log.error("will throw {}", errString); // NOI18N 330 throw new jmri.JmriException(errString); 331 } 332 } 333 334 public boolean isMainlineIndexed(int index) { 335 return traverser.isMainlineIndexed(index); 336 } 337 338 public boolean isMainlineOrdered(int i) { 339 return traverser.isMainlineOrdered(i); 340 } 341 342 @Override 343 public void scaleCoords(double xFactor, double yFactor) { 344 Point2D factor = new Point2D.Double(xFactor, yFactor); 345 super.setCoordsCenter(MathUtil.granulize(MathUtil.multiply(getCoordsCenter(), factor), 1.0)); 346 traverser.setSlotOffset(traverser.getSlotOffset() * Math.min(xFactor, yFactor)); 347 } 348 349 @Override 350 public void translateCoords(double xFactor, double yFactor) { 351 Point2D factor = new Point2D.Double(xFactor, yFactor); 352 super.setCoordsCenter(MathUtil.add(getCoordsCenter(), factor)); 353 } 354 355 @Override 356 public void rotateCoords(double angleDEG) { 357 // For now, we only support 90-degree rotations 358 if (angleDEG == 90 || angleDEG == -270) { 359 setOrientation(1 - getOrientation()); // Toggle between HORIZONTAL and VERTICAL 360 } else if (angleDEG == -90 || angleDEG == 270) { 361 setOrientation(1 - getOrientation()); // Toggle between HORIZONTAL and VERTICAL 362 } 363 } 364 365 @Override 366 protected HitPointType findHitPointType(Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) { 367 HitPointType result = HitPointType.NONE; // assume point not on connection 368 Point2D p; 369// Point2D minPoint = MathUtil.zeroPoint2D; 370 371 double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize(); 372 double slotControlRadius = traverser.getSlotOffset() * 0.25; 373 double distance, minDistance = POSITIVE_INFINITY; 374 375 // check the center point 376 if (!requireUnconnected) { 377 p = getControlPointCenter(); 378 distance = MathUtil.distance(p, hitPoint); 379 if (distance < minDistance) { 380 minDistance = distance; 381// minPoint = p; 382 result = HitPointType.TRAVERSER_CENTER; 383 } 384 } 385 386 for (int k = 0; k < getNumberSlots(); k++) { 387 if (!isSlotDisabled(k) && (!requireUnconnected || (getSlotConnectOrdered(k) == null))) { 388 p = getSlotCoordsOrdered(k); 389 distance = MathUtil.distance(p, hitPoint); 390 if (distance < minDistance) { 391 minDistance = distance; 392// minPoint = p; 393 result = HitPointType.traverserTrackIndexedValue(getSlotIndex(k)); 394 } 395 } 396 } 397 398 if (result == HitPointType.TRAVERSER_CENTER) { 399 if (minDistance > circleRadius) { 400 result = HitPointType.NONE; 401 } 402 } else if (HitPointType.isTraverserSlotHitType(result)) { 403 if (minDistance > slotControlRadius) { 404 result = HitPointType.NONE; 405 } 406 } else { 407 result = HitPointType.NONE; 408 } 409 return result; 410 } 411 412 public String tLayoutBlockName = ""; 413 414 public boolean isTurnoutControlled() { 415 return traverser.isTurnoutControlled(); 416 } 417 418 public void setTurnoutControlled(boolean boo) { 419 traverser.setTurnoutControlled(boo); 420 } 421 422 private JPopupMenu popupMenu = null; 423 424 @Override 425 @Nonnull 426 protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent) { 427 if (popupMenu != null) { 428 popupMenu.removeAll(); 429 } else { 430 popupMenu = new JPopupMenu(); 431 } 432 433 JMenuItem jmi = popupMenu.add(Bundle.getMessage("MakeLabel", traverser.getTypeName()) + getName()); 434 jmi.setEnabled(false); 435 436 LayoutBlock lb = getLayoutBlock(); 437 if (lb == null) { 438 jmi = popupMenu.add(Bundle.getMessage("NoBlock")); 439 } else { 440 String displayName = lb.getDisplayName(); 441 jmi = popupMenu.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + displayName); 442 } 443 jmi.setEnabled(false); 444 445 if (!traverser.slotList.isEmpty()) { 446 JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections")); 447 traverser.slotList.forEach((rt) -> { 448 TrackSegment ts = rt.getConnect(); 449 if (ts != null) { 450 TrackSegmentView tsv = layoutEditor.getTrackSegmentView(ts); 451 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "" + rt.getConnectionIndex()) + ts.getName()) { 452 @Override 453 public void actionPerformed(ActionEvent e) { 454 layoutEditor.setSelectionRect(tsv.getBounds()); 455 tsv.showPopup(); 456 } 457 }); 458 } 459 }); 460 popupMenu.add(connectionsMenu); 461 } 462 463 popupMenu.add(new JSeparator(JSeparator.HORIZONTAL)); 464 465 popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonEdit")) { 466 @Override 467 public void actionPerformed(ActionEvent e) { 468 editor.editLayoutTrack(LayoutTraverserView.this); 469 } 470 }); 471 popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) { 472 @Override 473 public void actionPerformed(ActionEvent e) { 474 if (removeInlineLogixNG() && layoutEditor.removeTraverser(traverser)) { 475 remove(); 476 dispose(); 477 } 478 } 479 }); 480 layoutEditor.setShowAlignmentMenu(popupMenu); 481 addCommonPopupItems(mouseEvent, popupMenu); 482 popupMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY()); 483 return popupMenu; 484 } 485 486 private JPopupMenu slotPopup = null; 487 488 protected void showSlotPopUp(JmriMouseEvent e, int index) { 489 if (slotPopup != null) { 490 slotPopup.removeAll(); 491 } else { 492 slotPopup = new JPopupMenu(); 493 } 494 495 for (SlotTrack rt : traverser.slotList) { 496 if (rt.getConnectionIndex() == index) { 497 JMenuItem jmi = slotPopup.add("Traverser Slot " + index); 498 jmi.setEnabled(false); 499 500 slotPopup.add(new AbstractAction( 501 Bundle.getMessage("MakeLabel", 502 Bundle.getMessage("Connected")) 503 + rt.getConnect().getName()) { 504 @Override 505 public void actionPerformed(ActionEvent e) { 506 LayoutEditorFindItems lf = layoutEditor.getFinder(); 507 LayoutTrack lt = lf.findObjectByName(rt.getConnect().getName()); 508 509 if (lt != null) { 510 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 511 layoutEditor.setSelectionRect(ltv.getBounds()); 512 ltv.showPopup(); 513 } 514 } 515 }); 516 517 if (rt.getTurnout() != null) { 518 String info = rt.getTurnout().getDisplayName(); 519 String stateString = getTurnoutStateString(rt.getTurnoutState()); 520 if (!stateString.isEmpty()) { 521 info += " (" + stateString + ")"; 522 } 523 jmi = slotPopup.add(info); 524 jmi.setEnabled(false); 525 526 slotPopup.add(new JSeparator(JSeparator.HORIZONTAL)); 527 528 JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(Bundle.getMessage("Disabled")); 529 cbmi.setSelected(rt.isDisabled()); 530 slotPopup.add(cbmi); 531 cbmi.addActionListener((java.awt.event.ActionEvent e2) -> { 532 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e2.getSource(); 533 rt.setDisabled(o.isSelected()); 534 }); 535 536 cbmi = new JCheckBoxMenuItem(Bundle.getMessage("DisabledWhenOccupied")); 537 cbmi.setSelected(rt.isDisabledWhenOccupied()); 538 slotPopup.add(cbmi); 539 cbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 540 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e3.getSource(); 541 rt.setDisabledWhenOccupied(o.isSelected()); 542 }); 543 } 544 slotPopup.show(e.getComponent(), e.getX(), e.getY()); 545 break; 546 } 547 } 548 } 549 550 public void setPosition(int index) { 551 traverser.setPosition(index); 552 } 553 554 public int getPosition() { 555 return traverser.getPosition(); 556 } 557 558 public void dispose() { 559 if (popupMenu != null) { 560 popupMenu.removeAll(); 561 } 562 popupMenu = null; 563 traverser.slotList.forEach((rt) -> { 564 rt.dispose(); 565 }); 566 } 567 568 public void remove() { 569 active = false; 570 } 571 572 private boolean active = true; 573 574 public boolean isActive() { 575 return active; 576 } 577 578 @Override 579 protected void drawDecorations(Graphics2D g2) {} 580 581 @Override 582 protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) { 583 float trackWidth = 2.F; 584 585 586 // Only draw in the appropriate pass (mainline or sideline) 587 if (isMain != traverser.isMainline()) { 588 return; 589 } 590 591 // Save original stroke and color 592 Stroke originalStroke = g2.getStroke(); 593 Color originalColor = g2.getColor(); 594 595 // Draw pit outline and control circles - these are static and belong to the traverser itself 596 if (!isBlock && (isMain == traverser.isMainline())) { 597 // Draw the outer pit rectangle with a thin line 598 g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 599 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 600 g2.draw(getBounds()); 601 602 // Draw the control circles with the standard track width 603 604 double circleRadius = Math.max(traverser.getDeckWidth() / 8.f, trackWidth * 2); 605 double circleDiameter = circleRadius * 2.f; 606 Point2D center = getControlPointCenter(); 607 g2.setStroke(new BasicStroke(trackWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 608 g2.draw(new Ellipse2D.Double(center.getX() - circleRadius, center.getY() - circleRadius, circleDiameter, circleDiameter)); 609 g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 610 g2.draw(trackControlCircleAt(center)); 611 612 // Restore original stroke and color for subsequent drawing 613 g2.setStroke(originalStroke); 614 g2.setColor(originalColor); 615 } 616 617 // Draw slot tracks 618 for (int i = 0; i < getNumberSlots(); i++) { // Loop through all slots 619 if (!traverser.isSlotDisabled(i)) { // Only draw if the slot is NOT disabled 620 Color slotColor = null; 621 TrackSegment ts = getSlotConnectOrdered(i); 622// // A slot is mainline if the bridge is, or if the connected track is. 623// boolean slotIsMain = false; 624// if (ts != null) { 625// slotIsMain = ts.isMainline(); 626// } 627 // Set color for block, if any 628 if (isBlock) { 629 if (ts == null) { 630 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 631 } else { 632 LayoutBlock lb = ts.getLayoutBlock(); 633 if (lb != null) { 634 slotColor = g2.getColor(); 635 setColorForTrackBlock(g2, lb); 636 } 637 } 638 } 639 640 //LayoutTrackDrawingOptions ltdo = layoutEditor.getLayoutTrackDrawingOptions(); 641 //float width = isMain ? ltdo.getMainBlockLineWidth() : ltdo.getSideBlockLineWidth(); 642 //g2.setStroke(new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 643 Point2D edgePoint = getSlotEdgePointOrdered(i); 644 Point2D anchorPoint = getSlotCoordsOrdered(i); 645 g2.draw(new Line2D.Double(edgePoint, anchorPoint)); 646 647 // Restore color if changed 648 if (slotColor != null) { 649 g2.setColor(slotColor); 650 } 651 } 652 } 653 654 // Draw the sliding bridge 655 int currentPositionConnectionIndex = getPosition(); 656 int orderedIndex = -1; 657 if (currentPositionConnectionIndex != -1) { 658 for (int i = 0; i < getNumberSlots(); i++) { 659 if (getSlotIndex(i) == currentPositionConnectionIndex) { 660 orderedIndex = i; 661 break; 662 } 663 } 664 } 665 // Only draw the bridge if a slot is selected, it's not disabled, and its mainline status matches the current pass 666 if (orderedIndex != -1 && !traverser.isSlotDisabled(orderedIndex) && (isMain == traverser.isMainline())) { 667 // Set color for bridge 668 if (isBlock) { 669 LayoutBlock lb = getLayoutBlock(); 670 if (lb != null) { 671 setColorForTrackBlock(g2, lb); 672 } else { 673 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 674 } 675 } else { 676 g2.setColor(traverser.isMainline() ? layoutEditor.getLayoutTrackDrawingOptions().getMainRailColor() 677 : layoutEditor.getLayoutTrackDrawingOptions().getSideRailColor()); 678 } 679 // Set the stroke for the bridge 680 //LayoutTrackDrawingOptions ltdo = layoutEditor.getLayoutTrackDrawingOptions(); 681 //float width = traverser.isMainline() ? ltdo.getMainBlockLineWidth() : ltdo.getSideBlockLineWidth(); 682 //g2.setStroke(new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 683 684 Point2D center = getCoordsCenter(); 685 Rectangle2D pit = getBounds(); 686 double offset = getSlotOffsetValue(orderedIndex); 687 double gap = traverser.getDeckWidth() * 0.2; 688 boolean sideA = (orderedIndex % 2 == 0); 689 690 if (getOrientation() == LayoutTraverser.HORIZONTAL) { 691 double y = center.getY() + offset; 692 if (sideA) { 693 g2.draw(new Line2D.Double(pit.getMinX(), y, pit.getMaxX() - gap, y)); 694 } else { 695 g2.draw(new Line2D.Double(pit.getMinX() + gap, y, pit.getMaxX(), y)); 696 } 697 } else { // VERTICAL 698 double x = center.getX() + offset; 699 if (sideA) { 700 g2.draw(new Line2D.Double(x, pit.getMinY(), x, pit.getMaxY() - gap)); 701 } else { 702 g2.draw(new Line2D.Double(x, pit.getMinY() + gap, x, pit.getMaxY())); 703 } 704 } 705 } 706 if (!layoutEditor.isEditable()) { 707 drawTurnoutControls(g2); 708 } 709 710 // Restore original stroke and color 711 g2.setStroke(originalStroke); 712 g2.setColor(originalColor); 713 } 714 715 @Override 716 protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) { 717 // For now, just draw a simple line for the rails 718 if (!isMain) return; 719 720 Rectangle2D pit = getBounds(); 721 if (getOrientation() == LayoutTraverser.HORIZONTAL) { 722 g2.draw(new Line2D.Double(pit.getMinX(), pit.getMinY() + railDisplacement, pit.getMaxX(), pit.getMinY() + railDisplacement)); 723 g2.draw(new Line2D.Double(pit.getMinX(), pit.getMaxY() - railDisplacement, pit.getMaxX(), pit.getMaxY() - railDisplacement)); 724 } else { // VERTICAL 725 g2.draw(new Line2D.Double(pit.getMinX() + railDisplacement, pit.getMinY(), pit.getMinX() + railDisplacement, pit.getMaxY())); 726 g2.draw(new Line2D.Double(pit.getMaxX() - railDisplacement, pit.getMinY(), pit.getMaxX() - railDisplacement, pit.getMaxY())); 727 } 728 } 729 730 @Override 731 protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) { 732 for (int j = 0; j < getNumberSlots(); j++) { 733 if (!traverser.isSlotDisabled(j) && ((specificType == HitPointType.NONE) || (specificType == (HitPointType.traverserTrackIndexedValue(j))))) { 734 if (getSlotConnectOrdered(j) == null) { 735 Point2D pt = getSlotCoordsOrdered(j); 736 g2.fill(trackControlCircleAt(pt)); 737 } 738 } 739 } 740 } 741 742 @Override 743 protected void drawTurnoutControls(Graphics2D g2) { 744 if (isTurnoutControlled()) { 745 for (int j = 0; j < getNumberSlots(); j++) { 746 if (getPosition() != getSlotIndex(j)) { 747 SlotTrack rt = traverser.slotList.get(j); // Get the SlotTrack object 748 if (!rt.isDisabled() && !(rt.isDisabledWhenOccupied() && rt.isOccupied())) { 749 Point2D pt = getSlotCoordsOrdered(j); 750 g2.draw(trackControlCircleAt(pt)); 751 } 752 } 753 } 754 } 755 } 756 757 @Override 758 protected void drawEditControls(Graphics2D g2) { 759 for (int j = 0; j < getNumberSlots(); j++) { 760 if (!isSlotDisabled(j)) { 761 Point2D pt = getSlotCoordsOrdered(j); 762 763 if (getSlotConnectOrdered(j) == null) { 764 g2.setColor(Color.red); 765 } else { 766 g2.setColor(Color.green); 767 } 768 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 769 } 770 } 771 } 772 773 @Override 774 protected void reCheckBlockBoundary() {} 775 776 @Override 777 protected List<LayoutConnectivity> getLayoutConnectivity() { 778 return null; 779 } 780 781 @Override 782 public List<HitPointType> checkForFreeConnections() { 783 List<HitPointType> result = new ArrayList<>(); 784 for (int k = 0; k < getNumberSlots(); k++) { 785 if (getSlotConnectOrdered(k) == null) { 786 result.add(HitPointType.traverserTrackIndexedValue(k)); 787 } 788 } 789 return result; 790 } 791 792 @Override 793 public boolean checkForUnAssignedBlocks() { 794 return true; 795 } 796 797 @Override 798 public void checkForNonContiguousBlocks( 799 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 800 } 801 802 @Override 803 public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName, 804 @Nonnull Set<String> TrackNameSet) { 805 } 806 807 @Override 808 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 809 } 810 811 @Override 812 public boolean canRemove() { 813 return true; 814 } 815 816 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTraverserView.class); 817}