001package jmri.jmrit.display.layoutEditor; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.awt.geom.*; 006import static java.lang.Float.POSITIVE_INFINITY; 007import java.text.MessageFormat; 008import java.util.List; 009import java.util.*; 010 011import javax.annotation.CheckForNull; 012import javax.annotation.Nonnull; 013import javax.swing.*; 014 015import jmri.*; 016import jmri.jmrit.display.layoutEditor.LayoutTurntable.RayTrack; 017import jmri.util.MathUtil; 018import jmri.util.swing.JmriMouseEvent; 019 020/** 021 * MVC View component for the LayoutTurntable class. 022 * 023 * @author Bob Jacobsen Copyright (c) 2020 024 * 025 */ 026public class LayoutTurntableView extends LayoutTrackView { 027 028 // defined constants 029 // operational instance variables (not saved between sessions) 030 private final jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTurntableEditor editor; 031 032 /** 033 * Constructor method. 034 * @param turntable the layout turntable to create view for. 035 * @param c where to put it 036 * @param layoutEditor what layout editor panel to put it in 037 */ 038 public LayoutTurntableView(@Nonnull LayoutTurntable turntable, 039 @Nonnull Point2D c, 040 @Nonnull LayoutEditor layoutEditor) { 041 super(turntable, c, layoutEditor); 042 this.turntable = turntable; 043 044 editor = new jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTurntableEditor(layoutEditor); 045 } 046 047 final private LayoutTurntable turntable; 048 049 final public LayoutTurntable getTurntable() { return turntable; } 050 051 /** 052 * Get a string that represents this object. This should only be used for 053 * debugging. 054 * 055 * @return the string 056 */ 057 @Override 058 public String toString() { 059 return "LayoutTurntable " + getName(); 060 } 061 062 // 063 // Accessor methods 064 // 065 /** 066 * Get the radius for this turntable. 067 * 068 * @return the radius for this turntable 069 */ 070 public double getRadius() { 071 return turntable.getRadius(); 072 } 073 074 /** 075 * Set the radius for this turntable. 076 * 077 * @param r the radius for this turntable 078 */ 079 public void setRadius(double r) { 080 turntable.setRadius(r); 081 } 082 083 /** 084 * @return the layout block name 085 */ 086 @Nonnull 087 public String getBlockName() { 088 return turntable.getBlockName(); 089 } 090 091 /** 092 * @return the layout block 093 */ 094 public LayoutBlock getLayoutBlock() { 095 return turntable.getLayoutBlock(); 096 } 097 098 /** 099 * Set up a LayoutBlock for this LayoutTurntable. 100 * 101 * @param newLayoutBlock the LayoutBlock to set 102 */ 103 public void setLayoutBlock(@CheckForNull LayoutBlock newLayoutBlock) { 104 turntable.setLayoutBlock(newLayoutBlock); 105 } 106 107 /** 108 * Set up a LayoutBlock for this LayoutTurntable. 109 * 110 * @param name the name of the new LayoutBlock 111 */ 112 public void setLayoutBlockByName(@CheckForNull String name) { 113 turntable.setLayoutBlockByName(name); 114 } 115 116 /* 117 * non-accessor methods 118 */ 119 /** 120 * @return the bounds of this turntable. 121 */ 122 @Override 123 public Rectangle2D getBounds() { 124 Rectangle2D result; 125 126 result = new Rectangle2D.Double(getCoordsCenter().getX(), getCoordsCenter().getY(), 0, 0); 127 for (int k = 0; k < getNumberRays(); k++) { 128 result.add(getRayCoordsOrdered(k)); 129 } 130 return result; 131 } 132 133 /** 134 * Add a ray at the specified angle. 135 * 136 * @param angle the angle 137 * @return the RayTrack 138 */ 139 public RayTrack addRay(double angle) { 140 return turntable.addRay(angle); 141 } 142 143 /** 144 * Get the connection for the ray with this index. 145 * 146 * @param index the index 147 * @return the connection for the ray with this value of getConnectionIndex 148 */ 149 public TrackSegment getRayConnectIndexed(int index) { 150 return turntable.getRayConnectIndexed(index); 151 } 152 153 /** 154 * Get the connection for the ray at the index in the rayTrackList. 155 * 156 * @param i the index in the rayTrackList 157 * @return the connection for the ray at that index in the rayTrackList or null 158 */ 159 public TrackSegment getRayConnectOrdered(int i) { 160 return turntable.getRayConnectOrdered(i); 161 } 162 163 /** 164 * Set the connection for the ray at the index in the rayTrackList. 165 * 166 * @param ts the connection 167 * @param index the index in the rayTrackList 168 */ 169 public void setRayConnect(TrackSegment ts, int index) { 170 turntable.setRayConnect(ts, index); 171 } 172 173 // should only be used by xml save code 174 public List<RayTrack> getRayTrackList() { 175 return turntable.getRayTrackList(); 176 } 177 178 /** 179 * Get the number of rays on turntable. 180 * 181 * @return the number of rays 182 */ 183 public int getNumberRays() { 184 return turntable.getNumberRays(); 185 } 186 187 /** 188 * Get the index for the ray at this position in the rayTrackList. 189 * 190 * @param i the position in the rayTrackList 191 * @return the index 192 */ 193 public int getRayIndex(int i) { 194 return turntable.getRayIndex(i); 195 } 196 197 /** 198 * Get the angle for the ray at this position in the rayTrackList. 199 * 200 * @param i the position in the rayTrackList 201 * @return the angle 202 */ 203 public double getRayAngle(int i) { 204 return turntable.getRayAngle(i); 205 } 206 207 /** 208 * Set the turnout and state for the ray with this index. 209 * 210 * @param index the index 211 * @param turnoutName the turnout name 212 * @param state the state 213 */ 214 public void setRayTurnout(int index, String turnoutName, int state) { 215 turntable.setRayTurnout(index, turnoutName, state); 216 } 217 218 /** 219 * Get the name of the turnout for the ray at this index. 220 * 221 * @param i the index 222 * @return name of the turnout for the ray at this index 223 */ 224 public String getRayTurnoutName(int i) { 225 return turntable.getRayTurnoutName(i); 226 } 227 228 /** 229 * Get the turnout for the ray at this index. 230 * 231 * @param i the index 232 * @return the turnout for the ray at this index 233 */ 234 public Turnout getRayTurnout(int i) { 235 return turntable.getRayTurnout(i); 236 } 237 238 /** 239 * Get the state of the turnout for the ray at this index. 240 * 241 * @param i the index 242 * @return state of the turnout for the ray at this index 243 */ 244 public int getRayTurnoutState(int i) { 245 return turntable.getRayTurnoutState(i); 246 } 247 248 /** 249 * Get if the ray at this index is disabled. 250 * 251 * @param i the index 252 * @return true if disabled 253 */ 254 public boolean isRayDisabled(int i) { 255 return turntable.isRayDisabled(i); 256 } 257 258 /** 259 * Set the disabled state of the ray at this index. 260 * 261 * @param i the index 262 * @param boo the state 263 */ 264 public void setRayDisabled(int i, boolean boo) { 265 turntable.setRayDisabled(i, boo); 266 } 267 268 /** 269 * Get the disabled when occupied state of the ray at this index. 270 * 271 * @param i the index 272 * @return the state 273 */ 274 public boolean isRayDisabledWhenOccupied(int i) { 275 return turntable.isRayDisabledWhenOccupied(i); 276 } 277 278 /** 279 * Set the disabled when occupied state of the ray at this index. 280 * 281 * @param i the index 282 * @param boo the state 283 */ 284 public void setRayDisabledWhenOccupied(int i, boolean boo) { 285 turntable.setRayDisabledWhenOccupied(i, boo); 286 } 287 288 /** 289 * Get the coordinates for the ray with this index. 290 * 291 * @param index the index 292 * @return the coordinates 293 */ 294 public Point2D getRayCoordsIndexed(int index) { 295 Point2D result = MathUtil.zeroPoint2D; 296 double rayRadius = getRadius() + LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize(); 297 for (RayTrack rt : turntable.rayTrackList) { 298 if (rt.getConnectionIndex() == index) { 299 double angle = Math.toRadians(rt.getAngle()); 300 // calculate coordinates 301 result = new Point2D.Double( 302 (getCoordsCenter().getX() + (rayRadius * Math.sin(angle))), 303 (getCoordsCenter().getY() - (rayRadius * Math.cos(angle)))); 304 break; 305 } 306 } 307 return result; 308 } 309 310 /** 311 * Get the coordinates for the ray at this index. 312 * 313 * @param i the index; zero point returned if this is out of range 314 * @return the coordinates 315 */ 316 public Point2D getRayCoordsOrdered(int i) { 317 Point2D result = MathUtil.zeroPoint2D; 318 if (i < turntable.rayTrackList.size()) { 319 RayTrack rt = turntable.rayTrackList.get(i); 320 if (rt != null) { 321 double angle = Math.toRadians(rt.getAngle()); 322 double rayRadius = getRadius() + LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize(); 323 // calculate coordinates 324 result = new Point2D.Double( 325 (getCoordsCenter().getX() + (rayRadius * Math.sin(angle))), 326 (getCoordsCenter().getY() - (rayRadius * Math.cos(angle)))); 327 } 328 } 329 return result; 330 } 331 332 /** 333 * Set the coordinates for the ray at this index. 334 * 335 * @param x the x coordinates 336 * @param y the y coordinates 337 * @param index the index 338 */ 339 public void setRayCoordsIndexed(double x, double y, int index) { 340 boolean found = false; // assume failure (pessimist!) 341 for (RayTrack rt : turntable.rayTrackList) { 342 if (rt.getConnectionIndex() == index) { 343 // convert these coordinates to an angle 344 double angle = Math.atan2(x - getCoordsCenter().getX(), y - getCoordsCenter().getY()); 345 angle = MathUtil.wrapPM360(180.0 - Math.toDegrees(angle)); 346 rt.setAngle(angle); 347 found = true; 348 break; 349 } 350 } 351 if (!found) { 352 log.error("{}.setRayCoordsIndexed({}, {}, {}); Attempt to move a non-existant ray track", 353 getName(), x, y, index); 354 } 355 } 356 357 /** 358 * Set the coordinates for the ray at this index. 359 * 360 * @param point the new coordinates 361 * @param index the index 362 */ 363 public void setRayCoordsIndexed(Point2D point, int index) { 364 setRayCoordsIndexed(point.getX(), point.getY(), index); 365 } 366 367 /** 368 * Get the coordinates for a specified connection type. 369 * 370 * @param connectionType the connection type 371 * @return the coordinates 372 */ 373 @Override 374 public Point2D getCoordsForConnectionType(HitPointType connectionType) { 375 Point2D result = getCoordsCenter(); 376 if (HitPointType.TURNTABLE_CENTER == connectionType) { 377 // nothing to see here, move along... 378 // (results are already correct) 379 } else if (HitPointType.isTurntableRayHitType(connectionType)) { 380 result = getRayCoordsIndexed(connectionType.turntableTrackIndex()); 381 } else { 382 log.error("{}.getCoordsForConnectionType({}); Invalid connection type", 383 getName(), connectionType); // NOI18N 384 } 385 return result; 386 } 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override 392 public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException { 393 LayoutTrack result = null; 394 if (HitPointType.isTurntableRayHitType(connectionType)) { 395 result = getRayConnectIndexed(connectionType.turntableTrackIndex()); 396 } else { 397 String errString = MessageFormat.format("{0}.getCoordsForConnectionType({1}); Invalid connection type", 398 getName(), connectionType); // NOI18N 399 log.error("will throw {}", errString); // NOI18N 400 throw new jmri.JmriException(errString); 401 } 402 return result; 403 } 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override 409 public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException { 410 if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) { 411 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid type", 412 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 413 log.error("will throw {}", errString); // NOI18N 414 throw new jmri.JmriException(errString); 415 } 416 if (HitPointType.isTurntableRayHitType(connectionType)) { 417 if ((o == null) || (o instanceof TrackSegment)) { 418 setRayConnect((TrackSegment) o, connectionType.turntableTrackIndex()); 419 } else { 420 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid object: {4}", 421 getName(), connectionType, o.getName(), 422 type, o.getClass().getName()); // NOI18N 423 log.error("will throw {}", errString); // NOI18N 424 throw new jmri.JmriException(errString); 425 } 426 } else { 427 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid connection type", 428 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 429 log.error("will throw {}", errString); // NOI18N 430 throw new jmri.JmriException(errString); 431 } 432 } 433 434 /** 435 * Test if ray with this index is a mainline track or not. 436 * <p> 437 * Defaults to false (not mainline) if connecting track segment is missing. 438 * 439 * @param index the index 440 * @return true if connecting track segment is mainline 441 */ 442 public boolean isMainlineIndexed(int index) { 443 boolean result = false; // assume failure (pessimist!) 444 445 for (RayTrack rt : turntable.rayTrackList) { 446 if (rt.getConnectionIndex() == index) { 447 TrackSegment ts = rt.getConnect(); 448 if (ts != null) { 449 result = ts.isMainline(); 450 break; 451 } 452 } 453 } 454 return result; 455 } 456 457 /** 458 * Test if ray at this index is a mainline track or not. 459 * <p> 460 * Defaults to false (not mainline) if connecting track segment is missing 461 * 462 * @param i the index 463 * @return true if connecting track segment is mainline 464 */ 465 public boolean isMainlineOrdered(int i) { 466 boolean result = false; // assume failure (pessimist!) 467 if (i < turntable.rayTrackList.size()) { 468 RayTrack rt = turntable.rayTrackList.get(i); 469 if (rt != null) { 470 TrackSegment ts = rt.getConnect(); 471 if (ts != null) { 472 result = ts.isMainline(); 473 } 474 } 475 } 476 return result; 477 } 478 479 // 480 // Modify coordinates methods 481 // 482 /** 483 * Scale this LayoutTrack's coordinates by the x and y factors. 484 * 485 * @param xFactor the amount to scale X coordinates 486 * @param yFactor the amount to scale Y coordinates 487 */ 488 @Override 489 public void scaleCoords(double xFactor, double yFactor) { 490 Point2D factor = new Point2D.Double(xFactor, yFactor); 491 super.setCoordsCenter(MathUtil.granulize(MathUtil.multiply(getCoordsCenter(), factor), 1.0)); 492 setRadius( getRadius() * Math.hypot(xFactor, yFactor) ); 493 } 494 495 /** 496 * Translate (2D move) this LayoutTrack's coordinates by the x and y 497 * factors. 498 * 499 * @param xFactor the amount to translate X coordinates 500 * @param yFactor the amount to translate Y coordinates 501 */ 502 @Override 503 public void translateCoords(double xFactor, double yFactor) { 504 Point2D factor = new Point2D.Double(xFactor, yFactor); 505 super.setCoordsCenter(MathUtil.add(getCoordsCenter(), factor)); 506 } 507 508 /** 509 * {@inheritDoc} 510 */ 511 @Override 512 public void rotateCoords(double angleDEG) { 513 // rotate all rayTracks 514 turntable.rayTrackList.forEach((rayTrack) -> { 515 rayTrack.setAngle(rayTrack.getAngle() + angleDEG); 516 }); 517 } 518 519 /** 520 * {@inheritDoc} 521 */ 522 @Override 523 protected HitPointType findHitPointType(Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) { 524 HitPointType result = HitPointType.NONE; // assume point not on connection 525 // note: optimization here: instead of creating rectangles for all the 526 // points to check below, we create a rectangle for the test point 527 // and test if the points below are in that rectangle instead. 528 Rectangle2D r = layoutEditor.layoutEditorControlCircleRectAt(hitPoint); 529 Point2D p, minPoint = MathUtil.zeroPoint2D; 530 531 double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize(); 532 double distance, minDistance = POSITIVE_INFINITY; 533 if (!requireUnconnected) { 534 // check the center point 535 p = getCoordsCenter(); 536 distance = MathUtil.distance(p, hitPoint); 537 if (distance < minDistance) { 538 minDistance = distance; 539 minPoint = p; 540 result = HitPointType.TURNTABLE_CENTER; 541 } 542 } 543 544 for (int k = 0; k < getNumberRays(); k++) { 545 if (!requireUnconnected || (getRayConnectOrdered(k) == null)) { 546 p = getRayCoordsOrdered(k); 547 distance = MathUtil.distance(p, hitPoint); 548 if (distance < minDistance) { 549 minDistance = distance; 550 minPoint = p; 551 result = HitPointType.turntableTrackIndexedValue(k); 552 } 553 } 554 } 555 if ((useRectangles && !r.contains(minPoint)) 556 || (!useRectangles && (minDistance > circleRadius))) { 557 result = HitPointType.NONE; 558 } 559 return result; 560 } 561 562 public String tLayoutBlockName = ""; 563 564 /** 565 * Is this turntable turnout controlled? 566 * 567 * @return true if so 568 */ 569 public boolean isTurnoutControlled() { 570 return turntable.isTurnoutControlled(); 571 } 572 573 /** 574 * Set if this turntable is turnout controlled. 575 * 576 * @param boo set true if so 577 */ 578 public void setTurnoutControlled(boolean boo) { 579 turntable.setTurnoutControlled(boo); 580 } 581 582 private JPopupMenu popupMenu = null; 583 584 /** 585 * {@inheritDoc} 586 */ 587 @Override 588 @Nonnull 589 protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent) { 590 if (popupMenu != null) { 591 popupMenu.removeAll(); 592 } else { 593 popupMenu = new JPopupMenu(); 594 } 595 596 JMenuItem jmi = popupMenu.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Turntable")) + getName()); 597 jmi.setEnabled(false); 598 599 LayoutBlock lb = getLayoutBlock(); 600 if (lb == null) { 601 jmi = popupMenu.add(Bundle.getMessage("NoBlock")); 602 } else { 603 String displayName = lb.getDisplayName(); 604 jmi = popupMenu.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + displayName); 605 } 606 jmi.setEnabled(false); 607 608 /// if there are any track connections 609 if (!turntable.rayTrackList.isEmpty()) { 610 JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections")); 611 turntable.rayTrackList.forEach((rt) -> { 612 TrackSegment ts = rt.getConnect(); 613 if (ts != null) { 614 TrackSegmentView tsv = layoutEditor.getTrackSegmentView(ts); 615 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "" + rt.getConnectionIndex()) + ts.getName()) { 616 @Override 617 public void actionPerformed(ActionEvent e) { 618 layoutEditor.setSelectionRect(tsv.getBounds()); 619 tsv.showPopup(); 620 } 621 }); 622 } 623 }); 624 popupMenu.add(connectionsMenu); 625 } 626 627 popupMenu.add(new JSeparator(JSeparator.HORIZONTAL)); 628 629 popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonEdit")) { 630 @Override 631 public void actionPerformed(ActionEvent e) { 632 editor.editLayoutTrack(LayoutTurntableView.this); 633 } 634 }); 635 popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) { 636 @Override 637 public void actionPerformed(ActionEvent e) { 638 if (removeInlineLogixNG() && layoutEditor.removeTurntable(turntable)) { 639 // Returned true if user did not cancel 640 remove(); 641 dispose(); 642 } 643 } 644 }); 645 layoutEditor.setShowAlignmentMenu(popupMenu); 646 addCommonPopupItems(mouseEvent, popupMenu); 647 popupMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY()); 648 return popupMenu; 649 } 650 651 private JPopupMenu rayPopup = null; 652 653 protected void showRayPopUp(JmriMouseEvent e, int index) { 654 if (rayPopup != null) { 655 rayPopup.removeAll(); 656 } else { 657 rayPopup = new JPopupMenu(); 658 } 659 660 for (RayTrack rt : turntable.rayTrackList) { 661 if (rt.getConnectionIndex() == index) { 662 JMenuItem jmi = rayPopup.add("Turntable Ray " + index); 663 jmi.setEnabled(false); 664 665 rayPopup.add(new AbstractAction( 666 Bundle.getMessage("MakeLabel", 667 Bundle.getMessage("Connected")) 668 + rt.getConnect().getName()) { 669 @Override 670 public void actionPerformed(ActionEvent e) { 671 LayoutEditorFindItems lf = layoutEditor.getFinder(); 672 LayoutTrack lt = lf.findObjectByName(rt.getConnect().getName()); 673 674 // this shouldn't ever be null... however... 675 if (lt != null) { 676 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 677 layoutEditor.setSelectionRect(ltv.getBounds()); 678 ltv.showPopup(); 679 } 680 } 681 }); 682 683 if (rt.getTurnout() != null) { 684 String info = rt.getTurnout().getDisplayName(); 685 String stateString = getTurnoutStateString(rt.getTurnoutState()); 686 if (!stateString.isEmpty()) { 687 info += " (" + stateString + ")"; 688 } 689 jmi = rayPopup.add(info); 690 jmi.setEnabled(false); 691 692 rayPopup.add(new JSeparator(JSeparator.HORIZONTAL)); 693 694 JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(Bundle.getMessage("Disabled")); 695 cbmi.setSelected(rt.isDisabled()); 696 rayPopup.add(cbmi); 697 cbmi.addActionListener((java.awt.event.ActionEvent e2) -> { 698 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e2.getSource(); 699 rt.setDisabled(o.isSelected()); 700 }); 701 702 cbmi = new JCheckBoxMenuItem(Bundle.getMessage("DisabledWhenOccupied")); 703 cbmi.setSelected(rt.isDisabledWhenOccupied()); 704 rayPopup.add(cbmi); 705 cbmi.addActionListener((java.awt.event.ActionEvent e3) -> { 706 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e3.getSource(); 707 rt.setDisabledWhenOccupied(o.isSelected()); 708 }); 709 } 710 rayPopup.show(e.getComponent(), e.getX(), e.getY()); 711 break; 712 } 713 } 714 } 715 716 /** 717 * Set turntable position to the ray with this index. 718 * 719 * @param index the index 720 */ 721 public void setPosition(int index) { 722 turntable.setPosition(index); 723 } 724 725 /** 726 * Get the turntable position. 727 * 728 * @return the turntable position 729 */ 730 public int getPosition() { 731 return turntable.getPosition(); 732 } 733 734 /** 735 * Delete this ray track. 736 * 737 * @param rayTrack the ray track 738 */ 739 public void deleteRay(RayTrack rayTrack) { 740 TrackSegment t = null; 741 if (turntable.rayTrackList == null) { 742 log.error("{}.deleteRay(null); rayTrack is null", getName()); 743 } else { 744 t = rayTrack.getConnect(); 745 getRayTrackList().remove(rayTrack.getConnectionIndex()); 746 rayTrack.dispose(); 747 } 748 if (t != null) { 749 layoutEditor.removeTrackSegment(t); 750 } 751 752 // update the panel 753 layoutEditor.redrawPanel(); 754 layoutEditor.setDirty(); 755 } 756 757 /** 758 * Clean up when this object is no longer needed. Should not be called while 759 * the object is still displayed; see remove(). 760 */ 761 public void dispose() { 762 if (popupMenu != null) { 763 popupMenu.removeAll(); 764 } 765 popupMenu = null; 766 turntable.rayTrackList.forEach((rt) -> { 767 rt.dispose(); 768 }); 769 } 770 771 /** 772 * Remove this object from display and persistance. 773 */ 774 public void remove() { 775 // remove from persistance by flagging inactive 776 active = false; 777 } 778 779 private boolean active = true; 780 781 /** 782 * @return "active" true means that the object is still displayed, and should be stored. 783 */ 784 public boolean isActive() { 785 return active; 786 } 787 788 public static class RayTrackVisuals { 789 790 // public final RayTrack track; 791 792 // persistant instance variables 793 private double rayAngle = 0.0; 794 795 /** 796 * Get the angle for this ray. 797 * 798 * @return the angle for this ray 799 */ 800 public double getAngle() { 801 return rayAngle; 802 } 803 804 /** 805 * Set the angle for this ray. 806 * 807 * @param an the angle for this ray 808 */ 809 public void setAngle(double an) { 810 rayAngle = MathUtil.wrapPM360(an); 811 } 812 813 public RayTrackVisuals(RayTrack track) { 814 // this.track = track; 815 } 816 } 817 818 /** 819 * Draw track decorations. 820 * 821 * This type of track has none, so this method is empty. 822 */ 823 @Override 824 protected void drawDecorations(Graphics2D g2) {} 825 826 /** 827 * {@inheritDoc} 828 */ 829 @Override 830 protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) { 831 log.trace("LayoutTurntable:draw1 at {}", getCoordsCenter()); 832 float trackWidth = 2.F; 833 double diameter = 2.f * getRadius(); 834 835 if (isBlock && isMain) { 836 double radius2 = Math.max(getRadius() / 4.f, trackWidth * 2); 837 double diameter2 = radius2 * 2.f; 838 Stroke stroke = g2.getStroke(); 839 Color color = g2.getColor(); 840 // draw turntable circle - default track color, side track width 841 g2.setStroke(new BasicStroke(trackWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 842 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 843 g2.draw(new Ellipse2D.Double(getCoordsCenter().getX() - getRadius(), getCoordsCenter().getY() - getRadius(), diameter, diameter)); 844 g2.draw(new Ellipse2D.Double(getCoordsCenter().getX() - radius2, getCoordsCenter().getY() - radius2, diameter2, diameter2)); 845 g2.setStroke(stroke); 846 g2.setColor(color); 847 } 848 849 // draw ray tracks 850 for (int j = 0; j < getNumberRays(); j++) { 851 boolean main = false; 852 Color color = null; 853 TrackSegment ts = getRayConnectOrdered(j); 854 if (ts != null) { 855 main = ts.isMainline(); 856 } 857 858 if (isBlock) { 859 if (ts == null) { 860 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 861 } else { 862 LayoutBlock lb = ts.getLayoutBlock(); 863 if (lb != null) { 864 color = g2.getColor(); 865 setColorForTrackBlock(g2, lb); 866 } 867 } 868 } 869 870 Point2D pt2 = getRayCoordsOrdered(j); 871 Point2D delta = MathUtil.normalize(MathUtil.subtract(pt2, getCoordsCenter()), getRadius()); 872 Point2D pt1 = MathUtil.add(getCoordsCenter(), delta); 873 if (main == isMain) { 874 g2.draw(new Line2D.Double(pt1, pt2)); 875 } 876 877 int knownPosition = getPosition(); 878 int commandedPosition = turntable.getCommandedPosition(); 879 880 // Don't draw the bridge if animating and position is changing 881 if (layoutEditor.isAnimating() && isTurnoutControlled() && knownPosition != commandedPosition) { 882 continue; 883 } 884 885 int currentPositionIndex = (knownPosition != -1) ? getRayIndex(knownPosition) : -1; 886 if (isMain && isTurnoutControlled() && (currentPositionIndex == j) ) { 887 if (isBlock) { 888 LayoutBlock lb = getLayoutBlock(); 889 if (lb != null) { 890 color = (color == null) ? g2.getColor() : color; 891 setColorForTrackBlock(g2, lb); 892 } else { 893 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 894 } 895 } 896 // Draw an asymmetric bridge to act as a pointer. 897 // The long end (pt2) points toward the selected ray. 898 // The short end (short_pt1) points away from it, at 0.8 * radius. 899 Point2D short_delta = MathUtil.normalize(delta, getRadius() * 0.8); 900 Point2D short_pt1 = MathUtil.subtract(getCoordsCenter(), short_delta); 901 g2.draw(new Line2D.Double(short_pt1, pt2)); 902 } 903 if (color != null) { 904 g2.setColor(color); /// restore previous color 905 } 906 } 907 } 908 909 /** 910 * {@inheritDoc} 911 */ 912 @Override 913 protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) { 914 log.trace("LayoutTurntable:draw2 at {}", getCoordsCenter()); 915 916 float trackWidth = 2.F; 917 float halfTrackWidth = trackWidth / 2.f; 918 919 // draw ray tracks 920 for (int j = 0; j < getNumberRays(); j++) { 921 boolean main = false; 922// Color c = null; 923 TrackSegment ts = getRayConnectOrdered(j); 924 if (ts != null) { 925 main = ts.isMainline(); 926// LayoutBlock lb = ts.getLayoutBlock(); 927// if (lb != null) { 928// c = g2.getColor(); 929// setColorForTrackBlock(g2, lb); 930// } 931 } 932 Point2D pt2 = getRayCoordsOrdered(j); 933 Point2D vDelta = MathUtil.normalize(MathUtil.subtract(pt2, getCoordsCenter()), getRadius()); 934 Point2D vDeltaO = MathUtil.normalize(MathUtil.orthogonal(vDelta), railDisplacement); 935 Point2D pt1 = MathUtil.add(getCoordsCenter(), vDelta); 936 Point2D pt1L = MathUtil.subtract(pt1, vDeltaO); 937 Point2D pt1R = MathUtil.add(pt1, vDeltaO); 938 Point2D pt2L = MathUtil.subtract(pt2, vDeltaO); 939 Point2D pt2R = MathUtil.add(pt2, vDeltaO); 940 if (main == isMain) { 941 log.trace(" draw main at {} {}, {} {}", pt1L, pt2L, pt1R, pt2R); 942 g2.draw(new Line2D.Double(pt1L, pt2L)); 943 g2.draw(new Line2D.Double(pt1R, pt2R)); 944 } 945 // getPosition() will return -1 if no ray is selected (all turnouts are closed). 946 int currentPositionIndex = (getPosition() != -1) ? getRayIndex(getPosition()) : -1; 947 if (isMain && isTurnoutControlled() && (currentPositionIndex == j)) { 948// LayoutBlock lb = getLayoutBlock(); 949// if (lb != null) { 950// c = g2.getColor(); 951// setColorForTrackBlock(g2, lb); 952// } else { 953// g2.setColor(layoutEditor.getDefaultTrackColorColor()); 954// } 955 vDelta = MathUtil.normalize(vDelta, getRadius() - halfTrackWidth); 956 pt1 = MathUtil.subtract(getCoordsCenter(), vDelta); 957 pt1L = MathUtil.subtract(pt1, vDeltaO); 958 pt1R = MathUtil.add(pt1, vDeltaO); 959 log.trace(" draw not main at {} {}, {} {}", pt1L, pt2L, pt1R, pt2R); 960 g2.draw(new Line2D.Double(pt1L, pt2L)); 961 g2.draw(new Line2D.Double(pt1R, pt2R)); 962 } 963// if (c != null) { 964// g2.setColor(c); /// restore previous color 965// } 966 } 967 } 968 969 /** 970 * {@inheritDoc} 971 */ 972 @Override 973 protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) { 974 log.trace("LayoutTurntable:highlightUnconnected"); 975 for (int j = 0; j < getNumberRays(); j++) { 976 if ( (specificType == HitPointType.NONE) 977 || (specificType == (HitPointType.turntableTrackIndexedValue(j))) 978 ) 979 { 980 if (getRayConnectOrdered(j) == null) { 981 Point2D pt = getRayCoordsOrdered(j); 982 log.trace(" draw at {}", pt); 983 g2.fill(trackControlCircleAt(pt)); 984 } 985 } 986 } 987 } 988 989 /** 990 * Draw this turntable's controls. 991 * 992 * @param g2 the graphics port to draw to 993 */ 994 @Override 995 protected void drawTurnoutControls(Graphics2D g2) { 996 log.trace("LayoutTurntable:drawTurnoutControls"); 997 if (isTurnoutControlled()) { 998 // draw control circles at all but current position ray tracks 999 for (int j = 0; j < getNumberRays(); j++) { 1000 if (getPosition() != j) { 1001 RayTrack rt = turntable.rayTrackList.get(j); 1002 if (!rt.isDisabled() && !(rt.isDisabledWhenOccupied() && rt.isOccupied())) { 1003 Point2D pt = getRayCoordsOrdered(j); 1004 g2.draw(trackControlCircleAt(pt)); 1005 } 1006 } 1007 } 1008 } 1009 } 1010 1011 /** 1012 * Draw this turntable's edit controls. 1013 * 1014 * @param g2 the graphics port to draw to 1015 */ 1016 @Override 1017 protected void drawEditControls(Graphics2D g2) { 1018 Point2D pt = getCoordsCenter(); 1019 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 1020 g2.draw(trackControlCircleAt(pt)); 1021 1022 for (int j = 0; j < getNumberRays(); j++) { 1023 pt = getRayCoordsOrdered(j); 1024 1025 if (getRayConnectOrdered(j) == null) { 1026 g2.setColor(Color.red); 1027 } else { 1028 g2.setColor(Color.green); 1029 } 1030 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 1031 } 1032 } 1033 1034 /** 1035 * {@inheritDoc} 1036 */ 1037 @Override 1038 protected void reCheckBlockBoundary() { 1039 // nothing to see here... move along... 1040 } 1041 1042 /** 1043 * {@inheritDoc} 1044 */ 1045 @Override 1046 protected List<LayoutConnectivity> getLayoutConnectivity() { 1047 // nothing to see here... move along... 1048 return null; 1049 } 1050 1051 /** 1052 * {@inheritDoc} 1053 */ 1054 @Override 1055 public List<HitPointType> checkForFreeConnections() { 1056 List<HitPointType> result = new ArrayList<>(); 1057 1058 for (int k = 0; k < getNumberRays(); k++) { 1059 if (getRayConnectOrdered(k) == null) { 1060 result.add(HitPointType.turntableTrackIndexedValue(k)); 1061 } 1062 } 1063 return result; 1064 } 1065 1066 /** 1067 * {@inheritDoc} 1068 */ 1069 @Override 1070 public boolean checkForUnAssignedBlocks() { 1071 // Layout turnouts get their block information from the 1072 // track segments attached to their rays so... 1073 // nothing to see here... move along... 1074 return true; 1075 } 1076 1077 /** 1078 * {@inheritDoc} 1079 */ 1080 @Override 1081 public void checkForNonContiguousBlocks( 1082 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 1083 /* 1084 * For each (non-null) blocks of this track do: 1085 * #1) If it's got an entry in the blockNamesToTrackNameSetMap then 1086 * #2) If this track is already in the TrackNameSet for this block 1087 * then return (done!) 1088 * #3) else add a new set (with this block// track) to 1089 * blockNamesToTrackNameSetMap and check all the connections in this 1090 * block (by calling the 2nd method below) 1091 * <p> 1092 * Basically, we're maintaining contiguous track sets for each block found 1093 * (in blockNamesToTrackNameSetMap) 1094 */ 1095 1096 // We're using a map here because it is convient to 1097 // use it to pair up blocks and connections 1098 Map<LayoutTrack, String> blocksAndTracksMap = new HashMap<>(); 1099 for (int k = 0; k < getNumberRays(); k++) { 1100 TrackSegment ts = getRayConnectOrdered(k); 1101 if (ts != null) { 1102 String blockName = ts.getBlockName(); 1103 blocksAndTracksMap.put(ts, blockName); 1104 } 1105 } 1106 1107 List<Set<String>> TrackNameSets; 1108 Set<String> TrackNameSet; 1109 for (Map.Entry<LayoutTrack, String> entry : blocksAndTracksMap.entrySet()) { 1110 LayoutTrack theConnect = entry.getKey(); 1111 String theBlockName = entry.getValue(); 1112 1113 TrackNameSet = null; // assume not found (pessimist!) 1114 TrackNameSets = blockNamesToTrackNameSetsMap.get(theBlockName); 1115 if (TrackNameSets != null) { // (#1) 1116 for (Set<String> checkTrackNameSet : TrackNameSets) { 1117 if (checkTrackNameSet.contains(getName())) { // (#2) 1118 TrackNameSet = checkTrackNameSet; 1119 break; 1120 } 1121 } 1122 } else { // (#3) 1123 log.debug("*New block (''{}'') trackNameSets", theBlockName); 1124 TrackNameSets = new ArrayList<>(); 1125 blockNamesToTrackNameSetsMap.put(theBlockName, TrackNameSets); 1126 } 1127 if (TrackNameSet == null) { 1128 TrackNameSet = new LinkedHashSet<>(); 1129 TrackNameSets.add(TrackNameSet); 1130 } 1131 if (TrackNameSet.add(getName())) { 1132 log.debug("* Add track ''{}'' to trackNameSet for block ''{}''", getName(), theBlockName); 1133 } 1134 theConnect.collectContiguousTracksNamesInBlockNamed(theBlockName, TrackNameSet); 1135 } 1136 } 1137 1138 /** 1139 * {@inheritDoc} 1140 */ 1141 @Override 1142 public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName, 1143 @Nonnull Set<String> TrackNameSet) { 1144 if (!TrackNameSet.contains(getName())) { 1145 // for all the rays with matching blocks in this turnout 1146 // #1) if its track segment's block is in this block 1147 // #2) add turntable to TrackNameSet (if not already there) 1148 // #3) if the track segment isn't in the TrackNameSet 1149 // #4) flood it 1150 for (int k = 0; k < getNumberRays(); k++) { 1151 TrackSegment ts = getRayConnectOrdered(k); 1152 if (ts != null) { 1153 String blk = ts.getBlockName(); 1154 if ((!blk.isEmpty()) && (blk.equals(blockName))) { // (#1) 1155 // if we are added to the TrackNameSet 1156 if (TrackNameSet.add(getName())) { 1157 log.debug("* Add track ''{}'' for block ''{}''", getName(), blockName); 1158 } 1159 // it's time to play... flood your neighbours! 1160 ts.collectContiguousTracksNamesInBlockNamed(blockName, 1161 TrackNameSet); // (#4) 1162 } 1163 } 1164 } 1165 } 1166 } 1167 1168 /** 1169 * {@inheritDoc} 1170 */ 1171 @Override 1172 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 1173 // turntables don't have blocks... 1174 // nothing to see here, move along... 1175 } 1176 1177 /** 1178 * {@inheritDoc} 1179 */ 1180 @Override 1181 public boolean canRemove() { 1182 return true; 1183 } 1184 1185 1186 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurntableView.class); 1187}