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 float halfTrackWidth = trackWidth / 2.f; 834 double diameter = 2.f * getRadius(); 835 836 if (isBlock && isMain) { 837 double radius2 = Math.max(getRadius() / 4.f, trackWidth * 2); 838 double diameter2 = radius2 * 2.f; 839 Stroke stroke = g2.getStroke(); 840 Color color = g2.getColor(); 841 // draw turntable circle - default track color, side track width 842 g2.setStroke(new BasicStroke(trackWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 843 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 844 g2.draw(new Ellipse2D.Double(getCoordsCenter().getX() - getRadius(), getCoordsCenter().getY() - getRadius(), diameter, diameter)); 845 g2.draw(new Ellipse2D.Double(getCoordsCenter().getX() - radius2, getCoordsCenter().getY() - radius2, diameter2, diameter2)); 846 g2.setStroke(stroke); 847 g2.setColor(color); 848 } 849 850 // draw ray tracks 851 for (int j = 0; j < getNumberRays(); j++) { 852 boolean main = false; 853 Color color = null; 854 TrackSegment ts = getRayConnectOrdered(j); 855 if (ts != null) { 856 main = ts.isMainline(); 857 } 858 859 if (isBlock) { 860 if (ts == null) { 861 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 862 } else { 863 LayoutBlock lb = ts.getLayoutBlock(); 864 if (lb != null) { 865 color = g2.getColor(); 866 setColorForTrackBlock(g2, lb); 867 } 868 } 869 } 870 871 Point2D pt2 = getRayCoordsOrdered(j); 872 Point2D delta = MathUtil.normalize(MathUtil.subtract(pt2, getCoordsCenter()), getRadius()); 873 Point2D pt1 = MathUtil.add(getCoordsCenter(), delta); 874 if (main == isMain) { 875 g2.draw(new Line2D.Double(pt1, pt2)); 876 } 877 if (isMain && isTurnoutControlled() && (getPosition() == j)) { 878 if (isBlock) { 879 LayoutBlock lb = getLayoutBlock(); 880 if (lb != null) { 881 color = (color == null) ? g2.getColor() : color; 882 setColorForTrackBlock(g2, lb); 883 } else { 884 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 885 } 886 } 887 delta = MathUtil.normalize(delta, getRadius() - halfTrackWidth); 888 pt1 = MathUtil.subtract(getCoordsCenter(), delta); 889 g2.draw(new Line2D.Double(pt1, pt2)); 890 } 891 if (color != null) { 892 g2.setColor(color); /// restore previous color 893 } 894 } 895 } 896 897 /** 898 * {@inheritDoc} 899 */ 900 @Override 901 protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) { 902 log.trace("LayoutTurntable:draw2 at {}", getCoordsCenter()); 903 904 float trackWidth = 2.F; 905 float halfTrackWidth = trackWidth / 2.f; 906 907 // draw ray tracks 908 for (int j = 0; j < getNumberRays(); j++) { 909 boolean main = false; 910// Color c = null; 911 TrackSegment ts = getRayConnectOrdered(j); 912 if (ts != null) { 913 main = ts.isMainline(); 914// LayoutBlock lb = ts.getLayoutBlock(); 915// if (lb != null) { 916// c = g2.getColor(); 917// setColorForTrackBlock(g2, lb); 918// } 919 } 920 Point2D pt2 = getRayCoordsOrdered(j); 921 Point2D vDelta = MathUtil.normalize(MathUtil.subtract(pt2, getCoordsCenter()), getRadius()); 922 Point2D vDeltaO = MathUtil.normalize(MathUtil.orthogonal(vDelta), railDisplacement); 923 Point2D pt1 = MathUtil.add(getCoordsCenter(), vDelta); 924 Point2D pt1L = MathUtil.subtract(pt1, vDeltaO); 925 Point2D pt1R = MathUtil.add(pt1, vDeltaO); 926 Point2D pt2L = MathUtil.subtract(pt2, vDeltaO); 927 Point2D pt2R = MathUtil.add(pt2, vDeltaO); 928 if (main == isMain) { 929 log.trace(" draw main at {} {}, {} {}", pt1L, pt2L, pt1R, pt2R); 930 g2.draw(new Line2D.Double(pt1L, pt2L)); 931 g2.draw(new Line2D.Double(pt1R, pt2R)); 932 } 933 if (isMain && isTurnoutControlled() && (getPosition() == j)) { 934// LayoutBlock lb = getLayoutBlock(); 935// if (lb != null) { 936// c = g2.getColor(); 937// setColorForTrackBlock(g2, lb); 938// } else { 939// g2.setColor(layoutEditor.getDefaultTrackColorColor()); 940// } 941 vDelta = MathUtil.normalize(vDelta, getRadius() - halfTrackWidth); 942 pt1 = MathUtil.subtract(getCoordsCenter(), vDelta); 943 pt1L = MathUtil.subtract(pt1, vDeltaO); 944 pt1R = MathUtil.add(pt1, vDeltaO); 945 log.trace(" draw not main at {} {}, {} {}", pt1L, pt2L, pt1R, pt2R); 946 g2.draw(new Line2D.Double(pt1L, pt2L)); 947 g2.draw(new Line2D.Double(pt1R, pt2R)); 948 } 949// if (c != null) { 950// g2.setColor(c); /// restore previous color 951// } 952 } 953 } 954 955 /** 956 * {@inheritDoc} 957 */ 958 @Override 959 protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) { 960 log.trace("LayoutTurntable:highlightUnconnected"); 961 for (int j = 0; j < getNumberRays(); j++) { 962 if ( (specificType == HitPointType.NONE) 963 || (specificType == (HitPointType.turntableTrackIndexedValue(j))) 964 ) 965 { 966 if (getRayConnectOrdered(j) == null) { 967 Point2D pt = getRayCoordsOrdered(j); 968 log.trace(" draw at {}", pt); 969 g2.fill(trackControlCircleAt(pt)); 970 } 971 } 972 } 973 } 974 975 /** 976 * Draw this turntable's controls. 977 * 978 * @param g2 the graphics port to draw to 979 */ 980 @Override 981 protected void drawTurnoutControls(Graphics2D g2) { 982 log.trace("LayoutTurntable:drawTurnoutControls"); 983 if (isTurnoutControlled()) { 984 // draw control circles at all but current position ray tracks 985 for (int j = 0; j < getNumberRays(); j++) { 986 if (getPosition() != j) { 987 RayTrack rt = turntable.rayTrackList.get(j); 988 if (!rt.isDisabled() && !(rt.isDisabledWhenOccupied() && rt.isOccupied())) { 989 Point2D pt = getRayCoordsOrdered(j); 990 g2.draw(trackControlCircleAt(pt)); 991 } 992 } 993 } 994 } 995 } 996 997 /** 998 * Draw this turntable's edit controls. 999 * 1000 * @param g2 the graphics port to draw to 1001 */ 1002 @Override 1003 protected void drawEditControls(Graphics2D g2) { 1004 Point2D pt = getCoordsCenter(); 1005 g2.setColor(layoutEditor.getDefaultTrackColorColor()); 1006 g2.draw(trackControlCircleAt(pt)); 1007 1008 for (int j = 0; j < getNumberRays(); j++) { 1009 pt = getRayCoordsOrdered(j); 1010 1011 if (getRayConnectOrdered(j) == null) { 1012 g2.setColor(Color.red); 1013 } else { 1014 g2.setColor(Color.green); 1015 } 1016 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 1017 } 1018 } 1019 1020 /** 1021 * {@inheritDoc} 1022 */ 1023 @Override 1024 protected void reCheckBlockBoundary() { 1025 // nothing to see here... move along... 1026 } 1027 1028 /** 1029 * {@inheritDoc} 1030 */ 1031 @Override 1032 protected List<LayoutConnectivity> getLayoutConnectivity() { 1033 // nothing to see here... move along... 1034 return null; 1035 } 1036 1037 /** 1038 * {@inheritDoc} 1039 */ 1040 @Override 1041 public List<HitPointType> checkForFreeConnections() { 1042 List<HitPointType> result = new ArrayList<>(); 1043 1044 for (int k = 0; k < getNumberRays(); k++) { 1045 if (getRayConnectOrdered(k) == null) { 1046 result.add(HitPointType.turntableTrackIndexedValue(k)); 1047 } 1048 } 1049 return result; 1050 } 1051 1052 /** 1053 * {@inheritDoc} 1054 */ 1055 @Override 1056 public boolean checkForUnAssignedBlocks() { 1057 // Layout turnouts get their block information from the 1058 // track segments attached to their rays so... 1059 // nothing to see here... move along... 1060 return true; 1061 } 1062 1063 /** 1064 * {@inheritDoc} 1065 */ 1066 @Override 1067 public void checkForNonContiguousBlocks( 1068 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 1069 /* 1070 * For each (non-null) blocks of this track do: 1071 * #1) If it's got an entry in the blockNamesToTrackNameSetMap then 1072 * #2) If this track is already in the TrackNameSet for this block 1073 * then return (done!) 1074 * #3) else add a new set (with this block// track) to 1075 * blockNamesToTrackNameSetMap and check all the connections in this 1076 * block (by calling the 2nd method below) 1077 * <p> 1078 * Basically, we're maintaining contiguous track sets for each block found 1079 * (in blockNamesToTrackNameSetMap) 1080 */ 1081 1082 // We're using a map here because it is convient to 1083 // use it to pair up blocks and connections 1084 Map<LayoutTrack, String> blocksAndTracksMap = new HashMap<>(); 1085 for (int k = 0; k < getNumberRays(); k++) { 1086 TrackSegment ts = getRayConnectOrdered(k); 1087 if (ts != null) { 1088 String blockName = ts.getBlockName(); 1089 blocksAndTracksMap.put(ts, blockName); 1090 } 1091 } 1092 1093 List<Set<String>> TrackNameSets; 1094 Set<String> TrackNameSet; 1095 for (Map.Entry<LayoutTrack, String> entry : blocksAndTracksMap.entrySet()) { 1096 LayoutTrack theConnect = entry.getKey(); 1097 String theBlockName = entry.getValue(); 1098 1099 TrackNameSet = null; // assume not found (pessimist!) 1100 TrackNameSets = blockNamesToTrackNameSetsMap.get(theBlockName); 1101 if (TrackNameSets != null) { // (#1) 1102 for (Set<String> checkTrackNameSet : TrackNameSets) { 1103 if (checkTrackNameSet.contains(getName())) { // (#2) 1104 TrackNameSet = checkTrackNameSet; 1105 break; 1106 } 1107 } 1108 } else { // (#3) 1109 log.debug("*New block (''{}'') trackNameSets", theBlockName); 1110 TrackNameSets = new ArrayList<>(); 1111 blockNamesToTrackNameSetsMap.put(theBlockName, TrackNameSets); 1112 } 1113 if (TrackNameSet == null) { 1114 TrackNameSet = new LinkedHashSet<>(); 1115 TrackNameSets.add(TrackNameSet); 1116 } 1117 if (TrackNameSet.add(getName())) { 1118 log.debug("* Add track ''{}'' to trackNameSet for block ''{}''", getName(), theBlockName); 1119 } 1120 theConnect.collectContiguousTracksNamesInBlockNamed(theBlockName, TrackNameSet); 1121 } 1122 } 1123 1124 /** 1125 * {@inheritDoc} 1126 */ 1127 @Override 1128 public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName, 1129 @Nonnull Set<String> TrackNameSet) { 1130 if (!TrackNameSet.contains(getName())) { 1131 // for all the rays with matching blocks in this turnout 1132 // #1) if its track segment's block is in this block 1133 // #2) add turntable to TrackNameSet (if not already there) 1134 // #3) if the track segment isn't in the TrackNameSet 1135 // #4) flood it 1136 for (int k = 0; k < getNumberRays(); k++) { 1137 TrackSegment ts = getRayConnectOrdered(k); 1138 if (ts != null) { 1139 String blk = ts.getBlockName(); 1140 if ((!blk.isEmpty()) && (blk.equals(blockName))) { // (#1) 1141 // if we are added to the TrackNameSet 1142 if (TrackNameSet.add(getName())) { 1143 log.debug("* Add track ''{}'' for block ''{}''", getName(), blockName); 1144 } 1145 // it's time to play... flood your neighbours! 1146 ts.collectContiguousTracksNamesInBlockNamed(blockName, 1147 TrackNameSet); // (#4) 1148 } 1149 } 1150 } 1151 } 1152 } 1153 1154 /** 1155 * {@inheritDoc} 1156 */ 1157 @Override 1158 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 1159 // turntables don't have blocks... 1160 // nothing to see here, move along... 1161 } 1162 1163 /** 1164 * {@inheritDoc} 1165 */ 1166 @Override 1167 public boolean canRemove() { 1168 return true; 1169 } 1170 1171 1172 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurntableView.class); 1173}