001package jmri.jmrit.display.layoutEditor; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.awt.geom.*; 006import java.util.List; 007import java.util.*; 008import java.util.function.*; 009 010import javax.annotation.CheckForNull; 011import javax.annotation.Nonnull; 012import javax.swing.*; 013 014import jmri.jmrit.display.layoutEditor.blockRoutingTable.LayoutBlockRouteTableAction; 015import jmri.util.*; 016import jmri.util.swing.JmriColorChooser; 017import jmri.util.swing.JmriMouseEvent; 018 019/** 020 * MVC View component for the TrackSegment class. 021 * <p> 022 * Arrows and bumpers are visual, presentation aspects handled in the View. 023 * 024 * @author Bob Jacobsen Copyright (c) 2020 025 */ 026public class TrackSegmentView extends LayoutTrackView { 027 028 public TrackSegmentView(@Nonnull TrackSegment track, @Nonnull LayoutEditor layoutEditor) { 029 super(track, layoutEditor); 030 031 this.trackSegment = track; 032 033 setupDefaultBumperSizes(layoutEditor); 034 editor = new jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.TrackSegmentEditor(layoutEditor); 035 } 036 037 /** 038 * constructor method. 039 * 040 * @param track the track segment to view 041 * @param layoutEditor for reference to tools 042 * @param arc specify display 043 * @param flip specify display 044 * @param circle specify display 045 */ 046 public TrackSegmentView(@Nonnull TrackSegment track, @Nonnull LayoutEditor layoutEditor, 047 boolean arc, boolean flip, boolean circle 048 ) { 049 this(track, layoutEditor); 050 } 051 052 // persistent instances variables (saved between sessions) 053 private boolean dashed = false; 054 055 private boolean arc = false; 056 private boolean circle = false; 057 private boolean flip = false; 058 private double angle = 0.0D; 059 060 private boolean changed = false; 061 private boolean bezier = false; 062 063 // for Bezier 064 private final ArrayList<Point2D> bezierControlPoints = new ArrayList<>(); // list of control point displacements 065 066 // temporary reference to the Editor that will eventually be part of View 067 private final jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.TrackSegmentEditor editor; 068 069 final private TrackSegment trackSegment; 070 071 // temporary? 072 @Nonnull 073 public TrackSegment getTrackSegment() { 074 return trackSegment; 075 } 076 077 /** 078 * Get debugging string for the TrackSegment. 079 * 080 * @return text showing id and connections of this segment 081 */ 082 @Override 083 public String toString() { 084 return "TrackSegmentView " + getName() 085 + " c1:{" + getConnect1Name() + " (" + getType1() + ")}," 086 + " c2:{" + getConnect2Name() + " (" + getType2() + ")}"; 087 088 } 089 090 /* 091 * Accessor methods 092 */ 093 @Nonnull 094 public String getBlockName() { 095 return trackSegment.getBlockName(); 096 } 097 098 public HitPointType getType1() { 099 return trackSegment.getType1(); 100 } 101 102 public HitPointType getType2() { 103 return trackSegment.getType2(); 104 } 105 106 public LayoutTrack getConnect1() { 107 return trackSegment.getConnect1(); 108 } 109 110 public LayoutTrack getConnect2() { 111 return trackSegment.getConnect2(); 112 } 113 114 /** 115 * set a new connection 1 116 * 117 * @param connectTrack the track we want to connect to 118 * @param connectionType where on that track we want to be connected 119 */ 120 protected void setNewConnect1(@CheckForNull LayoutTrack connectTrack, HitPointType connectionType) { 121 trackSegment.setNewConnect1(connectTrack, connectionType); 122 } 123 124 /** 125 * set a new connection 2 126 * 127 * @param connectTrack the track we want to connect to 128 * @param connectionType where on that track we want to be connected 129 */ 130 protected void setNewConnect2(@CheckForNull LayoutTrack connectTrack, HitPointType connectionType) { 131 trackSegment.setNewConnect2(connectTrack, connectionType); 132 } 133 134 /** 135 * replace old track connection with new track connection 136 * 137 * @param oldTrack the old track connection 138 * @param newTrack the new track connection 139 * @param newType type of the new track connection 140 * @return true if successful 141 */ 142 public boolean replaceTrackConnection(@CheckForNull LayoutTrack oldTrack, @CheckForNull LayoutTrack newTrack, HitPointType newType) { 143 return trackSegment.replaceTrackConnection(oldTrack, newTrack, newType); 144 } 145 146 /** 147 * @return true if track segment should be drawn dashed 148 */ 149 public boolean isDashed() { 150 return dashed; 151 } 152 153 public void setDashed(boolean dash) { 154 if (dashed != dash) { 155 dashed = dash; 156 layoutEditor.redrawPanel(); 157 layoutEditor.setDirty(); 158 } 159 } 160 161 /** 162 * @return true if track segment is an arc 163 */ 164 public boolean isArc() { 165 return arc; 166 } 167 168 public void setArc(boolean boo) { 169 if (arc != boo) { 170 arc = boo; 171 if (arc) { 172 circle = false; 173 bezier = false; 174 hideConstructionLines(SHOWCON); 175 } 176 changed = true; 177 } 178 } 179 180 /** 181 * @return true if track segment is circle 182 */ 183 public boolean isCircle() { 184 return circle; 185 } 186 187 public void setCircle(boolean boo) { 188 if (circle != boo) { 189 circle = boo; 190 if (circle) { 191 // if it was a bezier 192 if (bezier) { 193 // then use control point to calculate arc 194 // adjacent connections must be defined... 195 if ((getConnect1() != null) && (getConnect2() != null)) { 196 Point2D end1 = layoutEditor.getCoords(getConnect1(), getType1()); 197 Point2D end2 = layoutEditor.getCoords(getConnect2(), getType2()); 198 double chordLength = MathUtil.distance(end1, end2); 199 200 // get first and last control points 201 int cnt = bezierControlPoints.size(); 202 203 Point2D cp0 = bezierControlPoints.get(0); 204 Point2D cpN = bezierControlPoints.get(cnt - 1); 205 // calculate orthoginal points 206 Point2D op1 = MathUtil.add(end1, MathUtil.orthogonal(MathUtil.subtract(cp0, end1))); 207 Point2D op2 = MathUtil.subtract(end2, MathUtil.orthogonal(MathUtil.subtract(cpN, end2))); 208 // use them to find center point 209 Point2D ip = MathUtil.intersect(end1, op1, end2, op2); 210 if (ip != null) { // single intersection point found 211 double r1 = MathUtil.distance(ip, end1); 212 double r2 = MathUtil.distance(ip, end2); 213 if (Math.abs(r1 - r2) <= 1.0) { 214 // calculate arc: θ = 2 sin-1(c/(2r)) 215 setAngle(Math.toDegrees(2.0 * Math.asin(chordLength / (2.0 * r1)))); 216 // the sign of the distance tells what side of the line the center point is on 217 double distance = MathUtil.distance(end1, end2, ip); 218 setFlip(distance < 0.0); 219 } 220 } 221 } 222 bezier = false; 223 } else if (getAngle() < 1.0D) { 224 setAngle(90.0D); 225 } 226 arc = true; 227 hideConstructionLines(SHOWCON); 228 } 229 changed = true; 230 } 231 } 232 233 /** 234 * @return true if track segment circle or arc should be drawn flipped 235 */ 236 public boolean isFlip() { 237 return flip; 238 } 239 240 public void setFlip(boolean boo) { 241 if (flip != boo) { 242 flip = boo; 243 changed = true; 244 hideConstructionLines(SHOWCON); 245 layoutEditor.redrawPanel(); 246 layoutEditor.setDirty(); 247 } 248 } 249 250 /** 251 * @return true if track segment is a bezier curve 252 */ 253 public boolean isBezier() { 254 return bezier; 255 } 256 257 /** 258 * @param bool Set true to turn on Bezier curve representation. 259 */ 260 public void setBezier(boolean bool) { 261 if (bezier != bool) { 262 bezier = bool; 263 if (bezier) { 264 arc = false; 265 circle = false; 266 hideConstructionLines(SHOWCON); 267 } 268 changed = true; 269 } 270 } 271 272 public double getAngle() { 273 return angle; 274 } 275 276 public void setAngle(double x) { 277 angle = MathUtil.pin(x, 0.0D, 180.0D); 278 changed = true; 279 } 280 281 /** 282 * Get the direction from end point 1 to 2. 283 * <p> 284 * Note: Goes CW from east (0) to south (PI/2) to west (PI) to north 285 * (PI*3/2), etc. 286 * 287 * @return The direction (in radians) 288 */ 289 public double getDirectionRAD() { 290 Point2D ep1 = getCoordsCenter(), ep2 = getCoordsCenter(); 291 if (getConnect1() != null) { 292 ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 293 } 294 if (getConnect2() != null) { 295 ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 296 } 297 return (Math.PI / 2.D) - MathUtil.computeAngleRAD(ep1, ep2); 298 } 299 300 /** 301 * Get the direction from end point 1 to 2. 302 * <p> 303 * Note: Goes CW from east (0) to south (90) to west (180) to north (270), 304 * etc. 305 * 306 * @return the direction (in degrees) 307 */ 308 public double getDirectionDEG() { 309 return Math.toDegrees(getDirectionRAD()); 310 } 311 312 /** 313 * Determine if we need to redraw a curved piece of track. Saves having to 314 * recalculate the circle details each time. 315 * 316 * @return true means needs to be (re)drawn 317 */ 318 public boolean trackNeedsRedraw() { 319 return changed; 320 } 321 322 public void trackRedrawn() { 323 changed = false; 324 } 325 326 public LayoutBlock getLayoutBlock() { 327 return trackSegment.getLayoutBlock(); 328 } 329 330 public String getConnect1Name() { 331 return trackSegment.getConnect1Name(); 332 } 333 334 public String getConnect2Name() { 335 return trackSegment.getConnect2Name(); 336 } 337 338 @Override 339 public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException { 340 return trackSegment.getConnection(connectionType); 341 } 342 343 /** 344 * {@inheritDoc} 345 * <p> 346 * This implementation does nothing because {@link #setNewConnect1} and 347 * {@link #setNewConnect2} should be used instead. 348 */ 349 // only implemented here to suppress "does not override abstract method " error in compiler 350 @Override 351 public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException { 352 } 353 354 public int getNumberOfBezierControlPoints() { 355 return bezierControlPoints.size(); 356 } 357 358 /** 359 * @param index If negative, this is index from the end i.e. -1 is the last 360 * element 361 * @return Reference to the indexed control point 362 */ 363 public Point2D getBezierControlPoint(int index) { 364 Point2D result = getCoordsCenter(); 365 if (index < 0) { 366 index += bezierControlPoints.size(); 367 } 368 if ((index >= 0) && (index < bezierControlPoints.size())) { 369 result = bezierControlPoints.get(index); 370 } 371 return result; 372 } 373 374 /** 375 * @param p the location of the point to be set 376 * @param index If negative, this is index from the end i.e. -1 is the last 377 * element 378 */ 379 public void setBezierControlPoint(@CheckForNull Point2D p, int index) { 380 if (index < 0) { 381 index += bezierControlPoints.size(); 382 } 383 if ((index >= 0) && (index <= bezierControlPoints.size())) { 384 if (index < bezierControlPoints.size()) { 385 bezierControlPoints.set(index, p); 386 } else { 387 bezierControlPoints.add(p); 388 } 389 } 390 } 391 392 @Nonnull 393 public ArrayList<Point2D> getBezierControlPoints() { 394 return bezierControlPoints; 395 } 396 397 /** 398 * Set up a LayoutBlock for this Track Segment. 399 * 400 * @param newLayoutBlock the LayoutBlock to set 401 */ 402 public void setLayoutBlock(@CheckForNull LayoutBlock newLayoutBlock) { 403 trackSegment.setLayoutBlock(newLayoutBlock); 404 } 405 406 /** 407 * Set up a LayoutBlock for this Track Segment. 408 * 409 * @param name the name of the new LayoutBlock 410 */ 411 public void setLayoutBlockByName(@CheckForNull String name) { 412 trackSegment.setLayoutBlockByName(name); 413 } 414 415 /* 416 * non-accessor methods 417 */ 418 /** 419 * {@inheritDoc} 420 */ 421 @Override 422 public void scaleCoords(double xFactor, double yFactor) { 423 Point2D factor = new Point2D.Double(xFactor, yFactor); 424 super.setCoordsCenter(MathUtil.multiply(getCoordsCenter(), factor)); 425 if (isBezier()) { 426 for (Point2D p : bezierControlPoints) { 427 p.setLocation(MathUtil.multiply(p, factor)); 428 } 429 } 430 } 431 432 /** 433 * {@inheritDoc} 434 */ 435 @Override 436 public void translateCoords(double xFactor, double yFactor) { 437 super.setCoordsCenter(MathUtil.add(getCoordsCenter(), new Point2D.Double(xFactor, yFactor))); 438 } 439 440 /** 441 * {@inheritDoc} 442 */ 443 @Override 444 public void rotateCoords(double angleDEG) { 445 if (isBezier()) { 446 for (Point2D p : bezierControlPoints) { 447 p.setLocation(MathUtil.rotateDEG(p, getCoordsCenter(), angleDEG)); 448 } 449 } 450 } 451 452 /** 453 * Set center coordinates. 454 * 455 * @param newCenterPoint the coordinates to set 456 */ 457 @Override 458 public void setCoordsCenter(@Nonnull Point2D newCenterPoint) { 459 if (getCoordsCenter() != newCenterPoint) { 460 if (isBezier()) { 461 Point2D delta = MathUtil.subtract(newCenterPoint, getCoordsCenter()); 462 for (Point2D p : bezierControlPoints) { 463 p.setLocation(MathUtil.add(p, delta)); 464 } 465 } 466 super.setCoordsCenter(newCenterPoint); 467 } 468 } 469 470 // initialization instance variables (used when loading a LayoutEditor) 471 public String tConnect1Name = ""; 472 public String tConnect2Name = ""; 473 474 public String tLayoutBlockName = ""; 475 476 public void updateBlockInfo() { 477 trackSegment.updateBlockInfo(); 478 } 479 480 /** 481 * {@inheritDoc} 482 */ 483 @Override 484 protected HitPointType findHitPointType(Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) { 485 HitPointType result = HitPointType.NONE; // assume point not on connection 486 487 if (!requireUnconnected) { 488 // note: optimization here: instead of creating rectangles for all the 489 // points to check below, we create a rectangle for the test point 490 // and test if the points below are in that rectangle instead. 491 Rectangle2D r = layoutEditor.layoutEditorControlCircleRectAt(hitPoint); 492 Point2D p, minPoint = MathUtil.zeroPoint2D; 493 double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize(); 494 double distance, minDistance = Float.POSITIVE_INFINITY; 495 496 if (isCircle()) { 497 p = getCoordsCenterCircle(); 498 distance = MathUtil.distance(p, hitPoint); 499 if (distance < minDistance) { 500 minDistance = distance; 501 minPoint = p; 502 result = HitPointType.TRACK_CIRCLE_CENTRE; 503 } 504 } else if (isBezier()) { 505 // hit testing for the control points 506 for (int index = 0; index < bezierControlPoints.size(); index++) { 507 p = bezierControlPoints.get(index); 508 distance = MathUtil.distance(p, hitPoint); 509 if (distance < minDistance) { 510 minDistance = distance; 511 minPoint = p; 512 result = HitPointType.bezierPointIndexedValue(index); 513 } 514 } 515 } 516 p = getCentreSeg(); 517 if (r.contains(p)) { 518 distance = MathUtil.distance(p, hitPoint); 519 if (distance <= minDistance) { 520 minDistance = distance; 521 minPoint = p; 522 result = HitPointType.TRACK; 523 } 524 } 525 if ((result != HitPointType.NONE) && (useRectangles ? !r.contains(minPoint) : (minDistance > circleRadius))) { 526 result = HitPointType.NONE; 527 } 528 } 529 return result; 530 } // findHitPointType 531 532 /** 533 * Get the coordinates for a specified connection type. 534 * 535 * @param connectionType the connection type 536 * @return the coordinates for the specified connection type 537 */ 538 @Override 539 public Point2D getCoordsForConnectionType(HitPointType connectionType) { 540 Point2D result = getCentreSeg(); 541 if (connectionType == HitPointType.TRACK_CIRCLE_CENTRE) { 542 result = getCoordsCenterCircle(); 543 } else if (HitPointType.isBezierHitType(connectionType)) { 544 result = getBezierControlPoint(connectionType.bezierPointIndex()); 545 } 546 return result; 547 } 548 549 /** 550 * @return the bounds of this track segment 551 */ 552 @Override 553 public Rectangle2D getBounds() { 554 Point2D ep1 = getCoordsCenter(), ep2 = getCoordsCenter(); 555 if (getConnect1() != null) { 556 ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 557 } 558 if (getConnect2() != null) { 559 ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 560 } 561 562 Rectangle2D result = new Rectangle2D.Double(ep1.getX(), ep1.getY(), 0, 0); 563 result.add(ep2); 564 565 if (isArc()) { 566 result.add(getCentreSeg()); 567 if (isCircle()) { 568 result.add(getCoordsCenterCircle()); 569 } 570 } else if (isBezier()) { 571 for (int index = 0; index < bezierControlPoints.size(); index++) { 572 result.add(bezierControlPoints.get(index)); 573 } 574 } 575 result.add(getCoordsCenter()); 576 577 return result; 578 } 579 580 private JPopupMenu popupMenu = null; 581 private final JCheckBoxMenuItem mainlineCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("MainlineCheckBoxMenuItemTitle")); 582 private final JCheckBoxMenuItem hiddenCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("HiddenCheckBoxMenuItemTitle")); 583 private final JCheckBoxMenuItem dashedCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("DashedCheckBoxMenuItemTitle")); 584 private final JCheckBoxMenuItem flippedCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("FlippedCheckBoxMenuItemTitle")); 585 586 /** 587 * Maximum length of the bumper decoration. 588 */ 589 public static final int MAX_BUMPER_LENGTH = 40; 590 public static final int MIN_BUMPER_LENGTH = 8; 591 public static final int MAX_BUMPER_WIDTH = 10; 592 public static final int MIN_BUMPER_WIDTH = 1; 593 594 private static final int MAX_ARROW_LINE_WIDTH = 5; 595 private static final int MAX_ARROW_LENGTH = 60; 596 private static final int MAX_ARROW_GAP = 40; 597 598 private static final int MAX_BRIDGE_LINE_WIDTH = 5; 599 private static final int MIN_BRIDGE_LINE_WIDTH = 1; 600 601 private static final int MAX_BRIDGE_APPROACH_WIDTH = 100; 602 private static final int MIN_BRIDGE_APPROACH_WIDTH = 8; 603 604 private static final int MAX_BRIDGE_DECK_WIDTH = 80; 605 private static final int MIN_BRIDGE_DECK_WIDTH = 6; 606 607 private static final int MAX_BUMPER_LINE_WIDTH = 9; 608 private static final int MIN_BUMPER_LINE_WIDTH = 1; 609 610 private static final int MAX_TUNNEL_FLOOR_WIDTH = 40; 611 private static final int MIN_TUNNEL_FLOOR_WIDTH = 4; 612 613 private static final int MAX_TUNNEL_LINE_WIDTH = 9; 614 private static final int MIN_TUNNEL_LINE_WIDTH = 1; 615 616 private static final int MAX_TUNNEL_ENTRANCE_WIDTH = 80; 617 private static final int MIN_TUNNEL_ENTRANCE_WIDTH = 1; 618 619 /** 620 * Helper method, which adds "Set value" item to the menu. The value can be 621 * optionally range-checked. Item will be appended at the end of the menu. 622 * 623 * @param menu the target menu. 624 * @param titleKey bundle key for the menu title/dialog title 625 * @param toolTipKey bundle key for the menu item tooltip 626 * @param val value getter 627 * @param set value setter 628 * @param predicate checking predicate, possibly null. 629 */ 630 private void addNumericMenuItem(@Nonnull JMenu menu, 631 @Nonnull String titleKey, @Nonnull String toolTipKey, 632 @Nonnull Supplier<Integer> val, 633 @Nonnull Consumer<Integer> set, 634 @CheckForNull Predicate<Integer> predicate) { 635 int oldVal = val.get(); 636 JMenuItem jmi = menu.add(new JMenuItem(Bundle.getMessage("MakeLabel", 637 Bundle.getMessage(titleKey)) + oldVal)); 638 jmi.setToolTipText(Bundle.getMessage(toolTipKey)); 639 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 640 // prompt for lineWidth 641 int newValue = QuickPromptUtil.promptForInteger(layoutEditor, 642 Bundle.getMessage(titleKey), 643 Bundle.getMessage(titleKey), 644 // getting again, maybe something changed from the menu construction ? 645 val.get(), predicate); 646 set.accept(newValue); 647 layoutEditor.repaint(); 648 }); 649 } 650 651 /** 652 * {@inheritDoc} 653 */ 654 @Override 655 @Nonnull 656 protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent) { 657 if (popupMenu != null) { 658 popupMenu.removeAll(); 659 } else { 660 popupMenu = new JPopupMenu(); 661 } 662 663 String info = Bundle.getMessage("TrackSegment"); 664 if (isArc()) { 665 if (isCircle()) { 666 info = info + " (" + Bundle.getMessage("Circle") + ")"; 667 } else { 668 info = info + " (" + Bundle.getMessage("Ellipse") + ")"; 669 } 670 } else if (isBezier()) { 671 info = info + " (" + Bundle.getMessage("Bezier") + ")"; 672 } else { 673 info = info + " (" + Bundle.getMessage("Line") + ")"; 674 } 675 676 JMenuItem jmi = popupMenu.add(Bundle.getMessage("MakeLabel", info) + getName()); 677 jmi.setEnabled(false); 678 679 if (getBlockName().isEmpty()) { 680 jmi = popupMenu.add(Bundle.getMessage("NoBlock")); 681 } else { 682 jmi = popupMenu.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + getLayoutBlock().getDisplayName()); 683 } 684 jmi.setEnabled(false); 685 686 // if there are any track connections 687 if ((getConnect1() != null) || (getConnect2() != null)) { 688 JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections")); // there is no pane opening (which is what ... implies) 689 if (getConnect1() != null) { 690 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "1") + getConnect1().getName()) { 691 @Override 692 public void actionPerformed(ActionEvent e) { 693 LayoutEditorFindItems lf = layoutEditor.getFinder(); 694 LayoutTrack lt = lf.findObjectByName(getConnect1().getName()); 695 // this shouldn't ever be null... however... 696 if (lt != null) { 697 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 698 layoutEditor.setSelectionRect(ltv.getBounds()); 699 ltv.showPopup(); 700 } 701 } 702 }); 703 } 704 if (getConnect2() != null) { 705 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "2") + getConnect2().getName()) { 706 @Override 707 public void actionPerformed(ActionEvent e) { 708 LayoutEditorFindItems lf = layoutEditor.getFinder(); 709 LayoutTrack lt = lf.findObjectByName(getConnect2().getName()); 710 // this shouldn't ever be null... however... 711 if (lt != null) { 712 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 713 layoutEditor.setSelectionRect(ltv.getBounds()); 714 ltv.showPopup(); 715 } 716 } 717 }); 718 } 719 popupMenu.add(connectionsMenu); 720 } 721 722 popupMenu.add(new JSeparator(JSeparator.HORIZONTAL)); 723 724 popupMenu.add(mainlineCheckBoxMenuItem); 725 mainlineCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e3) -> trackSegment.setMainline(mainlineCheckBoxMenuItem.isSelected())); 726 mainlineCheckBoxMenuItem.setToolTipText(Bundle.getMessage("MainlineCheckBoxMenuItemToolTip")); 727 mainlineCheckBoxMenuItem.setSelected(trackSegment.isMainline()); 728 729 popupMenu.add(hiddenCheckBoxMenuItem); 730 hiddenCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e3) -> setHidden(hiddenCheckBoxMenuItem.isSelected())); 731 hiddenCheckBoxMenuItem.setToolTipText(Bundle.getMessage("HiddenCheckBoxMenuItemToolTip")); 732 hiddenCheckBoxMenuItem.setSelected(isHidden()); 733 734 popupMenu.add(dashedCheckBoxMenuItem); 735 dashedCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e3) -> setDashed(dashedCheckBoxMenuItem.isSelected())); 736 dashedCheckBoxMenuItem.setToolTipText(Bundle.getMessage("DashedCheckBoxMenuItemToolTip")); 737 dashedCheckBoxMenuItem.setSelected(dashed); 738 739 if (isArc()) { 740 popupMenu.add(flippedCheckBoxMenuItem); 741 flippedCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e3) -> setFlip(flippedCheckBoxMenuItem.isSelected())); 742 flippedCheckBoxMenuItem.setToolTipText(Bundle.getMessage("FlippedCheckBoxMenuItemToolTip")); 743 flippedCheckBoxMenuItem.setSelected(isFlip()); 744 } 745 746 // 747 // decorations menu 748 // 749 JMenu decorationsMenu = new JMenu(Bundle.getMessage("DecorationMenuTitle")); 750 decorationsMenu.setToolTipText(Bundle.getMessage("DecorationMenuToolTip")); 751 752 JCheckBoxMenuItem jcbmi; 753 754 // 755 // arrows menus 756 // 757 // arrows can only be added at edge connector 758 // 759 boolean hasEC1 = false; 760 if (getType1() == HitPointType.POS_POINT) { 761 PositionablePoint pp = (PositionablePoint) getConnect1(); 762 if (pp.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) { 763 hasEC1 = true; 764 } 765 } 766 boolean hasEC2 = false; 767 if (getType2() == HitPointType.POS_POINT) { 768 PositionablePoint pp = (PositionablePoint) getConnect2(); 769 if (pp.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) { 770 hasEC2 = true; 771 } 772 } 773 if (hasEC1 || hasEC2) { 774 JMenu arrowsMenu = new JMenu(Bundle.getMessage("ArrowsMenuTitle")); 775 decorationsMenu.setToolTipText(Bundle.getMessage("ArrowsMenuToolTip")); 776 decorationsMenu.add(arrowsMenu); 777 778 JMenu arrowsCountMenu = new JMenu(Bundle.getMessage("DecorationStyleMenuTitle")); 779 arrowsCountMenu.setToolTipText(Bundle.getMessage("DecorationStyleMenuToolTip")); 780 arrowsMenu.add(arrowsCountMenu); 781 782 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 783 arrowsCountMenu.add(jcbmi); 784 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 785 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 786 setArrowEndStart(false); 787 setArrowEndStop(false); 788 // setArrowStyle(0); 789 }); 790 jcbmi.setSelected(arrowStyle == 0); 791 792 // configure the arrows 793 for (int i = 1; i < NUM_ARROW_TYPES; i++) { 794 jcbmi = loadArrowImageToJCBItem(i, arrowsCountMenu); 795 final int n = i; 796 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 797 setArrowEndStart((getType1() == HitPointType.POS_POINT) && (((PositionablePoint) getConnect1()).getType() == PositionablePoint.PointType.EDGE_CONNECTOR)); 798 setArrowEndStop((getType2() == HitPointType.POS_POINT) && (((PositionablePoint) getConnect2()).getType() == PositionablePoint.PointType.EDGE_CONNECTOR)); 799 setArrowStyle(n); 800 }); 801 jcbmi.setSelected(arrowStyle == i); 802 } 803 804 if (hasEC1 && hasEC2) { 805 JMenu arrowsEndMenu = new JMenu(Bundle.getMessage("DecorationEndMenuTitle")); 806 arrowsEndMenu.setToolTipText(Bundle.getMessage("DecorationEndMenuToolTip")); 807 arrowsMenu.add(arrowsEndMenu); 808 809 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 810 arrowsEndMenu.add(jcbmi); 811 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 812 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 813 setArrowEndStart(false); 814 setArrowEndStop(false); 815 }); 816 jcbmi.setSelected(!arrowEndStart && !arrowEndStop); 817 818 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationStartMenuItemTitle")); 819 arrowsEndMenu.add(jcbmi); 820 jcbmi.setToolTipText(Bundle.getMessage("DecorationStartMenuItemToolTip")); 821 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 822 setArrowEndStart(true); 823 setArrowEndStop(false); 824 }); 825 jcbmi.setSelected(arrowEndStart && !arrowEndStop); 826 827 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationEndMenuItemTitle")); 828 arrowsEndMenu.add(jcbmi); 829 jcbmi.setToolTipText(Bundle.getMessage("DecorationEndMenuItemToolTip")); 830 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 831 setArrowEndStop(true); 832 setArrowEndStart(false); 833 }); 834 jcbmi.setSelected(!arrowEndStart && arrowEndStop); 835 836 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationBothMenuItemTitle")); 837 arrowsEndMenu.add(jcbmi); 838 jcbmi.setToolTipText(Bundle.getMessage("DecorationBothMenuItemToolTip")); 839 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 840 setArrowEndStart(true); 841 setArrowEndStop(true); 842 }); 843 jcbmi.setSelected(arrowEndStart && arrowEndStop); 844 } 845 846 JMenu arrowsDirMenu = new JMenu(Bundle.getMessage("ArrowsDirectionMenuTitle")); 847 arrowsDirMenu.setToolTipText(Bundle.getMessage("ArrowsDirectionMenuToolTip")); 848 arrowsMenu.add(arrowsDirMenu); 849 850 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 851 arrowsDirMenu.add(jcbmi); 852 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 853 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 854 setArrowDirIn(false); 855 setArrowDirOut(false); 856 }); 857 jcbmi.setSelected(!arrowDirIn && !arrowDirOut); 858 859 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("ArrowsDirectionInMenuItemTitle")); 860 arrowsDirMenu.add(jcbmi); 861 jcbmi.setToolTipText(Bundle.getMessage("ArrowsDirectionInMenuItemToolTip")); 862 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 863 setArrowDirIn(true); 864 setArrowDirOut(false); 865 }); 866 jcbmi.setSelected(arrowDirIn && !arrowDirOut); 867 868 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("ArrowsDirectionOutMenuItemTitle")); 869 arrowsDirMenu.add(jcbmi); 870 jcbmi.setToolTipText(Bundle.getMessage("ArrowsDirectionOutMenuItemToolTip")); 871 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 872 setArrowDirOut(true); 873 setArrowDirIn(false); 874 }); 875 jcbmi.setSelected(!arrowDirIn && arrowDirOut); 876 877 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("ArrowsDirectionBothMenuItemTitle")); 878 arrowsDirMenu.add(jcbmi); 879 jcbmi.setToolTipText(Bundle.getMessage("ArrowsDirectionBothMenuItemToolTip")); 880 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 881 setArrowDirIn(true); 882 setArrowDirOut(true); 883 }); 884 jcbmi.setSelected(arrowDirIn && arrowDirOut); 885 886 jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("DecorationColorMenuItemTitle"))); 887 jmi.setToolTipText(Bundle.getMessage("DecorationColorMenuItemToolTip")); 888 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 889 Color newColor = JmriColorChooser.showDialog(null, "Choose a color", arrowColor); 890 if ((newColor != null) && !newColor.equals(arrowColor)) { 891 setArrowColor(newColor); 892 } 893 }); 894 jmi.setForeground(arrowColor); 895 jmi.setBackground(ColorUtil.contrast(arrowColor)); 896 897 jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel", 898 Bundle.getMessage("DecorationLineWidthMenuItemTitle")) + arrowLineWidth)); 899 jmi.setToolTipText(Bundle.getMessage("DecorationLineWidthMenuItemToolTip")); 900 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 901 // prompt for arrow line width 902 int newValue = QuickPromptUtil.promptForInt(layoutEditor, 903 Bundle.getMessage("DecorationLineWidthMenuItemTitle"), 904 Bundle.getMessage("DecorationLineWidthMenuItemTitle"), 905 arrowLineWidth); 906 setArrowLineWidth(newValue); 907 }); 908 909 jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel", 910 Bundle.getMessage("DecorationLengthMenuItemTitle")) + arrowLength)); 911 jmi.setToolTipText(Bundle.getMessage("DecorationLengthMenuItemToolTip")); 912 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 913 // prompt for arrow length 914 int newValue = QuickPromptUtil.promptForInt(layoutEditor, 915 Bundle.getMessage("DecorationLengthMenuItemTitle"), 916 Bundle.getMessage("DecorationLengthMenuItemTitle"), 917 arrowLength); 918 setArrowLength(newValue); 919 }); 920 921 jmi = arrowsMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel", 922 Bundle.getMessage("DecorationGapMenuItemTitle")) + arrowGap)); 923 jmi.setToolTipText(Bundle.getMessage("DecorationGapMenuItemToolTip")); 924 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 925 // prompt for arrow gap 926 int newValue = QuickPromptUtil.promptForInt(layoutEditor, 927 Bundle.getMessage("DecorationGapMenuItemTitle"), 928 Bundle.getMessage("DecorationGapMenuItemTitle"), 929 arrowGap); 930 setArrowGap(newValue); 931 }); 932 } 933 934 // 935 // bridge menus 936 // 937 JMenu bridgeMenu = new JMenu(Bundle.getMessage("BridgeMenuTitle")); 938 decorationsMenu.setToolTipText(Bundle.getMessage("BridgeMenuToolTip")); 939 decorationsMenu.add(bridgeMenu); 940 941 JMenu bridgeSideMenu = new JMenu(Bundle.getMessage("DecorationSideMenuTitle")); 942 bridgeSideMenu.setToolTipText(Bundle.getMessage("DecorationSideMenuToolTip")); 943 bridgeMenu.add(bridgeSideMenu); 944 945 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 946 bridgeSideMenu.add(jcbmi); 947 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 948 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 949 setBridgeSideLeft(false); 950 setBridgeSideRight(false); 951 }); 952 jcbmi.setSelected(!bridgeSideLeft && !bridgeSideRight); 953 954 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationSideLeftMenuItemTitle")); 955 bridgeSideMenu.add(jcbmi); 956 jcbmi.setToolTipText(Bundle.getMessage("DecorationSideLeftMenuItemToolTip")); 957 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 958 setBridgeSideLeft(true); 959 setBridgeSideRight(false); 960 }); 961 jcbmi.setSelected(bridgeSideLeft && !bridgeSideRight); 962 963 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationSideRightMenuItemTitle")); 964 bridgeSideMenu.add(jcbmi); 965 jcbmi.setToolTipText(Bundle.getMessage("DecorationSideRightMenuItemToolTip")); 966 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 967 setBridgeSideRight(true); 968 setBridgeSideLeft(false); 969 }); 970 jcbmi.setSelected(!bridgeSideLeft && bridgeSideRight); 971 972 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationBothMenuItemTitle")); 973 bridgeSideMenu.add(jcbmi); 974 jcbmi.setToolTipText(Bundle.getMessage("DecorationBothMenuItemToolTip")); 975 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 976 setBridgeSideLeft(true); 977 setBridgeSideRight(true); 978 }); 979 jcbmi.setSelected(bridgeSideLeft && bridgeSideRight); 980 981 JMenu bridgeEndMenu = new JMenu(Bundle.getMessage("DecorationEndMenuTitle")); 982 bridgeEndMenu.setToolTipText(Bundle.getMessage("DecorationEndMenuToolTip")); 983 bridgeMenu.add(bridgeEndMenu); 984 985 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 986 bridgeEndMenu.add(jcbmi); 987 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 988 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 989 setBridgeHasEntry(false); 990 setBridgeHasExit(false); 991 }); 992 jcbmi.setSelected(!bridgeHasEntry && !bridgeHasExit); 993 994 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationEntryMenuItemTitle")); 995 bridgeEndMenu.add(jcbmi); 996 jcbmi.setToolTipText(Bundle.getMessage("DecorationEntryMenuItemToolTip")); 997 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 998 setBridgeHasEntry(true); 999 setBridgeHasExit(false); 1000 }); 1001 jcbmi.setSelected(bridgeHasEntry && !bridgeHasExit); 1002 1003 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationExitMenuItemTitle")); 1004 bridgeEndMenu.add(jcbmi); 1005 jcbmi.setToolTipText(Bundle.getMessage("DecorationExitMenuItemToolTip")); 1006 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1007 setBridgeHasExit(true); 1008 setBridgeHasEntry(false); 1009 }); 1010 jcbmi.setSelected(!bridgeHasEntry && bridgeHasExit); 1011 1012 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationBothMenuItemTitle")); 1013 bridgeEndMenu.add(jcbmi); 1014 jcbmi.setToolTipText(Bundle.getMessage("DecorationBothMenuItemToolTip")); 1015 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1016 setBridgeHasEntry(true); 1017 setBridgeHasExit(true); 1018 }); 1019 jcbmi.setSelected(bridgeHasEntry && bridgeHasExit); 1020 1021 jmi = bridgeMenu.add(new JMenuItem(Bundle.getMessage("DecorationColorMenuItemTitle"))); 1022 jmi.setToolTipText(Bundle.getMessage("DecorationColorMenuItemToolTip")); 1023 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1024 Color newColor = JmriColorChooser.showDialog(null, "Choose a color", bridgeColor); 1025 if ((newColor != null) && !newColor.equals(bridgeColor)) { 1026 setBridgeColor(newColor); 1027 } 1028 }); 1029 jmi.setForeground(bridgeColor); 1030 jmi.setBackground(ColorUtil.contrast(bridgeColor)); 1031 1032 addNumericMenuItem(bridgeMenu, 1033 "DecorationLineWidthMenuItemTitle", "DecorationLineWidthMenuItemToolTip", 1034 this::getBridgeLineWidth, this::setBridgeLineWidth, 1035 QuickPromptUtil.checkIntRange(1, MAX_BRIDGE_LINE_WIDTH, null)); 1036 1037 addNumericMenuItem(bridgeMenu, 1038 "BridgeApproachWidthMenuItemTitle", "BridgeApproachWidthMenuItemToolTip", 1039 this::getBridgeApproachWidth, this::setBridgeApproachWidth, 1040 QuickPromptUtil.checkIntRange(4, MAX_BRIDGE_APPROACH_WIDTH, null)); 1041 1042 addNumericMenuItem(bridgeMenu, 1043 "BridgeDeckWidthMenuItemTitle", "BridgeDeckWidthMenuItemToolTip", 1044 this::getBridgeDeckWidth, this::setBridgeDeckWidth, 1045 QuickPromptUtil.checkIntRange(1, MAX_BRIDGE_DECK_WIDTH, null)); 1046 1047 // 1048 // end bumper menus 1049 // 1050 // end bumper decorations can only be on end bumpers 1051 // 1052 boolean hasEB1 = false; 1053 if (getType1() == HitPointType.POS_POINT) { 1054 PositionablePoint pp = (PositionablePoint) getConnect1(); 1055 if (pp.getType() == PositionablePoint.PointType.END_BUMPER) { 1056 hasEB1 = true; 1057 } 1058 } 1059 boolean hasEB2 = false; 1060 if (getType2() == HitPointType.POS_POINT) { 1061 PositionablePoint pp = (PositionablePoint) getConnect2(); 1062 if (pp.getType() == PositionablePoint.PointType.END_BUMPER) { 1063 hasEB2 = true; 1064 } 1065 } 1066 if (hasEB1 || hasEB2) { 1067 JMenu endBumperMenu = new JMenu(Bundle.getMessage("EndBumperMenuTitle")); 1068 decorationsMenu.setToolTipText(Bundle.getMessage("EndBumperMenuToolTip")); 1069 decorationsMenu.add(endBumperMenu); 1070 1071 if (hasEB1 && hasEB2) { 1072 JMenu endBumperEndMenu = new JMenu(Bundle.getMessage("DecorationEndMenuTitle")); 1073 endBumperEndMenu.setToolTipText(Bundle.getMessage("DecorationEndMenuToolTip")); 1074 endBumperMenu.add(endBumperEndMenu); 1075 1076 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 1077 endBumperEndMenu.add(jcbmi); 1078 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 1079 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1080 setBumperEndStart(false); 1081 setBumperEndStop(false); 1082 }); 1083 jcbmi.setSelected(!bumperEndStart && !bumperEndStop); 1084 1085 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationStartMenuItemTitle")); 1086 endBumperEndMenu.add(jcbmi); 1087 jcbmi.setToolTipText(Bundle.getMessage("DecorationStartMenuItemToolTip")); 1088 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1089 setBumperEndStart(true); 1090 setBumperEndStop(false); 1091 }); 1092 jcbmi.setSelected(bumperEndStart && !bumperEndStop); 1093 1094 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationEndMenuItemTitle")); 1095 endBumperEndMenu.add(jcbmi); 1096 jcbmi.setToolTipText(Bundle.getMessage("DecorationEndMenuItemToolTip")); 1097 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1098 setBumperEndStart(false); 1099 setBumperEndStop(true); 1100 }); 1101 jcbmi.setSelected(!bumperEndStart && bumperEndStop); 1102 1103 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationBothMenuItemTitle")); 1104 endBumperEndMenu.add(jcbmi); 1105 jcbmi.setToolTipText(Bundle.getMessage("DecorationEndMenuItemToolTip")); 1106 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1107 setBumperEndStart(true); 1108 setBumperEndStop(true); 1109 }); 1110 jcbmi.setSelected(bumperEndStart && bumperEndStop); 1111 } else { 1112 JCheckBoxMenuItem enableCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("EndBumperEnableMenuItemTitle")); 1113 enableCheckBoxMenuItem.setToolTipText(Bundle.getMessage("EndBumperEnableMenuItemToolTip")); 1114 1115 endBumperMenu.add(enableCheckBoxMenuItem); 1116 enableCheckBoxMenuItem.addActionListener((java.awt.event.ActionEvent e3) -> { 1117 if ((getType1() == HitPointType.POS_POINT) && (((PositionablePoint) getConnect1()).getType() == PositionablePoint.PointType.END_BUMPER)) { 1118 setBumperEndStart(enableCheckBoxMenuItem.isSelected()); 1119 } 1120 if ((getType2() == HitPointType.POS_POINT) && (((PositionablePoint) getConnect2()).getType() == PositionablePoint.PointType.END_BUMPER)) { 1121 setBumperEndStop(enableCheckBoxMenuItem.isSelected()); 1122 } 1123 }); 1124 enableCheckBoxMenuItem.setSelected(bumperEndStart || bumperEndStop); 1125 } 1126 1127 jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("DecorationColorMenuItemTitle"))); 1128 jmi.setToolTipText(Bundle.getMessage("DecorationColorMenuItemToolTip")); 1129 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1130 Color newColor = JmriColorChooser.showDialog(null, "Choose a color", bumperColor); 1131 if ((newColor != null) && !newColor.equals(bumperColor)) { 1132 setBumperColor(newColor); 1133 } 1134 }); 1135 jmi.setForeground(bumperColor); 1136 jmi.setBackground(ColorUtil.contrast(bumperColor)); 1137 1138 jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel", 1139 Bundle.getMessage("DecorationLineWidthMenuItemTitle")) + bumperLineWidth)); 1140 jmi.setToolTipText(Bundle.getMessage("DecorationLineWidthMenuItemToolTip")); 1141 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1142 // prompt for width 1143 int newValue = QuickPromptUtil.promptForInteger(layoutEditor, 1144 Bundle.getMessage("DecorationLineWidthMenuItemTitle"), 1145 Bundle.getMessage("DecorationLineWidthMenuItemTitle"), 1146 getBumperLineWidth(), t -> { 1147 if (t < 0 || t > MAX_BUMPER_WIDTH) { 1148 throw new IllegalArgumentException( 1149 Bundle.getMessage("DecorationLengthMenuItemRange", MAX_BUMPER_WIDTH)); 1150 } 1151 return true; 1152 }); 1153 setBumperLineWidth(newValue); 1154 }); 1155 1156 jmi = endBumperMenu.add(new JMenuItem(Bundle.getMessage("MakeLabel", 1157 Bundle.getMessage("DecorationLengthMenuItemTitle")) + bumperLength)); 1158 jmi.setToolTipText(Bundle.getMessage("DecorationLengthMenuItemToolTip")); 1159 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1160 // prompt for length 1161 int newValue = QuickPromptUtil.promptForInteger(layoutEditor, 1162 Bundle.getMessage("DecorationLengthMenuItemTitle"), 1163 Bundle.getMessage("DecorationLengthMenuItemTitle"), 1164 bumperLength, t -> { 1165 if (t < 0 || t > MAX_BUMPER_LENGTH) { 1166 throw new IllegalArgumentException( 1167 Bundle.getMessage("DecorationLengthMenuItemRange", MAX_BUMPER_LENGTH)); 1168 } 1169 return true; 1170 }); 1171 setBumperLength(newValue); 1172 }); 1173 } 1174 1175 // 1176 // tunnel menus 1177 // 1178 JMenu tunnelMenu = new JMenu(Bundle.getMessage("TunnelMenuTitle")); 1179 decorationsMenu.setToolTipText(Bundle.getMessage("TunnelMenuToolTip")); 1180 decorationsMenu.add(tunnelMenu); 1181 1182 JMenu tunnelSideMenu = new JMenu(Bundle.getMessage("DecorationSideMenuTitle")); 1183 tunnelSideMenu.setToolTipText(Bundle.getMessage("DecorationSideMenuToolTip")); 1184 tunnelMenu.add(tunnelSideMenu); 1185 1186 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 1187 tunnelSideMenu.add(jcbmi); 1188 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 1189 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1190 setTunnelSideLeft(false); 1191 setTunnelSideRight(false); 1192 }); 1193 jcbmi.setSelected(!tunnelSideLeft && !tunnelSideRight); 1194 1195 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationSideLeftMenuItemTitle")); 1196 tunnelSideMenu.add(jcbmi); 1197 jcbmi.setToolTipText(Bundle.getMessage("DecorationSideLeftMenuItemToolTip")); 1198 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1199 setTunnelSideLeft(true); 1200 setTunnelSideRight(false); 1201 }); 1202 jcbmi.setSelected(tunnelSideLeft && !tunnelSideRight); 1203 1204 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationSideRightMenuItemTitle")); 1205 tunnelSideMenu.add(jcbmi); 1206 jcbmi.setToolTipText(Bundle.getMessage("DecorationSideRightMenuItemToolTip")); 1207 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1208 setTunnelSideRight(true); 1209 setTunnelSideLeft(false); 1210 }); 1211 jcbmi.setSelected(!tunnelSideLeft && tunnelSideRight); 1212 1213 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationBothMenuItemTitle")); 1214 tunnelSideMenu.add(jcbmi); 1215 jcbmi.setToolTipText(Bundle.getMessage("DecorationBothMenuItemToolTip")); 1216 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1217 setTunnelSideLeft(true); 1218 setTunnelSideRight(true); 1219 }); 1220 jcbmi.setSelected(tunnelSideLeft && tunnelSideRight); 1221 1222 JMenu tunnelEndMenu = new JMenu(Bundle.getMessage("DecorationEndMenuTitle")); 1223 tunnelEndMenu.setToolTipText(Bundle.getMessage("DecorationEndMenuToolTip")); 1224 tunnelMenu.add(tunnelEndMenu); 1225 1226 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationNoneMenuItemTitle")); 1227 tunnelEndMenu.add(jcbmi); 1228 jcbmi.setToolTipText(Bundle.getMessage("DecorationNoneMenuItemToolTip")); 1229 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1230 setTunnelHasEntry(false); 1231 setTunnelHasExit(false); 1232 }); 1233 jcbmi.setSelected(!tunnelHasEntry && !tunnelHasExit); 1234 1235 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationEntryMenuItemTitle")); 1236 tunnelEndMenu.add(jcbmi); 1237 jcbmi.setToolTipText(Bundle.getMessage("DecorationEntryMenuItemToolTip")); 1238 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1239 setTunnelHasEntry(true); 1240 setTunnelHasExit(false); 1241 }); 1242 jcbmi.setSelected(tunnelHasEntry && !tunnelHasExit); 1243 1244 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationExitMenuItemTitle")); 1245 tunnelEndMenu.add(jcbmi); 1246 jcbmi.setToolTipText(Bundle.getMessage("DecorationExitMenuItemToolTip")); 1247 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1248 setTunnelHasExit(true); 1249 setTunnelHasEntry(false); 1250 }); 1251 jcbmi.setSelected(!tunnelHasEntry && tunnelHasExit); 1252 1253 jcbmi = new JCheckBoxMenuItem(Bundle.getMessage("DecorationBothMenuItemTitle")); 1254 tunnelEndMenu.add(jcbmi); 1255 jcbmi.setToolTipText(Bundle.getMessage("DecorationBothMenuItemToolTip")); 1256 jcbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1257 setTunnelHasEntry(true); 1258 setTunnelHasExit(true); 1259 }); 1260 jcbmi.setSelected(tunnelHasEntry && tunnelHasExit); 1261 1262 jmi = tunnelMenu.add(new JMenuItem(Bundle.getMessage("DecorationColorMenuItemTitle"))); 1263 jmi.setToolTipText(Bundle.getMessage("DecorationColorMenuItemToolTip")); 1264 jmi.addActionListener((java.awt.event.ActionEvent e3) -> { 1265 Color newColor = JmriColorChooser.showDialog(null, "Choose a color", tunnelColor); 1266 if ((newColor != null) && !newColor.equals(tunnelColor)) { 1267 setTunnelColor(newColor); 1268 } 1269 }); 1270 jmi.setForeground(tunnelColor); 1271 jmi.setBackground(ColorUtil.contrast(tunnelColor)); 1272 1273 addNumericMenuItem(tunnelMenu, 1274 "TunnelFloorWidthMenuItemTitle", "TunnelFloorWidthMenuItemToolTip", 1275 this::getTunnelFloorWidth, this::setTunnelFloorWidth, 1276 QuickPromptUtil.checkIntRange(1, MAX_TUNNEL_FLOOR_WIDTH, null)); 1277 addNumericMenuItem(tunnelMenu, 1278 "DecorationLineWidthMenuItemTitle", "DecorationLineWidthMenuItemToolTip", 1279 this::getTunnelLineWidth, this::setTunnelLineWidth, 1280 QuickPromptUtil.checkIntRange(1, MAX_TUNNEL_LINE_WIDTH, null)); 1281 addNumericMenuItem(tunnelMenu, 1282 "TunnelEntranceWidthMenuItemTitle", "TunnelEntranceWidthMenuItemToolTip", 1283 this::getTunnelEntranceWidth, this::setTunnelEntranceWidth, 1284 QuickPromptUtil.checkIntRange(1, MAX_TUNNEL_ENTRANCE_WIDTH, null)); 1285 1286 popupMenu.add(decorationsMenu); 1287 1288 popupMenu.add(new JSeparator(JSeparator.HORIZONTAL)); 1289 popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonEdit")) { 1290 @Override 1291 public void actionPerformed(ActionEvent e) { 1292 editor.editLayoutTrack(TrackSegmentView.this); 1293 } 1294 }); 1295 popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) { 1296 @Override 1297 public void actionPerformed(ActionEvent e) { 1298 if (canRemove() && removeInlineLogixNG()) { 1299 layoutEditor.removeTrackSegment(trackSegment); 1300 remove(); 1301 dispose(); 1302 } 1303 } 1304 }); 1305 popupMenu.add(new AbstractAction(Bundle.getMessage("SplitTrackSegment")) { 1306 @Override 1307 public void actionPerformed(ActionEvent e) { 1308 splitTrackSegment(); 1309 } 1310 }); 1311 1312 JMenu lineType = new JMenu(Bundle.getMessage("ChangeTo")); 1313 jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("Line")) { 1314 @Override 1315 public void actionPerformed(ActionEvent e) { 1316 changeType(0); 1317 } 1318 })); 1319 jmi.setSelected(!isArc() && !isBezier()); 1320 1321 jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("Circle")) { 1322 @Override 1323 public void actionPerformed(ActionEvent e) { 1324 changeType(1); 1325 } 1326 })); 1327 jmi.setSelected(isArc() && isCircle()); 1328 1329 jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("Ellipse")) { 1330 @Override 1331 public void actionPerformed(ActionEvent e) { 1332 changeType(2); 1333 } 1334 })); 1335 jmi.setSelected(isArc() && !isCircle()); 1336 1337 jmi = lineType.add(new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("Bezier")) { 1338 @Override 1339 public void actionPerformed(ActionEvent e) { 1340 changeType(3); 1341 } 1342 })); 1343 jmi.setSelected(!isArc() && isBezier()); 1344 1345 popupMenu.add(lineType); 1346 1347 if (isArc() || isBezier()) { 1348 if (hideConstructionLines()) { 1349 popupMenu.add(new AbstractAction(Bundle.getMessage("ShowConstruct")) { 1350 @Override 1351 public void actionPerformed(ActionEvent e) { 1352 hideConstructionLines(SHOWCON); 1353 } 1354 }); 1355 } else { 1356 popupMenu.add(new AbstractAction(Bundle.getMessage("HideConstruct")) { 1357 @Override 1358 public void actionPerformed(ActionEvent e) { 1359 hideConstructionLines(HIDECON); 1360 } 1361 }); 1362 } 1363 } 1364 if ((!getBlockName().isEmpty()) && (jmri.InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled())) { 1365 popupMenu.add(new AbstractAction(Bundle.getMessage("ViewBlockRouting")) { 1366 @Override 1367 public void actionPerformed(ActionEvent e) { 1368 AbstractAction routeTableAction = new LayoutBlockRouteTableAction("ViewRouting", getLayoutBlock()); 1369 routeTableAction.actionPerformed(e); 1370 } 1371 }); 1372 } 1373 addCommonPopupItems(mouseEvent, popupMenu); 1374 popupMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY()); 1375 return popupMenu; 1376 } // showPopup 1377 1378 /** 1379 * {@inheritDoc} 1380 */ 1381 @Override 1382 public boolean canRemove() { 1383 List<String> itemList = new ArrayList<>(); 1384 1385 HitPointType type1Temp = getType1(); 1386 LayoutTrack conn1Temp = getConnect1(); 1387 itemList.addAll(getPointReferences(type1Temp, conn1Temp)); 1388 1389 HitPointType type2Temp = getType2(); 1390 LayoutTrack conn2Temp = getConnect2(); 1391 itemList.addAll(getPointReferences(type2Temp, conn2Temp)); 1392 1393 if (!itemList.isEmpty()) { 1394 displayRemoveWarningDialog(itemList, "TrackSegment"); // NOI18N 1395 } 1396 return itemList.isEmpty(); 1397 } 1398 1399 public ArrayList<String> getPointReferences(HitPointType type, LayoutTrack conn) { 1400 ArrayList<String> result = new ArrayList<>(); 1401 1402 if (type == HitPointType.POS_POINT && conn instanceof PositionablePoint) { 1403 PositionablePoint pt = (PositionablePoint) conn; 1404 if (!pt.getEastBoundSignal().isEmpty()) { 1405 result.add(pt.getEastBoundSignal()); 1406 } 1407 if (!pt.getWestBoundSignal().isEmpty()) { 1408 result.add(pt.getWestBoundSignal()); 1409 } 1410 if (!pt.getEastBoundSignalMastName().isEmpty()) { 1411 result.add(pt.getEastBoundSignalMastName()); 1412 } 1413 if (!pt.getWestBoundSignalMastName().isEmpty()) { 1414 result.add(pt.getWestBoundSignalMastName()); 1415 } 1416 if (!pt.getEastBoundSensorName().isEmpty()) { 1417 result.add(pt.getEastBoundSensorName()); 1418 } 1419 if (!pt.getWestBoundSensorName().isEmpty()) { 1420 result.add(pt.getWestBoundSensorName()); 1421 } 1422 if (pt.getType() == PositionablePoint.PointType.EDGE_CONNECTOR && pt.getLinkedPoint() != null) { 1423 result.add(Bundle.getMessage("DeleteECisActive")); // NOI18N 1424 } 1425 } 1426 1427 if (HitPointType.isTurnoutHitType(type) && conn instanceof LayoutTurnout) { 1428 LayoutTurnout lt = (LayoutTurnout) conn; 1429 switch (type) { 1430 case TURNOUT_A: { 1431 result = lt.getBeanReferences("A"); // NOI18N 1432 break; 1433 } 1434 case TURNOUT_B: { 1435 result = lt.getBeanReferences("B"); // NOI18N 1436 break; 1437 } 1438 case TURNOUT_C: { 1439 result = lt.getBeanReferences("C"); // NOI18N 1440 break; 1441 } 1442 case TURNOUT_D: { 1443 result = lt.getBeanReferences("D"); // NOI18N 1444 break; 1445 } 1446 default: { 1447 log.error("Unexpected HitPointType: {}", type); 1448 } 1449 } 1450 } 1451 1452 if (HitPointType.isLevelXingHitType(type) && conn instanceof LevelXing) { 1453 LevelXing lx = (LevelXing) conn; 1454 switch (type) { 1455 case LEVEL_XING_A: { 1456 result = lx.getBeanReferences("A"); // NOI18N 1457 break; 1458 } 1459 case LEVEL_XING_B: { 1460 result = lx.getBeanReferences("B"); // NOI18N 1461 break; 1462 } 1463 case LEVEL_XING_C: { 1464 result = lx.getBeanReferences("C"); // NOI18N 1465 break; 1466 } 1467 case LEVEL_XING_D: { 1468 result = lx.getBeanReferences("D"); // NOI18N 1469 break; 1470 } 1471 default: { 1472 log.error("Unexpected HitPointType: {}", type); 1473 } 1474 } 1475 } 1476 1477 if (HitPointType.isSlipHitType(type) && conn instanceof LayoutSlip) { 1478 LayoutSlip ls = (LayoutSlip) conn; 1479 switch (type) { 1480 case SLIP_A: { 1481 result = ls.getBeanReferences("A"); // NOI18N 1482 break; 1483 } 1484 case SLIP_B: { 1485 result = ls.getBeanReferences("B"); // NOI18N 1486 break; 1487 } 1488 case SLIP_C: { 1489 result = ls.getBeanReferences("C"); // NOI18N 1490 break; 1491 } 1492 case SLIP_D: { 1493 result = ls.getBeanReferences("D"); // NOI18N 1494 break; 1495 } 1496 default: { 1497 log.error("Unexpected HitPointType: {}", type); 1498 } 1499 } 1500 } 1501 1502 return result; 1503 } 1504 1505 /** 1506 * split a track segment into two track segments with an anchor point in between. 1507 */ 1508 public void splitTrackSegment() { 1509 // create a new anchor 1510 Point2D p = getCentreSeg(); 1511 PositionablePoint newAnchor = layoutEditor.addAnchor(p); 1512 // link it to me 1513 layoutEditor.setLink(newAnchor, HitPointType.POS_POINT, trackSegment, HitPointType.TRACK); 1514 1515 // get unique name for a new track segment 1516 String name = layoutEditor.getFinder().uniqueName("T", 1); 1517 1518 // create it between the new anchor and my getConnect2()(/type2) 1519 TrackSegment newTrackSegment = new TrackSegment(name, 1520 newAnchor, HitPointType.POS_POINT, 1521 getConnect2(), getType2(), 1522 trackSegment.isMainline(), layoutEditor); 1523 TrackSegmentView ntsv = new TrackSegmentView(newTrackSegment, 1524 layoutEditor); 1525 // add it to known tracks 1526 layoutEditor.addLayoutTrack(newTrackSegment, ntsv); 1527 layoutEditor.setDirty(); 1528 1529 // copy attributes to new track segment 1530 newTrackSegment.setLayoutBlock(this.getLayoutBlock()); 1531 ntsv.setArc(this.isArc()); 1532 ntsv.setCircle(this.isCircle()); 1533 // split any angle between the two new track segments 1534 ntsv.setAngle(this.getAngle() / 2.0); 1535 this.setAngle(this.getAngle() / 2.0); 1536 // newTrackSegment.setBezier(this.isBezier()); 1537 ntsv.setFlip(this.isFlip()); 1538 ntsv.setDashed(this.isDashed()); 1539 1540 // copy over decorations 1541 Map<String, String> d = new HashMap<>(); 1542 this.getDecorations().forEach((k, v) -> { 1543 if (k.equals("arrow")) { // if this is an arrow 1544 if (this.isArrowEndStop()) { // and it's on the stop end 1545 d.put(k, v); // copy it to new track 1546 this.setArrowEndStop(false); // and remove it from this track 1547 } 1548 } else if (k.equals("bumper")) { // if this is an end bumper 1549 if (this.isBumperEndStop()) { // amd it's on the stop end 1550 d.put(k, v); // copy it to new track 1551 this.setBumperEndStop(false); // and remove it from this track 1552 } 1553 } else { // otherwise... 1554 d.put(k, v); // copy to new track 1555 } 1556 }); 1557 ntsv.setDecorations(d); 1558 1559 // link my getConnect2() to the new track segment 1560 if (getConnect2() instanceof PositionablePoint) { 1561 PositionablePoint pp = (PositionablePoint) getConnect2(); 1562 pp.replaceTrackConnection(trackSegment, newTrackSegment); 1563 } else { 1564 layoutEditor.setLink(getConnect2(), getType2(), newTrackSegment, HitPointType.TRACK); 1565 } 1566 1567 // link the new anchor to the new track segment 1568 layoutEditor.setLink(newAnchor, HitPointType.POS_POINT, newTrackSegment, HitPointType.TRACK); 1569 1570 // link me to the new newAnchor 1571 trackSegment.setConnect2(newAnchor, HitPointType.POS_POINT); 1572 1573 // check on layout block 1574 LayoutBlock b = this.getLayoutBlock(); 1575 1576 if (b != null) { 1577 newTrackSegment.setLayoutBlock(b); 1578 layoutEditor.getLEAuxTools().setBlockConnectivityChanged(); 1579 newTrackSegment.updateBlockInfo(); 1580 } 1581 layoutEditor.setDirty(); 1582 layoutEditor.redrawPanel(); 1583 } // splitTrackSegment 1584 1585 /** 1586 * Display popup menu for information and editing. 1587 * 1588 * @param e The original event causing this 1589 * @param hitPointType the type of the underlying hit 1590 */ 1591 protected void showBezierPopUp(JmriMouseEvent e, HitPointType hitPointType) { 1592 int bezierControlPointIndex = hitPointType.bezierPointIndex(); 1593 if (popupMenu != null) { 1594 popupMenu.removeAll(); 1595 } else { 1596 popupMenu = new JPopupMenu(); 1597 } 1598 1599 JMenuItem jmi = popupMenu.add(Bundle.getMessage("BezierControlPoint") + " #" + bezierControlPointIndex); 1600 jmi.setEnabled(false); 1601 popupMenu.add(new JSeparator(JSeparator.HORIZONTAL)); 1602 1603 if (bezierControlPoints.size() <= HitPointType.NUM_BEZIER_CONTROL_POINTS) { 1604 popupMenu.add(new AbstractAction(Bundle.getMessage("AddBezierControlPointAfter")) { 1605 1606 @Override 1607 public void actionPerformed(ActionEvent e) { 1608 addBezierControlPointAfter(bezierControlPointIndex); 1609 } 1610 }); 1611 popupMenu.add(new AbstractAction(Bundle.getMessage("AddBezierControlPointBefore")) { 1612 1613 @Override 1614 public void actionPerformed(ActionEvent e) { 1615 addBezierControlPointBefore(bezierControlPointIndex); 1616 } 1617 }); 1618 } 1619 1620 if (bezierControlPoints.size() > 2) { 1621 popupMenu.add(new AbstractAction(Bundle.getMessage("DeleteBezierControlPoint") + " #" + bezierControlPointIndex) { 1622 1623 @Override 1624 public void actionPerformed(ActionEvent e) { 1625 deleteBezierControlPoint(bezierControlPointIndex); 1626 } 1627 }); 1628 } 1629 addCommonPopupItems(e, popupMenu); 1630 popupMenu.show(e.getComponent(), e.getX(), e.getY()); 1631 } 1632 1633 private void addBezierControlPointBefore(int index) { 1634 Point2D addPoint = getBezierControlPoint(index); 1635 if (index > 0) { 1636 addPoint = MathUtil.midPoint(getBezierControlPoint(index - 1), addPoint); 1637 } else { 1638 Point2D ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 1639 addPoint = MathUtil.midPoint(ep1, addPoint); 1640 } 1641 bezierControlPoints.add(index, addPoint); 1642 layoutEditor.redrawPanel(); 1643 layoutEditor.setDirty(); 1644 } 1645 1646 private void addBezierControlPointAfter(int index) { 1647 int cnt = bezierControlPoints.size(); 1648 Point2D addPoint = getBezierControlPoint(index); 1649 if (index < cnt - 1) { 1650 addPoint = MathUtil.midPoint(addPoint, getBezierControlPoint(index + 1)); 1651 bezierControlPoints.add(index + 1, addPoint); 1652 } else { 1653 Point2D ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 1654 addPoint = MathUtil.midPoint(addPoint, ep2); 1655 bezierControlPoints.add(addPoint); 1656 } 1657 layoutEditor.redrawPanel(); 1658 layoutEditor.setDirty(); 1659 } 1660 1661 private void deleteBezierControlPoint(int index) { 1662 if ((index >= 0) && (index < bezierControlPoints.size())) { 1663 bezierControlPoints.remove(index); 1664 layoutEditor.redrawPanel(); 1665 layoutEditor.setDirty(); 1666 } 1667 } 1668 1669 void changeType(int choice) { 1670 switch (choice) { 1671 case 0: // plain track segment (line) 1672 setArc(false); 1673 setAngle(0.0D); 1674 setCircle(false); 1675 setBezier(false); 1676 break; 1677 case 1: // circle 1678 setCircle(true); 1679 setArc(true); 1680// setAngle(90.0D); 1681// setBezier(false); // this is done in setCircle 1682 break; 1683 case 2: // arc 1684 setArc(true); 1685 setAngle(90.0D); 1686 setCircle(false); 1687 setBezier(false); 1688 break; 1689 case 3: 1690 setArc(false); // bezier 1691 setCircle(false); 1692 if (bezierControlPoints.size() == 0) { 1693 // TODO: Use MathUtil.intersect to find intersection of adjacent tracks 1694 // TODO: and place the control points halfway between that and the two endpoints 1695 1696 // set default control point displacements 1697 Point2D ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 1698 Point2D ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 1699 1700 // compute orthogonal offset0 with length one third the distance from ep1 to ep2 1701 Point2D offset = MathUtil.subtract(ep2, ep1); 1702 offset = MathUtil.normalize(offset, MathUtil.length(offset) / 3.0); 1703 offset = MathUtil.orthogonal(offset); 1704 1705 // add & subtract orthogonal offset0 to 1/3rd and 2/3rd points 1706 Point2D pt1 = MathUtil.add(MathUtil.oneThirdPoint(ep1, ep2), offset); 1707 Point2D pt2 = MathUtil.subtract(MathUtil.twoThirdsPoint(ep1, ep2), offset); 1708 1709 bezierControlPoints.add(pt1); 1710 bezierControlPoints.add(pt2); 1711 } 1712 setBezier(true); // do this last (it calls reCenter()) 1713 break; 1714 default: 1715 break; 1716 } 1717 layoutEditor.redrawPanel(); 1718 layoutEditor.setDirty(); 1719 } 1720 1721 /** 1722 * Clean up when this object is no longer needed. 1723 * <p> 1724 * Should not be called while the object is still displayed. 1725 * 1726 * @see #remove() 1727 */ 1728 public void dispose() { 1729 if (popupMenu != null) { 1730 popupMenu.removeAll(); 1731 } 1732 popupMenu = null; 1733 } 1734 1735 /** 1736 * Remove this object from display and persistance. 1737 */ 1738 public void remove() { 1739 // remove from persistance by flagging inactive 1740 active = false; 1741 } 1742 1743 private boolean active = true; 1744 1745 /** 1746 * Get state. "active" means that the object is still displayed, and should 1747 * be stored. 1748 * 1749 * @return true if active 1750 */ 1751 public boolean isActive() { 1752 return active; 1753 } 1754 1755 public static final int SHOWCON = 0x01; 1756 public static final int HIDECON = 0x02; // flag set on a segment basis. 1757 public static final int HIDECONALL = 0x04; // Used by layout editor for hiding all 1758 1759 public int showConstructionLine = SHOWCON; 1760 1761 /** 1762 * @return true if HIDECON is not set and HIDECONALL is not set 1763 */ 1764 public boolean isShowConstructionLines() { 1765 return (((showConstructionLine & HIDECON) != HIDECON) 1766 && ((showConstructionLine & HIDECONALL) != HIDECONALL)); 1767 } 1768 1769 /** 1770 * Method used by LayoutEditor. 1771 * <p> 1772 * If the argument is 1773 * <ul> 1774 * <li>HIDECONALL then set HIDECONALL 1775 * <li>SHOWCON reset HIDECONALL is set, other wise set SHOWCON 1776 * <li>HIDECON or otherwise set HIDECON 1777 * </ul> 1778 * Then always redraw the LayoutEditor panel and set it dirty. 1779 * 1780 * @param hide The specification i.e. HIDECONALL, SHOWCON et al 1781 */ 1782 public void hideConstructionLines(int hide) { 1783 if (hide == HIDECONALL) { 1784 showConstructionLine |= HIDECONALL; 1785 } else if (hide == SHOWCON) { 1786 if ((showConstructionLine & HIDECONALL) == HIDECONALL) { 1787 showConstructionLine &= ~HIDECONALL; 1788 } else { 1789 showConstructionLine = hide; 1790 } 1791 } else { 1792 showConstructionLine = HIDECON; 1793 } 1794 layoutEditor.redrawPanel(); 1795 layoutEditor.setDirty(); 1796 } 1797 1798 /** 1799 * @return true if SHOWCON is not set 1800 */ 1801 public boolean hideConstructionLines() { 1802 return ((showConstructionLine & SHOWCON) != SHOWCON); 1803 } 1804 1805 /** 1806 * The following are used only as a local store after a circle or arc has 1807 * been calculated. This prevents the need to recalculate the values each 1808 * time a re-draw is required. 1809 */ 1810 private Point2D pt1; 1811 private Point2D pt2; 1812 1813 public Point2D getTmpPt1() { 1814 return pt1; 1815 } 1816 1817 public Point2D getTmpPt2() { 1818 return pt2; 1819 } 1820 1821 public void setTmpPt1(Point2D Pt1) { 1822 pt1 = Pt1; 1823 changed = true; 1824 } 1825 1826 public void setTmpPt2(Point2D Pt2) { 1827 pt2 = Pt2; 1828 changed = true; 1829 } 1830 1831 private double cX; 1832 1833 public double getCX() { 1834 return cX; 1835 } 1836 1837 public void setCX(double CX) { 1838 cX = CX; 1839 } 1840 1841 private double cY; 1842 1843 public double getCY() { 1844 return cY; 1845 } 1846 1847 public void setCY(double CY) { 1848 cY = CY; 1849 } 1850 1851 private double cW; 1852 1853 public double getCW() { 1854 return cW; 1855 } 1856 1857 public void setCW(double CW) { 1858 cW = CW; 1859 } 1860 1861 private double cH; 1862 1863 public double getCH() { 1864 return cH; 1865 } 1866 1867 public void setCH(double CH) { 1868 cH = CH; 1869 } 1870 1871 private double startAdj; 1872 1873 public double getStartAdj() { 1874 return startAdj; 1875 } 1876 1877 public void setStartAdj(double startAdj) { 1878 this.startAdj = startAdj; 1879 } 1880 1881 // this is the center of the track segment (it is "on" the track segment) 1882 public double getCentreSegX() { 1883 return getCentreSeg().getX(); 1884 } 1885 1886 public void setCentreSegX(double x) { 1887 super.setCoordsCenter(new Point2D.Double(x, getCentreSeg().getY())); 1888 } 1889 1890 public double getCentreSegY() { 1891 return getCentreSeg().getY(); 1892 } 1893 1894 public void setCentreSegY(double y) { 1895 super.setCoordsCenter(new Point2D.Double(getCentreSeg().getX(), y)); 1896 } 1897 1898 /** 1899 * @return the location of the middle of the segment (on the segment) 1900 */ 1901 public Point2D getCentreSeg() { 1902 Point2D result = MathUtil.zeroPoint2D; 1903 1904 if ((getConnect1() != null) && (getConnect2() != null)) { 1905 // get the end points 1906 Point2D ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 1907 Point2D ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 1908 1909 if (isCircle()) { 1910 result = getCoordsCenter(); 1911 } else if (isArc()) { 1912 super.setCoordsCenter(MathUtil.midPoint(ep1, ep2)); 1913 if (isFlip()) { 1914 Point2D t = ep1; 1915 ep1 = ep2; 1916 ep2 = t; 1917 } 1918 Point2D delta = MathUtil.subtract(ep1, ep2); 1919 // are they of the same sign? 1920 if ((delta.getX() >= 0.0) != (delta.getY() >= 0.0)) { 1921 delta = MathUtil.divide(delta, +5.0, -5.0); 1922 } else { 1923 delta = MathUtil.divide(delta, -5.0, +5.0); 1924 } 1925 result = MathUtil.add(getCoordsCenter(), delta); 1926 } else if (isBezier()) { 1927 // compute result Bezier point for (t == 0.5); 1928 // copy all the control points (including end points) into an array 1929 int len = bezierControlPoints.size() + 2; 1930 Point2D[] points = new Point2D[len]; 1931 points[0] = ep1; 1932 for (int idx = 1; idx < len - 1; idx++) { 1933 points[idx] = bezierControlPoints.get(idx - 1); 1934 } 1935 points[len - 1] = ep2; 1936 1937 // calculate midpoints of all points (len - 1 order times) 1938 for (int idx = len - 1; idx > 0; idx--) { 1939 for (int jdx = 0; jdx < idx; jdx++) { 1940 points[jdx] = MathUtil.midPoint(points[jdx], points[jdx + 1]); 1941 } 1942 } 1943 result = points[0]; 1944 } else { 1945 result = MathUtil.midPoint(ep1, ep2); 1946 } 1947 super.setCoordsCenter(result); 1948 } 1949 return result; 1950 } 1951 1952 public void setCentreSeg(Point2D p) { 1953 super.setCoordsCenter(p); 1954 } 1955 1956 // this is the center of the track segment when configured as a circle 1957 private double centreX; 1958 1959 public double getCentreX() { 1960 return centreX; 1961 } 1962 1963 public void setCentreX(double x) { 1964 centreX = x; 1965 } 1966 1967 private double centreY; 1968 1969 public double getCentreY() { 1970 return centreY; 1971 } 1972 1973 public void setCentreY(double y) { 1974 centreY = y; 1975 } 1976 1977 public Point2D getCentre() { 1978 return new Point2D.Double(centreX, centreY); 1979 } 1980 1981 private double tmpangle; 1982 1983 public double getTmpAngle() { 1984 return tmpangle; 1985 } 1986 1987 public void setTmpAngle(double a) { 1988 tmpangle = a; 1989 } 1990 1991 /** 1992 * get center coordinates 1993 * 1994 * @return the center coordinates 1995 */ 1996 public Point2D getCoordsCenterCircle() { 1997 return getCentre(); 1998 } 1999 2000 /** 2001 * set center coordinates 2002 * 2003 * @param p the coordinates to set 2004 */ 2005 public void setCoordsCenterCircle(Point2D p) { 2006 centreX = p.getX(); 2007 centreY = p.getY(); 2008 } 2009 2010 private double chordLength; 2011 2012 public double getChordLength() { 2013 return chordLength; 2014 } 2015 2016 public void setChordLength(double chord) { 2017 chordLength = chord; 2018 } 2019 2020 /* 2021 * Called when the user changes the angle dynamically in edit mode 2022 * by dragging the centre of the cirle. 2023 */ 2024 protected void reCalculateTrackSegmentAngle(double x, double y) { 2025 if (!isBezier()) { 2026 double pt2x; 2027 double pt2y; 2028 double pt1x; 2029 double pt1y; 2030 2031 if (isFlip()) { 2032 pt1x = getTmpPt2().getX(); 2033 pt1y = getTmpPt2().getY(); 2034 pt2x = getTmpPt1().getX(); 2035 pt2y = getTmpPt1().getY(); 2036 } else { 2037 pt1x = getTmpPt1().getX(); 2038 pt1y = getTmpPt1().getY(); 2039 pt2x = getTmpPt2().getX(); 2040 pt2y = getTmpPt2().getY(); 2041 } 2042 // Point 1 to new point distance 2043 double a; 2044 double o; 2045 double la; 2046 // Compute arc's chord 2047 a = pt2x - x; 2048 o = pt2y - y; 2049 la = Math.hypot(a, o); 2050 2051 double lb; 2052 a = pt1x - x; 2053 o = pt1y - y; 2054 lb = Math.hypot(a, o); 2055 2056 double newangle = Math.toDegrees(Math.acos((-getChordLength() * getChordLength() + la * la + lb * lb) / (2 * la * lb))); 2057 setAngle(newangle); 2058 } 2059 } 2060 2061 /* 2062 * Calculate the initally parameters for drawing a circular track segment. 2063 */ 2064 protected void calculateTrackSegmentAngle() { 2065 Point2D pt1, pt2; 2066 if (isFlip()) { 2067 pt1 = layoutEditor.getCoords(getConnect2(), getType2()); 2068 pt2 = layoutEditor.getCoords(getConnect1(), getType1()); 2069 } else { 2070 pt1 = layoutEditor.getCoords(getConnect1(), getType1()); 2071 pt2 = layoutEditor.getCoords(getConnect2(), getType2()); 2072 } 2073 if ((getTmpPt1() != pt1) || (getTmpPt2() != pt2) || trackNeedsRedraw()) { 2074 setTmpPt1(pt1); 2075 setTmpPt2(pt2); 2076 2077 double pt1x = pt1.getX(); 2078 double pt1y = pt1.getY(); 2079 double pt2x = pt2.getX(); 2080 double pt2y = pt2.getY(); 2081 2082 if (getAngle() == 0.0D) { 2083 setTmpAngle(90.0D); 2084 } else { 2085 setTmpAngle(getAngle()); 2086 } 2087 // Convert angle to radiants in order to speed up math 2088 double halfAngleRAD = Math.toRadians(getTmpAngle()) / 2.D; 2089 2090 // Compute arc's chord 2091 double a = pt2x - pt1x; 2092 double o = pt2y - pt1y; 2093 double chord = Math.hypot(a, o); 2094 setChordLength(chord); 2095 2096 // Make sure chord is not null 2097 // In such a case (ep1 == ep2), there is no arc to draw 2098 if (chord > 0.D) { 2099 double radius = (chord / 2.D) / Math.sin(halfAngleRAD); 2100 // Circle 2101 double startRad = Math.atan2(a, o) - halfAngleRAD; 2102 setStartAdj(Math.toDegrees(startRad)); 2103 if (isCircle()) { 2104 // Circle - Compute center 2105 setCentreX(pt2x - Math.cos(startRad) * radius); 2106 setCentreY(pt2y + Math.sin(startRad) * radius); 2107 2108 // Circle - Compute rectangle required by Arc2D.Double 2109 setCW(radius * 2.0D); 2110 setCH(radius * 2.0D); 2111 setCX(getCentreX() - radius); 2112 setCY(getCentreY() - radius); 2113 2114 // Compute where to locate the control circle on the circle segment 2115 Point2D offset = new Point2D.Double( 2116 +radius * Math.cos(startRad + halfAngleRAD), 2117 -radius * Math.sin(startRad + halfAngleRAD)); 2118 setCentreSeg(MathUtil.add(getCentre(), offset)); 2119 } else { 2120 // Ellipse - Round start angle to the closest multiple of 90 2121 setStartAdj(Math.round(getStartAdj() / 90.0D) * 90.0D); 2122 // Ellipse - Compute rectangle required by Arc2D.Double 2123 setCW(Math.abs(a) * 2.0D); 2124 setCH(Math.abs(o) * 2.0D); 2125 // Ellipse - Adjust rectangle corner, depending on quadrant 2126 if (o * a < 0.0D) { 2127 a = -a; 2128 } else { 2129 o = -o; 2130 } 2131 setCX(Math.min(pt1x, pt2x) - Math.max(a, 0.0D)); 2132 setCY(Math.min(pt1y, pt2y) - Math.max(o, 0.0D)); 2133 } 2134 } 2135 } 2136 } // calculateTrackSegmentAngle 2137 2138 /** 2139 * {@inheritDoc} 2140 */ 2141 @Override 2142 protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) { 2143// if (getName().equals("T15")) { 2144// log.debug("STOP"); 2145// } 2146 if (!isBlock && isDashed() && getLayoutBlock() != null) { 2147 // Skip the dashed rail layer, the block layer will display the dashed track 2148 // This removes random rail fragments from between the block dashes 2149 return; 2150 } 2151 if (isMain == trackSegment.isMainline()) { 2152 if (isBlock) { 2153 setColorForTrackBlock(g2, getLayoutBlock()); 2154 } 2155 if (isArc()) { 2156 calculateTrackSegmentAngle(); 2157 g2.draw(new Arc2D.Double(getCX(), getCY(), getCW(), getCH(), getStartAdj(), getTmpAngle(), Arc2D.OPEN)); 2158 trackRedrawn(); 2159 } else if (isBezier()) { 2160 Point2D[] points = getBezierPoints(); 2161 MathUtil.drawBezier(g2, points); 2162 } else { 2163 Point2D end1 = layoutEditor.getCoords( 2164 getConnect1(), 2165 getType1()); 2166 Point2D end2 = layoutEditor.getCoords( 2167 getConnect2(), 2168 getType2()); 2169 2170 g2.draw(new Line2D.Double(end1, end2)); 2171 } 2172 } 2173 } 2174 2175 /** 2176 * {@inheritDoc} 2177 */ 2178 @Override 2179 protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) { 2180// if (getName().equals("T5")) { 2181// log.debug("STOP"); 2182// } 2183 if (isDashed() && getLayoutBlock() != null) { 2184 // Skip the dashed rail layer, the block layer will display the dashed track 2185 // This removes random rail fragments from between the block dashes 2186 return; 2187 } 2188 if (isMain == trackSegment.isMainline()) { 2189 if (isArc()) { 2190 calculateTrackSegmentAngle(); 2191 Rectangle2D cRectangle2D = new Rectangle2D.Double( 2192 getCX(), getCY(), getCW(), getCH()); 2193 Rectangle2D tRectangle2D = MathUtil.inset(cRectangle2D, -railDisplacement); 2194 double startAdj = getStartAdj(), tmpAngle = getTmpAngle(); 2195 g2.draw(new Arc2D.Double(tRectangle2D.getX(), tRectangle2D.getY(), 2196 tRectangle2D.getWidth(), tRectangle2D.getHeight(), 2197 startAdj, tmpAngle, Arc2D.OPEN)); 2198 tRectangle2D = MathUtil.inset(cRectangle2D, +railDisplacement); 2199 g2.draw(new Arc2D.Double(tRectangle2D.getX(), tRectangle2D.getY(), 2200 tRectangle2D.getWidth(), tRectangle2D.getHeight(), 2201 startAdj, tmpAngle, Arc2D.OPEN)); 2202 trackRedrawn(); 2203 } else if (isBezier()) { 2204 Point2D[] points = getBezierPoints(); 2205 MathUtil.drawBezier(g2, points, -railDisplacement); 2206 MathUtil.drawBezier(g2, points, +railDisplacement); 2207 } else { 2208 Point2D end1 = layoutEditor.getCoords(getConnect1(), getType1()); 2209 Point2D end2 = layoutEditor.getCoords(getConnect2(), getType2()); 2210 2211 Point2D delta = MathUtil.subtract(end2, end1); 2212 Point2D vector = MathUtil.normalize(delta, railDisplacement); 2213 vector = MathUtil.orthogonal(vector); 2214 2215 Point2D ep1L = MathUtil.add(end1, vector); 2216 Point2D ep2L = MathUtil.add(end2, vector); 2217 g2.draw(new Line2D.Double(ep1L, ep2L)); 2218 2219 Point2D ep1R = MathUtil.subtract(end1, vector); 2220 Point2D ep2R = MathUtil.subtract(end2, vector); 2221 g2.draw(new Line2D.Double(ep1R, ep2R)); 2222 } 2223 } 2224 } 2225 2226 /** 2227 * {@inheritDoc} 2228 */ 2229 @Override 2230 protected void highlightUnconnected(Graphics2D g2, HitPointType selectedType) { 2231 // TrackSegments are always connected 2232 // nothing to see here... move along... 2233 } 2234 2235 @Override 2236 protected void drawEditControls(Graphics2D g2) { 2237 g2.setColor(Color.black); 2238 if (isShowConstructionLines()) { 2239 Point2D ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 2240 Point2D ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 2241 if (isCircle()) { 2242 // draw radiuses 2243 Point2D circleCenterPoint = getCoordsCenterCircle(); 2244 g2.draw(new Line2D.Double(circleCenterPoint, ep1)); 2245 g2.draw(new Line2D.Double(circleCenterPoint, ep2)); 2246 // Draw a circle and square at the circles centre, that 2247 // allows the user to change the angle by dragging the mouse. 2248 g2.draw(trackEditControlCircleAt(circleCenterPoint)); 2249 g2.draw(layoutEditor.layoutEditorControlRectAt(circleCenterPoint)); 2250 } else if (isBezier()) { 2251 // draw construction lines and control circles 2252 Point2D lastPt = ep1; 2253 for (Point2D bcp : bezierControlPoints) { 2254 g2.draw(new Line2D.Double(lastPt, bcp)); 2255 lastPt = bcp; 2256 g2.draw(layoutEditor.layoutEditorControlRectAt(bcp)); 2257 } 2258 g2.draw(new Line2D.Double(lastPt, ep2)); 2259 } 2260 } 2261 g2.draw(trackEditControlCircleAt(getCentreSeg())); 2262 } // drawEditControls 2263 2264 @Override 2265 protected void drawTurnoutControls(Graphics2D g2) { 2266 // TrackSegments don't have turnout controls... 2267 // nothing to see here... move along... 2268 } 2269 2270 /** 2271 * {@inheritDoc} 2272 */ 2273 @Override 2274 public void reCheckBlockBoundary() { 2275 // nothing to see here... move along... 2276 } 2277 2278 /** 2279 * {@inheritDoc} 2280 */ 2281 @Override 2282 protected void drawDecorations(Graphics2D g2) { 2283 2284 log.trace("TrackSegmentView: drawDecorations arrowStyle {}", arrowStyle); 2285// get end points and calculate start/stop angles (in radians) 2286 Point2D ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 2287 Point2D ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 2288 Point2D p1, p2, p3, p4, p5, p6, p7; 2289 Point2D p1P = ep1, p2P = ep2, p3P, p4P, p5P, p6P, p7P; 2290 double startAngleRAD, stopAngleRAD; 2291 if (isArc()) { 2292 calculateTrackSegmentAngle(); 2293 double startAngleDEG = getStartAdj(), extentAngleDEG = getTmpAngle(); 2294 startAngleRAD = (Math.PI / 2.D) - Math.toRadians(startAngleDEG); 2295 stopAngleRAD = (Math.PI / 2.D) - Math.toRadians(startAngleDEG + extentAngleDEG); 2296 if (isFlip()) { 2297 startAngleRAD += Math.PI; 2298 stopAngleRAD += Math.PI; 2299 } else { 2300 double temp = startAngleRAD; 2301 startAngleRAD = stopAngleRAD; 2302 stopAngleRAD = temp; 2303 } 2304 } else if (isBezier()) { 2305 Point2D cp0 = bezierControlPoints.get(0); 2306 Point2D cpN = bezierControlPoints.get(bezierControlPoints.size() - 1); 2307 startAngleRAD = (Math.PI / 2.D) - MathUtil.computeAngleRAD(cp0, ep1); 2308 stopAngleRAD = (Math.PI / 2.D) - MathUtil.computeAngleRAD(ep2, cpN); 2309 } else { 2310 startAngleRAD = (Math.PI / 2.D) - MathUtil.computeAngleRAD(ep2, ep1); 2311 stopAngleRAD = startAngleRAD; 2312 } 2313 2314// 2315// arrow decorations 2316// 2317 if (arrowStyle > 0) { 2318 log.trace("arrowstyle>0 with width {}", arrowLineWidth); 2319 g2.setStroke(new BasicStroke(arrowLineWidth, 2320 BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.F)); 2321 g2.setColor(arrowColor); 2322 2323 // draw the start arrows 2324 int offset = 1; 2325 if (arrowEndStart) { 2326 if (arrowDirIn) { 2327 offset = drawArrow(g2, ep1, Math.PI + startAngleRAD, false, offset); 2328 } 2329 if (arrowDirOut) { 2330 offset = drawArrow(g2, ep1, Math.PI + startAngleRAD, true, offset); 2331 } 2332 } 2333 2334 // draw the stop arrows 2335 offset = 1; 2336 if (arrowEndStop) { 2337 if (arrowDirIn) { 2338 offset = drawArrow(g2, ep2, stopAngleRAD, false, offset); 2339 } 2340 if (arrowDirOut) { 2341 offset = drawArrow(g2, ep2, stopAngleRAD, true, offset); 2342 } 2343 } 2344 } // arrow decoration 2345 2346// 2347// bridge decorations 2348// 2349 if (bridgeSideLeft || bridgeSideRight) { 2350 float halfWidth = bridgeDeckWidth / 2.F; 2351 2352 log.trace("bridgeleft/right with width {}", bridgeLineWidth); 2353 g2.setStroke(new BasicStroke(bridgeLineWidth, 2354 BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.F)); 2355 g2.setColor(bridgeColor); 2356 2357 if (isArc()) { 2358 calculateTrackSegmentAngle(); 2359 Rectangle2D cRectangle2D = new Rectangle2D.Double( 2360 getCX(), getCY(), getCW(), getCH()); 2361 double startAdj = getStartAdj(), tmpAngle = getTmpAngle(); 2362 if (bridgeSideLeft) { 2363 Rectangle2D tRectangle2D = MathUtil.inset(cRectangle2D, -halfWidth); 2364 g2.draw(new Arc2D.Double(tRectangle2D.getX(), tRectangle2D.getY(), 2365 tRectangle2D.getWidth(), tRectangle2D.getHeight(), 2366 startAdj, tmpAngle, Arc2D.OPEN)); 2367 } 2368 if (bridgeSideRight) { 2369 Rectangle2D tRectangle2D = MathUtil.inset(cRectangle2D, +halfWidth); 2370 g2.draw(new Arc2D.Double(tRectangle2D.getX(), tRectangle2D.getY(), 2371 tRectangle2D.getWidth(), tRectangle2D.getHeight(), 2372 startAdj, tmpAngle, Arc2D.OPEN)); 2373 } 2374 } else if (isBezier()) { 2375 Point2D[] points = getBezierPoints(); 2376 if (bridgeSideLeft) { 2377 MathUtil.drawBezier(g2, points, -halfWidth); 2378 } 2379 if (bridgeSideRight) { 2380 MathUtil.drawBezier(g2, points, +halfWidth); 2381 } 2382 } else { 2383 Point2D delta = MathUtil.subtract(ep2, ep1); 2384 Point2D vector = MathUtil.normalize(delta, halfWidth); 2385 vector = MathUtil.orthogonal(vector); 2386 2387 if (bridgeSideRight) { 2388 Point2D ep1R = MathUtil.add(ep1, vector); 2389 Point2D ep2R = MathUtil.add(ep2, vector); 2390 g2.draw(new Line2D.Double(ep1R, ep2R)); 2391 } 2392 2393 if (bridgeSideLeft) { 2394 Point2D ep1L = MathUtil.subtract(ep1, vector); 2395 Point2D ep2L = MathUtil.subtract(ep2, vector); 2396 g2.draw(new Line2D.Double(ep1L, ep2L)); 2397 } 2398 } // if isArc() {} else if isBezier() {} else... 2399 2400 if (isFlip()) { 2401 boolean temp = bridgeSideRight; 2402 bridgeSideRight = bridgeSideLeft; 2403 bridgeSideLeft = temp; 2404 } 2405 2406 if (bridgeHasEntry) { 2407 if (bridgeSideRight) { 2408 p1 = new Point2D.Double(-bridgeApproachWidth, +bridgeApproachWidth + halfWidth); 2409 p2 = new Point2D.Double(0.0, +halfWidth); 2410 p1P = MathUtil.add(MathUtil.rotateRAD(p1, startAngleRAD), ep1); 2411 p2P = MathUtil.add(MathUtil.rotateRAD(p2, startAngleRAD), ep1); 2412 g2.draw(new Line2D.Double(p1P, p2P)); 2413 } 2414 if (bridgeSideLeft) { 2415 p1 = new Point2D.Double(-bridgeApproachWidth, -bridgeApproachWidth - halfWidth); 2416 p2 = new Point2D.Double(0.0, -halfWidth); 2417 p1P = MathUtil.add(MathUtil.rotateRAD(p1, startAngleRAD), ep1); 2418 p2P = MathUtil.add(MathUtil.rotateRAD(p2, startAngleRAD), ep1); 2419 g2.draw(new Line2D.Double(p1P, p2P)); 2420 } 2421 } 2422 if (bridgeHasExit) { 2423 if (bridgeSideRight) { 2424 p1 = new Point2D.Double(+bridgeApproachWidth, +bridgeApproachWidth + halfWidth); 2425 p2 = new Point2D.Double(0.0, +halfWidth); 2426 p1P = MathUtil.add(MathUtil.rotateRAD(p1, stopAngleRAD), ep2); 2427 p2P = MathUtil.add(MathUtil.rotateRAD(p2, stopAngleRAD), ep2); 2428 g2.draw(new Line2D.Double(p1P, p2P)); 2429 } 2430 if (bridgeSideLeft) { 2431 p1 = new Point2D.Double(+bridgeApproachWidth, -bridgeApproachWidth - halfWidth); 2432 p2 = new Point2D.Double(0.0, -halfWidth); 2433 p1P = MathUtil.add(MathUtil.rotateRAD(p1, stopAngleRAD), ep2); 2434 p2P = MathUtil.add(MathUtil.rotateRAD(p2, stopAngleRAD), ep2); 2435 g2.draw(new Line2D.Double(p1P, p2P)); 2436 } 2437 } 2438 2439 // if necessary flip these back 2440 if (isFlip()) { 2441 boolean temp = bridgeSideRight; 2442 bridgeSideRight = bridgeSideLeft; 2443 bridgeSideLeft = temp; 2444 } 2445 } 2446 2447// 2448// end bumper decorations 2449// 2450 if (bumperEndStart || bumperEndStop) { 2451 log.trace("bumper end/start with width {}", bumperLineWidth); 2452 g2.setStroke(new BasicStroke(bumperLineWidth, 2453 BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.F)); 2454 g2.setColor(bumperColor); 2455 2456 float halfLength = bumperLength / 2.F; 2457 2458 if (bumperFlipped) { 2459 double temp = startAngleRAD; 2460 startAngleRAD = stopAngleRAD; 2461 stopAngleRAD = temp; 2462 } 2463 2464 // common points 2465 p1 = new Point2D.Double(0.F, -halfLength); 2466 p2 = new Point2D.Double(0.F, +halfLength); 2467 2468 if (bumperEndStart) { 2469 p1P = MathUtil.add(MathUtil.rotateRAD(p1, startAngleRAD), ep1); 2470 p2P = MathUtil.add(MathUtil.rotateRAD(p2, startAngleRAD), ep1); 2471 // draw cross tie 2472 g2.draw(new Line2D.Double(p1P, p2P)); 2473 } 2474 if (bumperEndStop) { 2475 p1P = MathUtil.add(MathUtil.rotateRAD(p1, stopAngleRAD), ep2); 2476 p2P = MathUtil.add(MathUtil.rotateRAD(p2, stopAngleRAD), ep2); 2477 // draw cross tie 2478 g2.draw(new Line2D.Double(p1P, p2P)); 2479 } 2480 } // if (bumperEndStart || bumperEndStop) 2481 2482// 2483// tunnel decorations 2484// 2485 if (tunnelSideRight || tunnelSideLeft) { 2486 log.trace("tunnel left/right with width {}", tunnelLineWidth); 2487 float halfWidth = tunnelFloorWidth / 2.F; 2488 g2.setStroke(new BasicStroke(tunnelLineWidth, 2489 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.F, 2490 new float[]{6.F, 4.F}, 0)); 2491 g2.setColor(tunnelColor); 2492 2493 if (isArc()) { 2494 calculateTrackSegmentAngle(); 2495 Rectangle2D cRectangle2D = new Rectangle2D.Double( 2496 getCX(), getCY(), getCW(), getCH()); 2497 double startAngleDEG = getStartAdj(), extentAngleDEG = getTmpAngle(); 2498 if (tunnelSideRight) { 2499 Rectangle2D tRectangle2D = MathUtil.inset(cRectangle2D, +halfWidth); 2500 g2.draw(new Arc2D.Double(tRectangle2D.getX(), tRectangle2D.getY(), 2501 tRectangle2D.getWidth(), tRectangle2D.getHeight(), 2502 startAngleDEG, extentAngleDEG, Arc2D.OPEN)); 2503 } 2504 if (tunnelSideLeft) { 2505 Rectangle2D tRectangle2D = MathUtil.inset(cRectangle2D, -halfWidth); 2506 g2.draw(new Arc2D.Double(tRectangle2D.getX(), tRectangle2D.getY(), 2507 tRectangle2D.getWidth(), tRectangle2D.getHeight(), 2508 startAngleDEG, extentAngleDEG, Arc2D.OPEN)); 2509 } 2510 trackRedrawn(); 2511 } else if (isBezier()) { 2512 Point2D[] points = getBezierPoints(); 2513 if (tunnelSideRight) { 2514 MathUtil.drawBezier(g2, points, +halfWidth); 2515 } 2516 if (tunnelSideLeft) { 2517 MathUtil.drawBezier(g2, points, -halfWidth); 2518 } 2519 } else { 2520 Point2D delta = MathUtil.subtract(ep2, ep1); 2521 Point2D vector = MathUtil.normalize(delta, halfWidth); 2522 vector = MathUtil.orthogonal(vector); 2523 2524 if (tunnelSideRight) { 2525 Point2D ep1L = MathUtil.add(ep1, vector); 2526 Point2D ep2L = MathUtil.add(ep2, vector); 2527 g2.draw(new Line2D.Double(ep1L, ep2L)); 2528 } 2529 if (tunnelSideLeft) { 2530 Point2D ep1R = MathUtil.subtract(ep1, vector); 2531 Point2D ep2R = MathUtil.subtract(ep2, vector); 2532 g2.draw(new Line2D.Double(ep1R, ep2R)); 2533 } 2534 } // if isArc() {} else if isBezier() {} else... 2535 2536 g2.setStroke(new BasicStroke(tunnelLineWidth, 2537 BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.F)); 2538 g2.setColor(tunnelColor); 2539 2540 // don't let tunnelEntranceWidth be less than tunnelFloorWidth + 6 2541 tunnelEntranceWidth = Math.max(tunnelEntranceWidth, tunnelFloorWidth + 6); 2542 2543 double halfEntranceWidth = tunnelEntranceWidth / 2.0; 2544 double halfFloorWidth = tunnelFloorWidth / 2.0; 2545 double halfDiffWidth = halfEntranceWidth - halfFloorWidth; 2546 2547 if (isFlip()) { 2548 boolean temp = tunnelSideRight; 2549 tunnelSideRight = tunnelSideLeft; 2550 tunnelSideLeft = temp; 2551 } 2552 2553 if (tunnelHasEntry) { 2554 if (tunnelSideRight) { 2555 p1 = new Point2D.Double(0.0, 0.0); 2556 p2 = new Point2D.Double(0.0, +halfFloorWidth); 2557 p3 = new Point2D.Double(0.0, +halfEntranceWidth); 2558 p4 = new Point2D.Double(-halfEntranceWidth - halfFloorWidth, +halfEntranceWidth); 2559 p5 = new Point2D.Double(-halfEntranceWidth - halfFloorWidth, +halfEntranceWidth - halfDiffWidth); 2560 p6 = new Point2D.Double(-halfFloorWidth, +halfEntranceWidth - halfDiffWidth); 2561 p7 = new Point2D.Double(-halfDiffWidth, 0.0); 2562 2563 p1P = MathUtil.add(MathUtil.rotateRAD(p1, startAngleRAD), ep1); 2564 p2P = MathUtil.add(MathUtil.rotateRAD(p2, startAngleRAD), ep1); 2565 p3P = MathUtil.add(MathUtil.rotateRAD(p3, startAngleRAD), ep1); 2566 p4P = MathUtil.add(MathUtil.rotateRAD(p4, startAngleRAD), ep1); 2567 p5P = MathUtil.add(MathUtil.rotateRAD(p5, startAngleRAD), ep1); 2568 p6P = MathUtil.add(MathUtil.rotateRAD(p6, startAngleRAD), ep1); 2569 p7P = MathUtil.add(MathUtil.rotateRAD(p7, startAngleRAD), ep1); 2570 2571 GeneralPath path = new GeneralPath(); 2572 path.moveTo(p1P.getX(), p1P.getY()); 2573 path.lineTo(p2P.getX(), p2P.getY()); 2574 path.quadTo(p3P.getX(), p3P.getY(), p4P.getX(), p4P.getY()); 2575 path.lineTo(p5P.getX(), p5P.getY()); 2576 path.quadTo(p6P.getX(), p6P.getY(), p7P.getX(), p7P.getY()); 2577 path.closePath(); 2578 g2.draw(path); 2579 } 2580 if (tunnelSideLeft) { 2581 p1 = new Point2D.Double(0.0, 0.0); 2582 p2 = new Point2D.Double(0.0, -halfFloorWidth); 2583 p3 = new Point2D.Double(0.0, -halfEntranceWidth); 2584 p4 = new Point2D.Double(-halfEntranceWidth - halfFloorWidth, -halfEntranceWidth); 2585 p5 = new Point2D.Double(-halfEntranceWidth - halfFloorWidth, -halfEntranceWidth + halfDiffWidth); 2586 p6 = new Point2D.Double(-halfFloorWidth, -halfEntranceWidth + halfDiffWidth); 2587 p7 = new Point2D.Double(-halfDiffWidth, 0.0); 2588 2589 p1P = MathUtil.add(MathUtil.rotateRAD(p1, startAngleRAD), ep1); 2590 p2P = MathUtil.add(MathUtil.rotateRAD(p2, startAngleRAD), ep1); 2591 p3P = MathUtil.add(MathUtil.rotateRAD(p3, startAngleRAD), ep1); 2592 p4P = MathUtil.add(MathUtil.rotateRAD(p4, startAngleRAD), ep1); 2593 p5P = MathUtil.add(MathUtil.rotateRAD(p5, startAngleRAD), ep1); 2594 p6P = MathUtil.add(MathUtil.rotateRAD(p6, startAngleRAD), ep1); 2595 p7P = MathUtil.add(MathUtil.rotateRAD(p7, startAngleRAD), ep1); 2596 2597 GeneralPath path = new GeneralPath(); 2598 path.moveTo(p1P.getX(), p1P.getY()); 2599 path.lineTo(p2P.getX(), p2P.getY()); 2600 path.quadTo(p3P.getX(), p3P.getY(), p4P.getX(), p4P.getY()); 2601 path.lineTo(p5P.getX(), p5P.getY()); 2602 path.quadTo(p6P.getX(), p6P.getY(), p7P.getX(), p7P.getY()); 2603 path.closePath(); 2604 g2.draw(path); 2605 } 2606 } 2607 if (tunnelHasExit) { 2608 if (tunnelSideRight) { 2609 p1 = new Point2D.Double(0.0, 0.0); 2610 p2 = new Point2D.Double(0.0, +halfFloorWidth); 2611 p3 = new Point2D.Double(0.0, +halfEntranceWidth); 2612 p4 = new Point2D.Double(halfEntranceWidth + halfFloorWidth, +halfEntranceWidth); 2613 p5 = new Point2D.Double(halfEntranceWidth + halfFloorWidth, +halfEntranceWidth - halfDiffWidth); 2614 p6 = new Point2D.Double(halfFloorWidth, +halfEntranceWidth - halfDiffWidth); 2615 p7 = new Point2D.Double(halfDiffWidth, 0.0); 2616 2617 p1P = MathUtil.add(MathUtil.rotateRAD(p1, stopAngleRAD), ep2); 2618 p2P = MathUtil.add(MathUtil.rotateRAD(p2, stopAngleRAD), ep2); 2619 p3P = MathUtil.add(MathUtil.rotateRAD(p3, stopAngleRAD), ep2); 2620 p4P = MathUtil.add(MathUtil.rotateRAD(p4, stopAngleRAD), ep2); 2621 p5P = MathUtil.add(MathUtil.rotateRAD(p5, stopAngleRAD), ep2); 2622 p6P = MathUtil.add(MathUtil.rotateRAD(p6, stopAngleRAD), ep2); 2623 p7P = MathUtil.add(MathUtil.rotateRAD(p7, stopAngleRAD), ep2); 2624 2625 GeneralPath path = new GeneralPath(); 2626 path.moveTo(p1P.getX(), p1P.getY()); 2627 path.lineTo(p2P.getX(), p2P.getY()); 2628 path.quadTo(p3P.getX(), p3P.getY(), p4P.getX(), p4P.getY()); 2629 path.lineTo(p5P.getX(), p5P.getY()); 2630 path.quadTo(p6P.getX(), p6P.getY(), p7P.getX(), p7P.getY()); 2631 path.closePath(); 2632 g2.draw(path); 2633 } 2634 if (tunnelSideLeft) { 2635 p1 = new Point2D.Double(0.0, 0.0); 2636 p2 = new Point2D.Double(0.0, -halfFloorWidth); 2637 p3 = new Point2D.Double(0.0, -halfEntranceWidth); 2638 p4 = new Point2D.Double(halfEntranceWidth + halfFloorWidth, -halfEntranceWidth); 2639 p5 = new Point2D.Double(halfEntranceWidth + halfFloorWidth, -halfEntranceWidth + halfDiffWidth); 2640 p6 = new Point2D.Double(halfFloorWidth, -halfEntranceWidth + halfDiffWidth); 2641 p7 = new Point2D.Double(halfDiffWidth, 0.0); 2642 2643 p1P = MathUtil.add(MathUtil.rotateRAD(p1, stopAngleRAD), ep2); 2644 p2P = MathUtil.add(MathUtil.rotateRAD(p2, stopAngleRAD), ep2); 2645 p3P = MathUtil.add(MathUtil.rotateRAD(p3, stopAngleRAD), ep2); 2646 p4P = MathUtil.add(MathUtil.rotateRAD(p4, stopAngleRAD), ep2); 2647 p5P = MathUtil.add(MathUtil.rotateRAD(p5, stopAngleRAD), ep2); 2648 p6P = MathUtil.add(MathUtil.rotateRAD(p6, stopAngleRAD), ep2); 2649 p7P = MathUtil.add(MathUtil.rotateRAD(p7, stopAngleRAD), ep2); 2650 2651 GeneralPath path = new GeneralPath(); 2652 path.moveTo(p1P.getX(), p1P.getY()); 2653 path.lineTo(p2P.getX(), p2P.getY()); 2654 path.quadTo(p3P.getX(), p3P.getY(), p4P.getX(), p4P.getY()); 2655 path.lineTo(p5P.getX(), p5P.getY()); 2656 path.quadTo(p6P.getX(), p6P.getY(), p7P.getX(), p7P.getY()); 2657 path.closePath(); 2658 g2.draw(path); 2659 } 2660 } 2661 2662 // if necessary, put these back 2663 if (isFlip()) { 2664 boolean temp = tunnelSideRight; 2665 tunnelSideRight = tunnelSideLeft; 2666 tunnelSideLeft = temp; 2667 } 2668 } 2669 } // drawDecorations 2670 2671 /* 2672 * getBezierPoints 2673 * @return the points to pass to MathUtil.drawBezier(...) 2674 */ 2675 @Nonnull 2676 private Point2D[] getBezierPoints() { 2677 Point2D ep1 = layoutEditor.getCoords(getConnect1(), getType1()); 2678 Point2D ep2 = layoutEditor.getCoords(getConnect2(), getType2()); 2679 int cnt = bezierControlPoints.size() + 2; 2680 Point2D[] points = new Point2D[cnt]; 2681 points[0] = ep1; 2682 for (int idx = 0; idx < cnt - 2; idx++) { 2683 points[idx + 1] = bezierControlPoints.get(idx); 2684 } 2685 points[cnt - 1] = ep2; 2686 return points; 2687 } 2688 2689 private int drawArrow( 2690 Graphics2D g2, 2691 Point2D ep, 2692 double angleRAD, 2693 boolean dirOut, 2694 int offset) { 2695 Point2D p1, p2, p3, p4, p5, p6; 2696 log.trace("drawArrow in TrackSegmentView"); 2697 switch (arrowStyle) { 2698 default: { 2699 arrowStyle = 0; 2700 break; 2701 } 2702 case 0: { 2703 break; 2704 } 2705 case 1: { 2706 if (dirOut) { 2707 p1 = new Point2D.Double(offset, -arrowLength); 2708 p2 = new Point2D.Double(offset + arrowLength, 0.0); 2709 p3 = new Point2D.Double(offset, +arrowLength); 2710 } else { 2711 p1 = new Point2D.Double(offset + arrowLength, -arrowLength); 2712 p2 = new Point2D.Double(offset, 0.0); 2713 p3 = new Point2D.Double(offset + arrowLength, +arrowLength); 2714 } 2715 p1 = MathUtil.add(MathUtil.rotateRAD(p1, angleRAD), ep); 2716 p2 = MathUtil.add(MathUtil.rotateRAD(p2, angleRAD), ep); 2717 p3 = MathUtil.add(MathUtil.rotateRAD(p3, angleRAD), ep); 2718 2719 g2.draw(new Line2D.Double(p1, p2)); 2720 g2.draw(new Line2D.Double(p2, p3)); 2721 offset += arrowLength + arrowGap; 2722 break; 2723 } 2724 case 2: { 2725 if (dirOut) { 2726 p1 = new Point2D.Double(offset, -arrowLength); 2727 p2 = new Point2D.Double(offset + arrowLength, 0.0); 2728 p3 = new Point2D.Double(offset, +arrowLength); 2729 p4 = new Point2D.Double(offset + arrowLineWidth + arrowGap, -arrowLength); 2730 p5 = new Point2D.Double(offset + arrowLineWidth + arrowGap + arrowLength, 0.0); 2731 p6 = new Point2D.Double(offset + arrowLineWidth + arrowGap, +arrowLength); 2732 } else { 2733 p1 = new Point2D.Double(offset + arrowLength, -arrowLength); 2734 p2 = new Point2D.Double(offset, 0.0); 2735 p3 = new Point2D.Double(offset + arrowLength, +arrowLength); 2736 p4 = new Point2D.Double(offset + arrowLineWidth + arrowGap + arrowLength, -arrowLength); 2737 p5 = new Point2D.Double(offset + arrowLineWidth + arrowGap, 0.0); 2738 p6 = new Point2D.Double(offset + arrowLineWidth + arrowGap + arrowLength, +arrowLength); 2739 } 2740 p1 = MathUtil.add(MathUtil.rotateRAD(p1, angleRAD), ep); 2741 p2 = MathUtil.add(MathUtil.rotateRAD(p2, angleRAD), ep); 2742 p3 = MathUtil.add(MathUtil.rotateRAD(p3, angleRAD), ep); 2743 p4 = MathUtil.add(MathUtil.rotateRAD(p4, angleRAD), ep); 2744 p5 = MathUtil.add(MathUtil.rotateRAD(p5, angleRAD), ep); 2745 p6 = MathUtil.add(MathUtil.rotateRAD(p6, angleRAD), ep); 2746 2747 g2.draw(new Line2D.Double(p1, p2)); 2748 g2.draw(new Line2D.Double(p2, p3)); 2749 g2.draw(new Line2D.Double(p4, p5)); 2750 g2.draw(new Line2D.Double(p5, p6)); 2751 offset += arrowLength + (2 * (arrowLineWidth + arrowGap)); 2752 break; 2753 } 2754 case 3: { 2755 if (dirOut) { 2756 p1 = new Point2D.Double(offset, -arrowLength); 2757 p2 = new Point2D.Double(offset + arrowLength, 0.0); 2758 p3 = new Point2D.Double(offset, +arrowLength); 2759 } else { 2760 p1 = new Point2D.Double(offset + arrowLength, -arrowLength); 2761 p2 = new Point2D.Double(offset, 0.0); 2762 p3 = new Point2D.Double(offset + arrowLength, +arrowLength); 2763 } 2764 p1 = MathUtil.add(MathUtil.rotateRAD(p1, angleRAD), ep); 2765 p2 = MathUtil.add(MathUtil.rotateRAD(p2, angleRAD), ep); 2766 p3 = MathUtil.add(MathUtil.rotateRAD(p3, angleRAD), ep); 2767 2768 GeneralPath path = new GeneralPath(); 2769 path.moveTo(p1.getX(), p1.getY()); 2770 path.lineTo(p2.getX(), p2.getY()); 2771 path.lineTo(p3.getX(), p3.getY()); 2772 path.closePath(); 2773 if (arrowLineWidth > 1) { 2774 g2.fill(path); 2775 } else { 2776 g2.draw(path); 2777 } 2778 offset += arrowLength + arrowGap; 2779 break; 2780 } 2781 case 4: { 2782 if (dirOut) { 2783 p1 = new Point2D.Double(offset, 0.0); 2784 p2 = new Point2D.Double(offset + (2 * arrowLength), -arrowLength); 2785 p3 = new Point2D.Double(offset + (3 * arrowLength), 0.0); 2786 p4 = new Point2D.Double(offset + (2 * arrowLength), +arrowLength); 2787 } else { 2788 p1 = new Point2D.Double(offset, 0.0); 2789 p2 = new Point2D.Double(offset + (4 * arrowLength), -arrowLength); 2790 p3 = new Point2D.Double(offset + (3 * arrowLength), 0.0); 2791 p4 = new Point2D.Double(offset + (4 * arrowLength), +arrowLength); 2792 } 2793 p1 = MathUtil.add(MathUtil.rotateRAD(p1, angleRAD), ep); 2794 p2 = MathUtil.add(MathUtil.rotateRAD(p2, angleRAD), ep); 2795 p3 = MathUtil.add(MathUtil.rotateRAD(p3, angleRAD), ep); 2796 p4 = MathUtil.add(MathUtil.rotateRAD(p4, angleRAD), ep); 2797 2798 g2.draw(new Line2D.Double(p1, p3)); 2799 g2.draw(new Line2D.Double(p2, p3)); 2800 g2.draw(new Line2D.Double(p3, p4)); 2801 2802 offset += (3 * arrowLength) + arrowGap; 2803 break; 2804 } 2805 case 5: { 2806 if (dirOut) { 2807 p1 = new Point2D.Double(offset, 0.0); 2808 p2 = new Point2D.Double(offset + (2 * arrowLength), -arrowLength); 2809 p3 = new Point2D.Double(offset + (3 * arrowLength), 0.0); 2810 p4 = new Point2D.Double(offset + (2 * arrowLength), +arrowLength); 2811 } else { 2812 p1 = new Point2D.Double(offset, 0.0); 2813 p2 = new Point2D.Double(offset + (4 * arrowLength), -arrowLength); 2814 p3 = new Point2D.Double(offset + (3 * arrowLength), 0.0); 2815 p4 = new Point2D.Double(offset + (4 * arrowLength), +arrowLength); 2816 } 2817 p1 = MathUtil.add(MathUtil.rotateRAD(p1, angleRAD), ep); 2818 p2 = MathUtil.add(MathUtil.rotateRAD(p2, angleRAD), ep); 2819 p3 = MathUtil.add(MathUtil.rotateRAD(p3, angleRAD), ep); 2820 p4 = MathUtil.add(MathUtil.rotateRAD(p4, angleRAD), ep); 2821 2822 GeneralPath path = new GeneralPath(); 2823 path.moveTo(p4.getX(), p4.getY()); 2824 path.lineTo(p2.getX(), p2.getY()); 2825 path.lineTo(p3.getX(), p3.getY()); 2826 path.closePath(); 2827 if (arrowLineWidth > 1) { 2828 g2.fill(path); 2829 } else { 2830 g2.draw(path); 2831 } 2832 g2.draw(new Line2D.Double(p1, p3)); 2833 2834 offset += (3 * arrowLength) + arrowGap; 2835 break; 2836 } 2837 } 2838 return offset; 2839 } // drawArrow 2840 2841 /*======================*\ 2842 |* decoration accessors *| 2843 \*======================*/ 2844 // Although the superclass LayoutTrack stores decorators in a Map, 2845 // here we store them in specific variables like arrowStyle, bridgeSideRight, etc. 2846 // We convert to and from the map during the getDecorations, setDecorations 2847 // and hasDecorations calls. 2848 /** 2849 * {@inheritDoc} 2850 */ 2851 @Override 2852 public boolean hasDecorations() { 2853 return ((arrowStyle > 0) 2854 || (bridgeSideLeft || bridgeSideRight) 2855 || (bumperEndStart || bumperEndStop) 2856 || (tunnelSideLeft || tunnelSideRight)); 2857 } 2858 2859 /** 2860 * {@inheritDoc} 2861 */ 2862 @Override 2863 public Map<String, String> getDecorations() { 2864 if (decorations == null) { 2865 decorations = new HashMap<>(); 2866 } // if (decorathions != null) 2867 2868 // 2869 // arrow decorations 2870 // 2871 if (arrowStyle > 0) { 2872 //<decoration name="arrow" value="double;both;linewidth=1;length=12;gap=1" /> 2873 List<String> arrowValues = new ArrayList<>(); 2874 2875 arrowValues.add("style=" + arrowStyle); 2876 2877 if (arrowEndStart && arrowEndStop) { 2878 // default behaviour is both 2879 } else if (arrowEndStop) { 2880 arrowValues.add("stop"); 2881 } else { 2882 arrowEndStart = true; 2883 arrowValues.add("start"); 2884 } 2885 2886 if (arrowDirIn && !arrowDirOut) { 2887 arrowValues.add("in"); 2888 } else if (!arrowDirIn && arrowDirOut) { 2889 arrowValues.add("out"); 2890 } else { 2891 arrowDirIn = true; 2892 arrowDirOut = true; 2893 arrowValues.add("both"); 2894 } 2895 arrowValues.add("color=" + ColorUtil.colorToHexString(arrowColor)); 2896 arrowValues.add("linewidth=" + arrowLineWidth); 2897 arrowValues.add("length=" + arrowLength); 2898 arrowValues.add("gap=" + arrowGap); 2899 decorations.put("arrow", String.join(";", arrowValues)); 2900 } // if (arrowCount > 0) 2901 2902 // 2903 // bridge decorations 2904 // 2905 if (bridgeSideLeft || bridgeSideRight) { 2906 //<decoration name="bridge" value="both;linewidth=2;deckwidth=8" /> 2907 List<String> bridgeValues = new ArrayList<>(); 2908 2909 if (bridgeHasEntry && !bridgeHasExit) { 2910 bridgeValues.add("entry"); 2911 } else if (!bridgeHasEntry && bridgeHasExit) { 2912 bridgeValues.add("exit"); 2913 } else if (bridgeHasEntry && bridgeHasExit) { 2914 bridgeValues.add("both"); 2915 } 2916 if (bridgeSideLeft && !bridgeSideRight) { 2917 bridgeValues.add("left"); 2918 } else if (!bridgeSideLeft && bridgeSideRight) { 2919 bridgeValues.add("right"); 2920 } 2921 bridgeValues.add("color=" + ColorUtil.colorToHexString(bridgeColor)); 2922 bridgeValues.add("linewidth=" + bridgeLineWidth); 2923 bridgeValues.add("approachwidth=" + bridgeApproachWidth); 2924 bridgeValues.add("deckwidth=" + bridgeDeckWidth); 2925 2926 decorations.put("bridge", String.join(";", bridgeValues)); 2927 } // if (bridgeSideLeft || bridgeSideRight) 2928 2929 // 2930 // end bumper decorations 2931 // 2932 if (bumperEndStart || bumperEndStop) { 2933 //<decoration name="bumper" value="double;linewidth=2;length=6;gap=2;flipped" /> 2934 List<String> bumperValues = new ArrayList<>(); 2935 if (bumperEndStart) { 2936 bumperValues.add("start"); 2937 } else if (bumperEndStop) { 2938 bumperValues.add("stop"); 2939 } 2940 2941 if (bumperFlipped) { 2942 bumperValues.add("flip"); 2943 } 2944 bumperValues.add("color=" + ColorUtil.colorToHexString(bumperColor)); 2945 bumperValues.add("length=" + bumperLength); 2946 bumperValues.add("linewidth=" + bumperLineWidth); 2947 2948 decorations.put("bumper", String.join(";", bumperValues)); 2949 } // if (bumperCount > 0) 2950 2951 // 2952 // tunnel decorations 2953 // 2954 if (tunnelSideLeft || tunnelSideRight) { 2955 //<decoration name="tunnel" value="both;linewidth=2;floorwidth=8" /> 2956 List<String> tunnelValues = new ArrayList<>(); 2957 2958 if (tunnelHasEntry && !tunnelHasExit) { 2959 tunnelValues.add("entry"); 2960 } else if (!tunnelHasEntry && tunnelHasExit) { 2961 tunnelValues.add("exit"); 2962 } else if (tunnelHasEntry && tunnelHasExit) { 2963 tunnelValues.add("both"); 2964 } 2965 2966 if (tunnelSideLeft && !tunnelSideRight) { 2967 tunnelValues.add("left"); 2968 } else if (tunnelSideLeft && !tunnelSideRight) { 2969 tunnelValues.add("right"); 2970 } 2971 tunnelValues.add("color=" + ColorUtil.colorToHexString(tunnelColor)); 2972 tunnelValues.add("linewidth=" + tunnelLineWidth); 2973 tunnelValues.add("entrancewidth=" + tunnelEntranceWidth); 2974 tunnelValues.add("floorwidth=" + tunnelFloorWidth); 2975 2976 decorations.put("tunnel", String.join(";", tunnelValues)); 2977 } // if (tunnelSideLeft || tunnelSideRight) 2978 return decorations; 2979 } 2980 2981 /** 2982 * {@inheritDoc} 2983 */ 2984 @Override 2985 public void setDecorations(@Nonnull Map<String, String> decorations) { 2986 Color defaultTrackColor = layoutEditor.getDefaultTrackColorColor(); 2987 super.setDecorations(decorations); 2988 if (decorations != null) { 2989 for (Map.Entry<String, String> entry : decorations.entrySet()) { 2990 log.debug("Key = ''{}'', Value = ''{}''", entry.getKey(), entry.getValue()); 2991 String key = entry.getKey(); 2992 // 2993 // arrow decorations 2994 // 2995 if (key.equals("arrow")) { 2996 String arrowValue = entry.getValue(); 2997 //<decoration name="arrow" value="double;both;linewidth=1;length=12;gap=1" /> 2998 boolean atStart = true, atStop = true; 2999 boolean hasIn = false, hasOut = false; 3000 int lineWidth = 1, length = 3, gap = 1, count = 1; 3001 Color color = defaultTrackColor; 3002 String[] values = arrowValue.split(";"); 3003 for (String value : values) { 3004 if (value.equals("single")) { 3005 count = 1; 3006 } else if (value.equals("double")) { 3007 count = 2; 3008 } else if (value.equals("triple")) { 3009 count = 3; 3010 } else if (value.startsWith("style=")) { 3011 String valueString = value.substring(value.lastIndexOf("=") + 1); 3012 count = Integer.parseInt(valueString); 3013 } else if (value.equals("start")) { 3014 atStop = false; 3015 } else if (value.equals("stop")) { 3016 atStart = false; 3017 } else if (value.equals("in")) { 3018 hasIn = true; 3019 } else if (value.equals("out")) { 3020 hasOut = true; 3021 } else if (value.equals("both")) { 3022 hasIn = true; 3023 hasOut = true; 3024 } else if (value.startsWith("color=")) { 3025 String valueString = value.substring(value.lastIndexOf("=") + 1); 3026 color = Color.decode(valueString); 3027 } else if (value.startsWith("linewidth=")) { 3028 String valueString = value.substring(value.lastIndexOf("=") + 1); 3029 lineWidth = Integer.parseInt(valueString); 3030 } else if (value.startsWith("length=")) { 3031 String valueString = value.substring(value.lastIndexOf("=") + 1); 3032 length = Integer.parseInt(valueString); 3033 } else if (value.startsWith("gap=")) { 3034 String valueString = value.substring(value.lastIndexOf("=") + 1); 3035 gap = Integer.parseInt(valueString); 3036 } else { 3037 log.debug("arrow value ignored: {}", value); 3038 } 3039 } 3040 hasIn |= !hasOut; // if hasOut is false make hasIn true 3041 if (!atStart && !atStop) { // if both false 3042 atStart = true; // set both true 3043 atStop = true; 3044 } 3045 setArrowEndStart(atStart); 3046 setArrowEndStop(atStop); 3047 setArrowDirIn(hasIn); 3048 setArrowDirOut(hasOut); 3049 setArrowColor(color); 3050 setArrowLineWidth(lineWidth); 3051 setArrowLength(length); 3052 setArrowGap(gap); 3053 // set count last so it will fix ends and dir (if necessary) 3054 setArrowStyle(count); 3055 } // if (key.equals("arrow")) { 3056 // 3057 // bridge decorations 3058 // 3059 else if (key.equals("bridge")) { 3060 String bridgeValue = entry.getValue(); 3061 //<decoration name="bridge" value="both;linewidth=2;deckwidth=8" /> 3062 // right/left default true; in/out default false 3063 boolean hasLeft = true, hasRight = true, hasEntry = false, hasExit = false; 3064 int approachWidth = 4, lineWidth = 1, deckWidth = 2; 3065 Color color = defaultTrackColor; 3066 String[] values = bridgeValue.split(";"); 3067 for (String value : values) { 3068 // log.info("value[{}]: ''{}''", i, value); 3069 if (value.equals("left")) { 3070 hasRight = false; 3071 } else if (value.equals("right")) { 3072 hasLeft = false; 3073 } else if (value.equals("entry")) { 3074 hasEntry = true; 3075 } else if (value.equals("exit")) { 3076 hasExit = true; 3077 } else if (value.equals("both")) { 3078 hasEntry = true; 3079 hasExit = true; 3080 } else if (value.startsWith("color=")) { 3081 String valueString = value.substring(value.lastIndexOf("=") + 1); 3082 color = Color.decode(valueString); 3083 } else if (value.startsWith("approachwidth=")) { 3084 String valueString = value.substring(value.lastIndexOf("=") + 1); 3085 approachWidth = Integer.parseInt(valueString); 3086 } else if (value.startsWith("linewidth=")) { 3087 String valueString = value.substring(value.lastIndexOf("=") + 1); 3088 lineWidth = Integer.parseInt(valueString); 3089 } else if (value.startsWith("deckwidth=")) { 3090 String valueString = value.substring(value.lastIndexOf("=") + 1); 3091 deckWidth = Integer.parseInt(valueString); 3092 } else { 3093 log.debug("bridge value ignored: {}", value); 3094 } 3095 } 3096 // these both can't be false 3097 if (!hasLeft && !hasRight) { 3098 hasLeft = true; 3099 hasRight = true; 3100 } 3101 setBridgeSideRight(hasRight); 3102 setBridgeSideLeft(hasLeft); 3103 setBridgeHasEntry(hasEntry); 3104 setBridgeHasExit(hasExit); 3105 setBridgeColor(color); 3106 setBridgeDeckWidth(deckWidth); 3107 setBridgeLineWidth(lineWidth); 3108 setBridgeApproachWidth(approachWidth); 3109 } // if (key.equals("bridge")) { 3110 // 3111 // bumper decorations 3112 // 3113 else if (key.equals("bumper")) { 3114 String bumperValue = entry.getValue(); 3115// if (getName().equals("T15")) { 3116// log.debug("STOP"); 3117// } 3118//<decoration name="bumper" value="double;linewidth=2;length=6;gap=2;flipped" /> 3119 int lineWidth = 1, length = 4; 3120 boolean isFlipped = false, atStart = true, atStop = true; 3121 Color color = defaultTrackColor; 3122 String[] values = bumperValue.split(";"); 3123 for (String value : values) { 3124 // log.info("value[{}]: ''{}''", i, value); 3125 if (value.equals("start")) { 3126 atStop = false; 3127 } else if (value.equals("stop")) { 3128 atStart = false; 3129 } else if (value.equals("both")) { 3130 // this is the default behaviour; parameter ignored 3131 } else if (value.equals("flip")) { 3132 isFlipped = true; 3133 } else if (value.startsWith("color=")) { 3134 String valueString = value.substring(value.lastIndexOf("=") + 1); 3135 color = Color.decode(valueString); 3136 } else if (value.startsWith("linewidth=")) { 3137 String valueString = value.substring(value.lastIndexOf("=") + 1); 3138 lineWidth = Integer.parseInt(valueString); 3139 } else if (value.startsWith("length=")) { 3140 String valueString = value.substring(value.lastIndexOf("=") + 1); 3141 length = Integer.parseInt(valueString); 3142 } else { 3143 log.debug("bumper value ignored: {}", value); 3144 } 3145 } 3146 atStop |= !atStart; // if atStart is false make atStop true 3147 setBumperEndStart(atStart); 3148 setBumperEndStop(atStop); 3149 setBumperColor(color); 3150 setBumperLineWidth(lineWidth); 3151 setBumperLength(length); 3152 setBumperFlipped(isFlipped); 3153 } // if (key.equals("bumper")) { 3154 // 3155 // tunnel decorations 3156 // 3157 else if (key.equals("tunnel")) { 3158 String tunnelValue = entry.getValue(); 3159 //<decoration name="tunnel" value="both;linewidth=2;floorwidth=8" /> 3160 // right/left default true; in/out default false 3161 boolean hasLeft = true, hasRight = true, hasIn = false, hasOut = false; 3162 int entranceWidth = 4, lineWidth = 1, floorWidth = 2; 3163 Color color = defaultTrackColor; 3164 String[] values = tunnelValue.split(";"); 3165 for (String value : values) { 3166 // log.info("value[{}]: ''{}''", i, value); 3167 if (value.equals("left")) { 3168 hasRight = false; 3169 } else if (value.equals("right")) { 3170 hasLeft = false; 3171 } else if (value.equals("entry")) { 3172 hasIn = true; 3173 } else if (value.equals("exit")) { 3174 hasOut = true; 3175 } else if (value.equals("both")) { 3176 hasIn = true; 3177 hasOut = true; 3178 } else if (value.startsWith("color=")) { 3179 String valueString = value.substring(value.lastIndexOf("=") + 1); 3180 color = Color.decode(valueString); 3181 } else if (value.startsWith("entrancewidth=")) { 3182 String valueString = value.substring(value.lastIndexOf("=") + 1); 3183 entranceWidth = Integer.parseInt(valueString); 3184 } else if (value.startsWith("linewidth=")) { 3185 String valueString = value.substring(value.lastIndexOf("=") + 1); 3186 lineWidth = Integer.parseInt(valueString); 3187 } else if (value.startsWith("floorwidth=")) { 3188 String valueString = value.substring(value.lastIndexOf("=") + 1); 3189 floorWidth = Integer.parseInt(valueString); 3190 } else { 3191 log.debug("tunnel value ignored: {}", value); 3192 } 3193 } 3194 // these both can't be false 3195 if (!hasLeft && !hasRight) { 3196 hasLeft = true; 3197 hasRight = true; 3198 } 3199 setTunnelSideRight(hasRight); 3200 setTunnelSideLeft(hasLeft); 3201 setTunnelHasEntry(hasIn); 3202 setTunnelHasExit(hasOut); 3203 setTunnelColor(color); 3204 setTunnelEntranceWidth(entranceWidth); 3205 setTunnelLineWidth(lineWidth); 3206 setTunnelFloorWidth(floorWidth); 3207 } // if (tunnelValue != null) 3208 else { 3209 log.debug("Unknown decoration key: {}, value: {}", key, entry.getValue()); 3210 } 3211 } // for (Map.Entry<String, String> entry : decorations.entrySet()) 3212 } // if (decorathions != null) 3213 } // setDirections 3214 3215 /** 3216 * Arrow decoration accessor. The 0 (none) and 1 through 5 arrow decorations 3217 * are keyed to files like 3218 * program:resources/icons/decorations/ArrowStyle1.png et al. 3219 * 3220 * @return Style number 3221 */ 3222 public int getArrowStyle() { 3223 return arrowStyle; 3224 } 3225 3226 /** 3227 * Set the arrow decoration. The 0 (none) and 1 through 5 arrow decorations 3228 * are keyed to files like 3229 * program:resources/icons/decorations/ArrowStyle1.png et al. 3230 * 3231 * @param newVal the new style number 3232 */ 3233 public void setArrowStyle(int newVal) { 3234 log.trace("TrackSegmentView:setArrowStyle {} {} {}", newVal, arrowEndStart, arrowEndStop); 3235 if (arrowStyle != newVal) { 3236 if (newVal > 0) { 3237 if (!arrowEndStart && !arrowEndStop) { 3238 arrowEndStart = true; 3239 arrowEndStop = true; 3240 } 3241 if (!arrowDirIn && !arrowDirOut) { 3242 arrowDirOut = true; 3243 } 3244 } else { 3245 newVal = 0; // only positive styles allowed! 3246 } 3247 arrowStyle = newVal; 3248 layoutEditor.redrawPanel(); 3249 layoutEditor.setDirty(); 3250 } 3251 } 3252 private int arrowStyle = 0; 3253 3254 public boolean isArrowEndStart() { 3255 return arrowEndStart; 3256 } 3257 3258 public void setArrowEndStart(boolean newVal) { 3259 if (arrowEndStart != newVal) { 3260 arrowEndStart = newVal; 3261 if (!arrowEndStart && !arrowEndStop) { 3262 arrowStyle = 0; 3263 } else if (arrowStyle == 0) { 3264 arrowStyle = 1; 3265 } 3266 layoutEditor.redrawPanel(); 3267 layoutEditor.setDirty(); 3268 } 3269 } 3270 private boolean arrowEndStart = false; 3271 3272 public boolean isArrowEndStop() { 3273 return arrowEndStop; 3274 } 3275 3276 public void setArrowEndStop(boolean newVal) { 3277 if (arrowEndStop != newVal) { 3278 arrowEndStop = newVal; 3279 if (!arrowEndStart && !arrowEndStop) { 3280 arrowStyle = 0; 3281 } else if (arrowStyle == 0) { 3282 arrowStyle = 1; 3283 } 3284 layoutEditor.redrawPanel(); 3285 layoutEditor.setDirty(); 3286 } 3287 } 3288 private boolean arrowEndStop = false; 3289 3290 public boolean isArrowDirIn() { 3291 return arrowDirIn; 3292 } 3293 3294 public void setArrowDirIn(boolean newVal) { 3295 if (arrowDirIn != newVal) { 3296 arrowDirIn = newVal; 3297 if (!arrowDirIn && !arrowDirOut) { 3298 arrowStyle = 0; 3299 } else if (arrowStyle == 0) { 3300 arrowStyle = 1; 3301 } 3302 layoutEditor.redrawPanel(); 3303 layoutEditor.setDirty(); 3304 } 3305 } 3306 private boolean arrowDirIn = false; 3307 3308 public boolean isArrowDirOut() { 3309 return arrowDirOut; 3310 } 3311 3312 public void setArrowDirOut(boolean newVal) { 3313 if (arrowDirOut != newVal) { 3314 arrowDirOut = newVal; 3315 if (!arrowDirIn && !arrowDirOut) { 3316 arrowStyle = 0; 3317 } else if (arrowStyle == 0) { 3318 arrowStyle = 1; 3319 } 3320 layoutEditor.redrawPanel(); 3321 layoutEditor.setDirty(); 3322 } 3323 } 3324 private boolean arrowDirOut = false; 3325 3326 public Color getArrowColor() { 3327 return arrowColor; 3328 } 3329 3330 public void setArrowColor(Color newVal) { 3331 if (arrowColor != newVal) { 3332 arrowColor = newVal; 3333 JmriColorChooser.addRecentColor(newVal); 3334 layoutEditor.redrawPanel(); 3335 layoutEditor.setDirty(); 3336 } 3337 } 3338 private Color arrowColor = Color.BLACK; 3339 3340 public int getArrowLineWidth() { 3341 return arrowLineWidth; 3342 } 3343 3344 public void setArrowLineWidth(int newVal) { 3345 if (arrowLineWidth != newVal) { 3346 arrowLineWidth = MathUtil.pin(newVal, 1, MAX_ARROW_LINE_WIDTH); 3347 layoutEditor.redrawPanel(); 3348 layoutEditor.setDirty(); 3349 } 3350 } 3351 private int arrowLineWidth = 4; 3352 3353 public int getArrowLength() { 3354 return arrowLength; 3355 } 3356 3357 public void setArrowLength(int newVal) { 3358 if (arrowLength != newVal) { 3359 arrowLength = MathUtil.pin(newVal, 2, MAX_ARROW_LENGTH); 3360 layoutEditor.redrawPanel(); 3361 layoutEditor.setDirty(); 3362 } 3363 } 3364 private int arrowLength = 4; 3365 3366 public int getArrowGap() { 3367 return arrowGap; 3368 } 3369 3370 public void setArrowGap(int newVal) { 3371 if (arrowGap != newVal) { 3372 arrowGap = MathUtil.pin(newVal, 0, MAX_ARROW_GAP); 3373 layoutEditor.redrawPanel(); 3374 layoutEditor.setDirty(); 3375 } 3376 } 3377 private int arrowGap = 1; 3378 3379 // 3380 // bridge decoration accessors 3381 // 3382 public boolean isBridgeSideRight() { 3383 return bridgeSideRight; 3384 } 3385 3386 public void setBridgeSideRight(boolean newVal) { 3387 if (bridgeSideRight != newVal) { 3388 bridgeSideRight = newVal; 3389 layoutEditor.redrawPanel(); 3390 layoutEditor.setDirty(); 3391 } 3392 } 3393 private boolean bridgeSideRight = false; 3394 3395 public boolean isBridgeSideLeft() { 3396 return bridgeSideLeft; 3397 } 3398 3399 public void setBridgeSideLeft(boolean newVal) { 3400 if (bridgeSideLeft != newVal) { 3401 bridgeSideLeft = newVal; 3402 layoutEditor.redrawPanel(); 3403 layoutEditor.setDirty(); 3404 } 3405 } 3406 private boolean bridgeSideLeft = false; 3407 3408 public boolean isBridgeHasEntry() { 3409 return bridgeHasEntry; 3410 } 3411 3412 public void setBridgeHasEntry(boolean newVal) { 3413 if (bridgeHasEntry != newVal) { 3414 bridgeHasEntry = newVal; 3415 layoutEditor.redrawPanel(); 3416 layoutEditor.setDirty(); 3417 } 3418 } 3419 private boolean bridgeHasEntry = false; 3420 3421 public boolean isBridgeHasExit() { 3422 return bridgeHasExit; 3423 } 3424 3425 public void setBridgeHasExit(boolean newVal) { 3426 if (bridgeHasExit != newVal) { 3427 bridgeHasExit = newVal; 3428 layoutEditor.redrawPanel(); 3429 layoutEditor.setDirty(); 3430 } 3431 } 3432 private boolean bridgeHasExit = false; 3433 3434 public Color getBridgeColor() { 3435 return bridgeColor; 3436 } 3437 3438 public void setBridgeColor(Color newVal) { 3439 if (bridgeColor != newVal) { 3440 bridgeColor = newVal; 3441 JmriColorChooser.addRecentColor(newVal); 3442 layoutEditor.redrawPanel(); 3443 layoutEditor.setDirty(); 3444 } 3445 } 3446 private Color bridgeColor = Color.BLACK; 3447 3448 public int getBridgeDeckWidth() { 3449 return bridgeDeckWidth; 3450 } 3451 3452 public void setBridgeDeckWidth(int newVal) { 3453 if (bridgeDeckWidth != newVal) { 3454 bridgeDeckWidth = Math.max(MIN_BRIDGE_DECK_WIDTH, newVal); // don't let value be less than MIN 3455 layoutEditor.redrawPanel(); 3456 layoutEditor.setDirty(); 3457 } 3458 } 3459 private int bridgeDeckWidth = 10; 3460 3461 public int getBridgeLineWidth() { 3462 return bridgeLineWidth; 3463 } 3464 3465 public void setBridgeLineWidth(int newVal) { 3466 if (bridgeLineWidth != newVal) { 3467 bridgeLineWidth = Math.max(MIN_BRIDGE_LINE_WIDTH, newVal); // don't let value be less than MIN 3468 layoutEditor.redrawPanel(); 3469 layoutEditor.setDirty(); 3470 } 3471 } 3472 private int bridgeLineWidth = 1; 3473 3474 public int getBridgeApproachWidth() { 3475 return bridgeApproachWidth; 3476 } 3477 3478 public void setBridgeApproachWidth(int newVal) { 3479 if (bridgeApproachWidth != newVal) { 3480 bridgeApproachWidth = Math.max(MIN_BRIDGE_APPROACH_WIDTH, newVal); // don't let value be less than MIN 3481 layoutEditor.redrawPanel(); 3482 layoutEditor.setDirty(); 3483 } 3484 } 3485 private int bridgeApproachWidth = 4; 3486 3487 // 3488 // bumper decoration accessors 3489 // 3490 public boolean isBumperEndStart() { 3491 return bumperEndStart; 3492 } 3493 3494 public void setBumperEndStart(boolean newVal) { 3495 if (bumperEndStart != newVal) { 3496 bumperEndStart = newVal; 3497 layoutEditor.redrawPanel(); 3498 layoutEditor.setDirty(); 3499 } 3500 } 3501 private boolean bumperEndStart = false; 3502 3503 public boolean isBumperEndStop() { 3504 return bumperEndStop; 3505 } 3506 3507 public void setBumperEndStop(boolean newVal) { 3508 if (bumperEndStop != newVal) { 3509 bumperEndStop = newVal; 3510 layoutEditor.redrawPanel(); 3511 layoutEditor.setDirty(); 3512 } 3513 } 3514 private boolean bumperEndStop = false; 3515 3516 public Color getBumperColor() { 3517 return bumperColor; 3518 } 3519 3520 public void setBumperColor(Color newVal) { 3521 if (bumperColor != newVal) { 3522 bumperColor = newVal; 3523 JmriColorChooser.addRecentColor(newVal); 3524 layoutEditor.redrawPanel(); 3525 layoutEditor.setDirty(); 3526 } 3527 } 3528 private Color bumperColor = Color.BLACK; 3529 3530 public int getBumperLineWidth() { 3531 return bumperLineWidth; 3532 } 3533 3534 public void setBumperLineWidth(int newVal) { 3535 if (bumperLineWidth != newVal) { 3536 bumperLineWidth = MathUtil.pin(newVal, 1, MAX_BUMPER_LINE_WIDTH); 3537 layoutEditor.redrawPanel(); 3538 layoutEditor.setDirty(); 3539 } 3540 } 3541 3542 private int bumperLineWidth = 3; 3543 3544 public int getBumperLength() { 3545 return bumperLength; 3546 } 3547 3548 public void setBumperLength(int newVal) { 3549 if (bumperLength != newVal) { 3550 bumperLength = Math.max(MIN_BUMPER_LENGTH, newVal); // don't let value be less than MIN 3551 layoutEditor.redrawPanel(); 3552 layoutEditor.setDirty(); 3553 } 3554 } 3555 private int bumperLength = 20; 3556 3557 public boolean isBumperFlipped() { 3558 return bumperFlipped; 3559 } 3560 3561 public void setBumperFlipped(boolean newVal) { 3562 if (bumperFlipped != newVal) { 3563 bumperFlipped = newVal; 3564 layoutEditor.redrawPanel(); 3565 layoutEditor.setDirty(); 3566 } 3567 } 3568 private boolean bumperFlipped = false; 3569 3570 private void setupDefaultBumperSizes(@Nonnull LayoutEditor layoutEditor) { 3571 LayoutTrackDrawingOptions ltdo = layoutEditor.getLayoutTrackDrawingOptions(); 3572 3573 // use these as default sizes for end bumpers 3574 int tieLength = ltdo.getSideTieLength(); 3575 int tieWidth = ltdo.getSideTieWidth(); 3576 int railWidth = ltdo.getSideRailWidth(); 3577 int railGap = ltdo.getSideRailGap(); 3578 if (trackSegment.isMainline()) { 3579 tieLength = ltdo.getMainTieLength(); 3580 tieWidth = ltdo.getMainTieWidth(); 3581 railWidth = ltdo.getMainRailWidth(); 3582 railGap = ltdo.getMainRailGap(); 3583 } 3584 3585 bumperLineWidth = Math.max(railWidth, ltdo.getMainBlockLineWidth()) * 2; 3586 bumperLength = railGap + (2 * railWidth); 3587 if ((tieLength > 0) && (tieWidth > 0)) { 3588 bumperLineWidth = tieWidth; 3589 bumperLength = tieLength * 3 / 2; 3590 } 3591 bumperLineWidth = Math.max(MIN_BUMPER_LINE_WIDTH, bumperLineWidth); // don't let value be less than MIN 3592 bumperLength = Math.max(MIN_BUMPER_LENGTH, bumperLength);// don't let value be less than MIN 3593 } 3594 3595 // 3596 // tunnel decoration accessors 3597 // 3598 public boolean isTunnelSideRight() { 3599 return tunnelSideRight; 3600 } 3601 3602 public void setTunnelSideRight(boolean newVal) { 3603 if (tunnelSideRight != newVal) { 3604 tunnelSideRight = newVal; 3605 layoutEditor.redrawPanel(); 3606 layoutEditor.setDirty(); 3607 } 3608 } 3609 private boolean tunnelSideRight = false; 3610 3611 public boolean isTunnelSideLeft() { 3612 return tunnelSideLeft; 3613 } 3614 3615 public void setTunnelSideLeft(boolean newVal) { 3616 if (tunnelSideLeft != newVal) { 3617 tunnelSideLeft = newVal; 3618 layoutEditor.redrawPanel(); 3619 layoutEditor.setDirty(); 3620 } 3621 } 3622 private boolean tunnelSideLeft = false; 3623 3624 public boolean isTunnelHasEntry() { 3625 return tunnelHasEntry; 3626 } 3627 3628 public void setTunnelHasEntry(boolean newVal) { 3629 if (tunnelHasEntry != newVal) { 3630 tunnelHasEntry = newVal; 3631 layoutEditor.redrawPanel(); 3632 layoutEditor.setDirty(); 3633 } 3634 } 3635 private boolean tunnelHasEntry = false; 3636 3637 public boolean isTunnelHasExit() { 3638 return tunnelHasExit; 3639 } 3640 3641 public void setTunnelHasExit(boolean newVal) { 3642 if (tunnelHasExit != newVal) { 3643 tunnelHasExit = newVal; 3644 layoutEditor.redrawPanel(); 3645 layoutEditor.setDirty(); 3646 } 3647 } 3648 private boolean tunnelHasExit = false; 3649 3650 public Color getTunnelColor() { 3651 return tunnelColor; 3652 } 3653 3654 public void setTunnelColor(Color newVal) { 3655 if (tunnelColor != newVal) { 3656 tunnelColor = newVal; 3657 JmriColorChooser.addRecentColor(newVal); 3658 layoutEditor.redrawPanel(); 3659 layoutEditor.setDirty(); 3660 } 3661 } 3662 private Color tunnelColor = Color.BLACK; 3663 3664 public int getTunnelFloorWidth() { 3665 return tunnelFloorWidth; 3666 } 3667 3668 public void setTunnelFloorWidth(int newVal) { 3669 if (tunnelFloorWidth != newVal) { 3670 tunnelFloorWidth = Math.max(MIN_TUNNEL_FLOOR_WIDTH, newVal); // don't let value be less than MIN 3671 layoutEditor.redrawPanel(); 3672 layoutEditor.setDirty(); 3673 } 3674 } 3675 private int tunnelFloorWidth = 10; 3676 3677 public int getTunnelLineWidth() { 3678 return tunnelLineWidth; 3679 } 3680 3681 public void setTunnelLineWidth(int newVal) { 3682 if (tunnelLineWidth != newVal) { 3683 tunnelLineWidth = Math.max(MIN_TUNNEL_LINE_WIDTH, newVal); // don't let value be less than MIN 3684 layoutEditor.redrawPanel(); 3685 layoutEditor.setDirty(); 3686 } 3687 } 3688 private int tunnelLineWidth = 1; 3689 3690 public int getTunnelEntranceWidth() { 3691 return tunnelEntranceWidth; 3692 } 3693 3694 public void setTunnelEntranceWidth(int newVal) { 3695 if (tunnelEntranceWidth != newVal) { 3696 tunnelEntranceWidth = Math.max(MIN_TUNNEL_ENTRANCE_WIDTH, newVal); // don't let value be less than 1 3697 layoutEditor.redrawPanel(); 3698 layoutEditor.setDirty(); 3699 } 3700 } 3701 private int tunnelEntranceWidth = 16; 3702 3703 /** 3704 * {@inheritDoc} 3705 */ 3706 @Override 3707 @Nonnull 3708 protected List<LayoutConnectivity> getLayoutConnectivity() { 3709 return trackSegment.getLayoutConnectivity(); 3710 } 3711 3712 /** 3713 * {@inheritDoc} 3714 */ 3715 @Override 3716 @Nonnull 3717 public List<HitPointType> checkForFreeConnections() { 3718 return new ArrayList<>(); 3719 } 3720 3721 /** 3722 * {@inheritDoc} 3723 */ 3724 @Override 3725 public boolean checkForUnAssignedBlocks() { 3726 return (getLayoutBlock() != null); 3727 } 3728 3729 /** 3730 * {@inheritDoc} 3731 */ 3732 @Override 3733 public void checkForNonContiguousBlocks( 3734 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 3735 /* 3736 * For each (non-null) blocks of this track do: 3737 * #1) If it's got an entry in the blockNamesToTrackNameSetMap then 3738 * #2) If this track is already in the TrackNameSet for this block 3739 * then return (done!) 3740 * #3) else add a new set (with this block/track) to 3741 * blockNamesToTrackNameSetMap and 3742 * #4) collect all the connections in this block 3743 * <p> 3744 * Basically, we're maintaining contiguous track sets for each block found 3745 * (in blockNamesToTrackNameSetMap) 3746 */ 3747 List<Set<String>> TrackNameSets = null; 3748 Set<String> TrackNameSet = null; // assume not found (pessimist!) 3749 String blockName = getBlockName(); 3750 if (!blockName.isEmpty()) { 3751 TrackNameSets = blockNamesToTrackNameSetsMap.get(blockName); 3752 if (TrackNameSets != null) { //(#1) 3753 for (Set<String> checkTrackNameSet : TrackNameSets) { 3754 if (checkTrackNameSet.contains(getName())) { //(#2) 3755 TrackNameSet = checkTrackNameSet; 3756 break; 3757 } 3758 } 3759 } else { //(#3) 3760 log.debug("*New block (''{}'') trackNameSets", blockName); 3761 TrackNameSets = new ArrayList<>(); 3762 blockNamesToTrackNameSetsMap.put(blockName, TrackNameSets); 3763 } 3764 if (TrackNameSet == null) { 3765 TrackNameSet = new LinkedHashSet<>(); 3766 TrackNameSets.add(TrackNameSet); 3767 } 3768 if (TrackNameSet.add(getName())) { 3769 log.debug("* Add track ''{}'' to TrackNameSets for block ''{}''", getName(), blockName); 3770 } 3771 //(#4) 3772 if (getConnect1() != null) { 3773 getConnect1().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 3774 } 3775 if (getConnect2() != null) { //(#4) 3776 getConnect2().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 3777 } 3778 } 3779 } 3780 3781 /** 3782 * {@inheritDoc} 3783 */ 3784 @Override 3785 public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName, 3786 @Nonnull Set<String> TrackNameSet) { 3787 if (!TrackNameSet.contains(getName())) { 3788 // is this the blockName we're looking for? 3789 if (getBlockName().equals(blockName)) { 3790 // if we are added to the TrackNameSet 3791 if (TrackNameSet.add(getName())) { 3792 log.debug("* Add track ''{}''for block ''{}''", getName(), blockName); 3793 } 3794 // these should never be null... but just in case... 3795 // it's time to play... flood your neighbours! 3796 if (getConnect1() != null) { 3797 getConnect1().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 3798 } 3799 if (getConnect2() != null) { 3800 getConnect2().collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 3801 } 3802 } 3803 } 3804 } 3805 3806 /** 3807 * {@inheritDoc} 3808 */ 3809 @Override 3810 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 3811 setLayoutBlock(layoutBlock); 3812 } 3813 3814 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrackSegmentView.class); 3815}