001package jmri.jmrit.display.layoutEditor; 002 003import java.awt.Color; 004import java.awt.Font; 005import java.awt.Graphics2D; 006import java.awt.event.ActionEvent; 007import java.awt.geom.*; 008import static java.lang.Float.POSITIVE_INFINITY; 009import java.text.ParseException; 010import java.util.*; 011 012import javax.annotation.CheckForNull; 013import javax.annotation.Nonnull; 014import javax.swing.*; 015 016import jmri.*; 017import jmri.jmrit.display.layoutEditor.LayoutTurnout.Geometry; 018import jmri.jmrit.display.layoutEditor.LayoutTurnout.LinkType; 019import jmri.jmrit.display.layoutEditor.LayoutTurnout.TurnoutType; 020import jmri.jmrit.display.layoutEditor.blockRoutingTable.LayoutBlockRouteTableAction; 021import jmri.util.IntlUtilities; 022import jmri.util.MathUtil; 023import jmri.util.swing.JmriJOptionPane; 024import jmri.util.swing.JmriMouseEvent; 025 026/** 027 * MVC View component for the LayoutTurnout class. 028 * 029 * @author Bob Jacobsen Copyright (c) 2020 030 * 031 */ 032public class LayoutTurnoutView extends LayoutTrackView { 033 034 public LayoutTurnoutView(@Nonnull LayoutTurnout turnout, 035 @Nonnull Point2D c, double rot, 036 @Nonnull LayoutEditor layoutEditor) { 037 this(turnout, c, rot, 1.0, 1.0, layoutEditor); 038 } 039 040 /** 041 * Constructor method. 042 * 043 * @param turnout the layout turnout to create the view for. 044 * @param c where to put it 045 * @param rot for display 046 * @param xFactor for display 047 * @param yFactor for display 048 * @param layoutEditor what layout editor panel to put it in 049 */ 050 public LayoutTurnoutView(@Nonnull LayoutTurnout turnout, 051 @Nonnull Point2D c, double rot, 052 double xFactor, double yFactor, 053 @Nonnull LayoutEditor layoutEditor) { 054 super(turnout, c, layoutEditor); 055 this.turnout = turnout; 056 057 setIdent(turnout.getName()); 058 059 int version = turnout.getVersion(); 060 061 // adjust initial coordinates 062 if (turnout.getTurnoutType() == TurnoutType.LH_TURNOUT) { 063 dispB = new Point2D.Double(layoutEditor.getTurnoutBX(), 0.0); 064 dispA = new Point2D.Double(layoutEditor.getTurnoutCX(), -layoutEditor.getTurnoutWid()); 065 } else if (turnout.getTurnoutType() == TurnoutType.RH_TURNOUT) { 066 dispB = new Point2D.Double(layoutEditor.getTurnoutBX(), 0.0); 067 dispA = new Point2D.Double(layoutEditor.getTurnoutCX(), layoutEditor.getTurnoutWid()); 068 } else if (turnout.getTurnoutType() == TurnoutType.WYE_TURNOUT) { 069 dispB = new Point2D.Double(layoutEditor.getTurnoutBX(), 0.5 * layoutEditor.getTurnoutWid()); 070 dispA = new Point2D.Double(layoutEditor.getTurnoutBX(), -0.5 * layoutEditor.getTurnoutWid()); 071 } else if (turnout.getTurnoutType() == TurnoutType.DOUBLE_XOVER) { 072 if (version == 2) { 073 super.setCoordsCenter(new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid())); 074 pointB = new Point2D.Double(layoutEditor.getXOverLong() * 2, 0); 075 pointC = new Point2D.Double(layoutEditor.getXOverLong() * 2, (layoutEditor.getXOverHWid() * 2)); 076 pointD = new Point2D.Double(0, (layoutEditor.getXOverHWid() * 2)); 077 super.setCoordsCenter(c); 078 } else { 079 dispB = new Point2D.Double(layoutEditor.getXOverLong(), -layoutEditor.getXOverHWid()); 080 dispA = new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid()); 081 } 082 } else if (turnout.getTurnoutType() == TurnoutType.RH_XOVER) { 083 if (version == 2) { 084 super.setCoordsCenter(new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid())); 085 pointB = new Point2D.Double((layoutEditor.getXOverShort() + layoutEditor.getXOverLong()), 0); 086 pointC = new Point2D.Double(layoutEditor.getXOverLong() * 2, (layoutEditor.getXOverHWid() * 2)); 087 pointD = new Point2D.Double((getCoordsCenter().getX() - layoutEditor.getXOverShort()), (layoutEditor.getXOverHWid() * 2)); 088 super.setCoordsCenter(c); 089 } else { 090 dispB = new Point2D.Double(layoutEditor.getXOverShort(), -layoutEditor.getXOverHWid()); 091 dispA = new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid()); 092 } 093 } else if (turnout.getTurnoutType() == TurnoutType.LH_XOVER) { 094 if (version == 2) { 095 super.setCoordsCenter(new Point2D.Double(layoutEditor.getXOverLong(), layoutEditor.getXOverHWid())); 096 097 pointA = new Point2D.Double((getCoordsCenter().getX() - layoutEditor.getXOverShort()), 0); 098 pointB = new Point2D.Double((layoutEditor.getXOverLong() * 2), 0); 099 pointC = new Point2D.Double(layoutEditor.getXOverLong() + layoutEditor.getXOverShort(), (layoutEditor.getXOverHWid() * 2)); 100 pointD = new Point2D.Double(0, (layoutEditor.getXOverHWid() * 2)); 101 102 super.setCoordsCenter(c); 103 } else { 104 dispB = new Point2D.Double(layoutEditor.getXOverLong(), -layoutEditor.getXOverHWid()); 105 dispA = new Point2D.Double(layoutEditor.getXOverShort(), layoutEditor.getXOverHWid()); 106 } 107 } 108 109 rotateCoords(rot); 110 111 // adjust size of new turnout 112 Point2D pt = new Point2D.Double(Math.round(dispB.getX() * xFactor), 113 Math.round(dispB.getY() * yFactor)); 114 dispB = pt; 115 pt = new Point2D.Double(Math.round(dispA.getX() * xFactor), 116 Math.round(dispA.getY() * yFactor)); 117 dispA = pt; 118 119 editor = new jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTurnoutEditor(layoutEditor); 120 } 121 122 /** 123 * Returns true if this is a turnout (not a crossover or slip) 124 * 125 * @param type the turnout type 126 * @return boolean true if this is a turnout 127 */ 128 public static boolean isTurnoutTypeTurnout(TurnoutType type) { 129 return LayoutTurnout.isTurnoutTypeTurnout(type); 130 } 131 132 /** 133 * Returns true if this is a turnout (not a crossover or slip) 134 * 135 * @return boolean true if this is a turnout 136 */ 137 public boolean isTurnoutTypeTurnout() { 138 return turnout.isTurnoutTypeTurnout(); 139 } 140 141 /** 142 * Returns true if this is a crossover 143 * 144 * @param type the turnout type 145 * @return boolean true if this is a crossover 146 */ 147 public static boolean isTurnoutTypeXover(TurnoutType type) { 148 return LayoutTurnout.isTurnoutTypeXover(type); 149 } 150 151 /** 152 * Returns true if this is a crossover 153 * 154 * @return boolean true if this is a crossover 155 */ 156 public boolean isTurnoutTypeXover() { 157 return turnout.isTurnoutTypeXover(); 158 } 159 160 /** 161 * Returns true if this is a slip 162 * 163 * @param type the turnout type 164 * @return boolean true if this is a slip 165 */ 166 public static boolean isTurnoutTypeSlip(TurnoutType type) { 167 return LayoutTurnout.isTurnoutTypeSlip(type); 168 } 169 170 /** 171 * Returns true if this is a slip 172 * 173 * @return boolean true if this is a slip 174 */ 175 public boolean isTurnoutTypeSlip() { 176 return turnout.isTurnoutTypeSlip(); 177 } 178 179 /** 180 * Returns true if this has a single-track entrance end. (turnout or wye) 181 * 182 * @param type the turnout type 183 * @return boolean true if single track entrance 184 */ 185 public static boolean hasEnteringSingleTrack(TurnoutType type) { 186 return LayoutTurnout.hasEnteringSingleTrack(type); 187 } 188 189 /** 190 * Returns true if this has a single-track entrance end. (turnout or wye) 191 * 192 * @return boolean true if single track entrance 193 */ 194 public boolean hasEnteringSingleTrack() { 195 return LayoutTurnout.hasEnteringSingleTrack(getTurnoutType()); 196 } 197 198 /** 199 * Returns true if this has double track on the entrance end (crossover or 200 * slip) 201 * 202 * @param type the turnout type 203 * @return boolean true if double track entrance 204 */ 205 public static boolean hasEnteringDoubleTrack(TurnoutType type) { 206 return LayoutTurnout.hasEnteringDoubleTrack(type); 207 } 208 209 /** 210 * Returns true if this has double track on the entrance end (crossover or 211 * slip) 212 * 213 * @return boolean true if double track entrance 214 */ 215 public boolean hasEnteringDoubleTrack() { 216 return turnout.hasEnteringDoubleTrack(); 217 } 218 219 // operational instance variables (not saved between sessions) 220 public static final int UNKNOWN = Turnout.UNKNOWN; 221 public static final int INCONSISTENT = Turnout.INCONSISTENT; 222 public static final int STATE_AC = 0x02; 223 public static final int STATE_BD = 0x04; 224 public static final int STATE_AD = 0x06; 225 public static final int STATE_BC = 0x08; 226 227 // program default turnout size parameters 228 public static final double turnoutBXDefault = 20.0; // RH, LH, WYE 229 public static final double turnoutCXDefault = 20.0; 230 public static final double turnoutWidDefault = 10.0; 231 public static final double xOverLongDefault = 30.0; // DOUBLE_XOVER, RH_XOVER, LH_XOVER 232 public static final double xOverHWidDefault = 10.0; 233 public static final double xOverShortDefault = 10.0; 234 235 // operational instance variables (not saved between sessions) 236 protected NamedBeanHandle<Turnout> namedTurnout = null; 237 // Second turnout is used to either throw a second turnout in a cross over or if one turnout address is used to throw two physical ones 238 protected NamedBeanHandle<Turnout> secondNamedTurnout = null; 239 240 // default is package protected 241 protected NamedBeanHandle<LayoutBlock> namedLayoutBlockA = null; 242 protected NamedBeanHandle<LayoutBlock> namedLayoutBlockB = null; // Xover - second block, if there is one 243 protected NamedBeanHandle<LayoutBlock> namedLayoutBlockC = null; // Xover - third block, if there is one 244 protected NamedBeanHandle<LayoutBlock> namedLayoutBlockD = null; // Xover - forth block, if there is one 245 246 protected NamedBeanHandle<SignalHead> signalA1HeadNamed = null; // signal 1 (continuing) (throat for RH, LH, WYE) 247 protected NamedBeanHandle<SignalHead> signalA2HeadNamed = null; // signal 2 (diverging) (throat for RH, LH, WYE) 248 protected NamedBeanHandle<SignalHead> signalA3HeadNamed = null; // signal 3 (second diverging) (3-way turnouts only) 249 protected NamedBeanHandle<SignalHead> signalB1HeadNamed = null; // continuing (RH, LH, WYE) signal 1 (double crossover) 250 protected NamedBeanHandle<SignalHead> signalB2HeadNamed = null; // LH_Xover and double crossover only 251 protected NamedBeanHandle<SignalHead> signalC1HeadNamed = null; // diverging (RH, LH, WYE) signal 1 (double crossover) 252 protected NamedBeanHandle<SignalHead> signalC2HeadNamed = null; // RH_Xover and double crossover only 253 protected NamedBeanHandle<SignalHead> signalD1HeadNamed = null; // single or double crossover only 254 protected NamedBeanHandle<SignalHead> signalD2HeadNamed = null; // LH_Xover and double crossover only 255 256 public Point2D dispB = new Point2D.Double(20.0, 0.0); 257 public Point2D dispA = new Point2D.Double(20.0, 10.0); 258 public Point2D pointA = new Point2D.Double(0, 0); 259 public Point2D pointB = new Point2D.Double(40, 0); 260 public Point2D pointC = new Point2D.Double(60, 20); 261 public Point2D pointD = new Point2D.Double(20, 20); 262 263 public boolean showUnknown = false; // if true, show "?" when state is UNKNOWN 264 265 private int version = 1; 266 267 private final boolean useBlockSpeed = false; 268 269 // temporary reference to the Editor that will eventually be part of View 270 protected jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTurnoutEditor editor; 271 272 final private LayoutTurnout turnout; 273 274 public final LayoutTurnout getLayoutTurnout() { 275 return turnout; 276 } // getTurnout() gets the real Turnout in the LayoutTurnout 277 278 /** 279 * {@inheritDoc} 280 */ 281 // this should only be used for debugging... 282 @Override 283 @Nonnull 284 public String toString() { 285 return "LayoutTurnout " + getName(); 286 } 287 288 // 289 // Accessor methods 290 // 291 public int getVersion() { 292 return version; 293 } 294 295 public void setVersion(int v) { 296 version = v; 297 } 298 299 public boolean useBlockSpeed() { 300 return useBlockSpeed; 301 } 302 303 // @CheckForNull - can this be null? or ""? 304 public String getTurnoutName() { 305 return turnout.getTurnoutName(); 306 } 307 308 // @CheckForNull - can this be null? or ""? 309 public String getSecondTurnoutName() { 310 return turnout.getSecondTurnoutName(); 311 } 312 313 @Nonnull 314 public String getBlockName() { 315 return turnout.getBlockName(); 316 } 317 318 @Nonnull 319 public String getBlockBName() { 320 return turnout.getBlockBName(); 321 } 322 323 @Nonnull 324 public String getBlockCName() { 325 return turnout.getBlockCName(); 326 } 327 328 @Nonnull 329 public String getBlockDName() { 330 return turnout.getBlockDName(); 331 } 332 333 @CheckForNull 334 public SignalHead getSignalHead(Geometry loc) { 335 return turnout.getSignalHead(loc); 336 } 337 338 @CheckForNull 339 public SignalHead getSignalA1() { 340 return turnout.getSignalA1(); 341 } 342 343 @Nonnull 344 public String getSignalA1Name() { 345 return turnout.getSignalA1Name(); 346 } 347 348 public void setSignalA1Name(@CheckForNull String signalHead) { 349 turnout.setSignalA1Name(signalHead); 350 } 351 352 @CheckForNull 353 public SignalHead getSignalA2() { 354 return turnout.getSignalA2(); 355 } 356 357 @Nonnull 358 public String getSignalA2Name() { 359 return turnout.getSignalA2Name(); 360 } 361 362 public void setSignalA2Name(@CheckForNull String signalHead) { 363 turnout.setSignalA2Name(signalHead); 364 } 365 366 @CheckForNull 367 public SignalHead getSignalA3() { 368 return turnout.getSignalA3(); 369 } 370 371 @Nonnull 372 public String getSignalA3Name() { 373 return turnout.getSignalA3Name(); 374 } 375 376 public void setSignalA3Name(@CheckForNull String signalHead) { 377 turnout.setSignalA3Name(signalHead); 378 } 379 380 @CheckForNull 381 public SignalHead getSignalB1() { 382 return turnout.getSignalB1(); 383 } 384 385 @Nonnull 386 public String getSignalB1Name() { 387 return turnout.getSignalB1Name(); 388 } 389 390 public void setSignalB1Name(@CheckForNull String signalHead) { 391 turnout.setSignalB1Name(signalHead); 392 } 393 394 @CheckForNull 395 public SignalHead getSignalB2() { 396 return turnout.getSignalB2(); 397 } 398 399 @Nonnull 400 public String getSignalB2Name() { 401 return turnout.getSignalB2Name(); 402 } 403 404 public void setSignalB2Name(@CheckForNull String signalHead) { 405 turnout.setSignalB2Name(signalHead); 406 } 407 408 @CheckForNull 409 public SignalHead getSignalC1() { 410 return turnout.getSignalC1(); 411 } 412 413 @Nonnull 414 public String getSignalC1Name() { 415 return turnout.getSignalC1Name(); 416 } 417 418 public void setSignalC1Name(@CheckForNull String signalHead) { 419 turnout.setSignalC1Name(signalHead); 420 } 421 422 @CheckForNull 423 public SignalHead getSignalC2() { 424 return turnout.getSignalC2(); 425 } 426 427 @Nonnull 428 public String getSignalC2Name() { 429 return turnout.getSignalC2Name(); 430 } 431 432 public void setSignalC2Name(@CheckForNull String signalHead) { 433 turnout.setSignalC2Name(signalHead); 434 } 435 436 @CheckForNull 437 public SignalHead getSignalD1() { 438 return turnout.getSignalD1(); 439 } 440 441 @Nonnull 442 public String getSignalD1Name() { 443 return turnout.getSignalD1Name(); 444 } 445 446 public void setSignalD1Name(@CheckForNull String signalHead) { 447 turnout.setSignalD1Name(signalHead); 448 } 449 450 @CheckForNull 451 public SignalHead getSignalD2() { 452 return turnout.getSignalD2(); 453 } 454 455 @Nonnull 456 public String getSignalD2Name() { 457 return turnout.getSignalD2Name(); 458 } 459 460 public void setSignalD2Name(@CheckForNull String signalHead) { 461 turnout.setSignalD2Name(signalHead); 462 } 463 464 public void removeBeanReference(@CheckForNull jmri.NamedBean nb) { 465 turnout.removeBeanReference(nb); 466 } 467 468 public void setShowUnknown(boolean show) { 469 showUnknown = show; 470 } 471 472 public boolean getShowUnknown() { 473 return showUnknown; 474 } 475 476 /** 477 * {@inheritDoc} 478 */ 479 @Override 480 public boolean canRemove() { 481 return turnout.canRemove(); 482 } 483 484 /** 485 * Build a list of sensors, signal heads, and signal masts attached to a 486 * turnout point. 487 * 488 * @param pointName Specify the point (A-D) or all (All) points. 489 * @return a list of bean reference names. 490 */ 491 @Nonnull 492 public ArrayList<String> getBeanReferences(String pointName) { 493 throw new IllegalArgumentException("should be called on LayoutTurnout"); 494 } 495 496 @Nonnull 497 public String getSignalAMastName() { 498 return turnout.getSignalAMastName(); 499 } 500 501 @CheckForNull 502 public SignalMast getSignalAMast() { 503 return turnout.getSignalAMast(); 504 } 505 506 public void setSignalAMast(@CheckForNull String signalMast) { 507 turnout.setSignalAMast(signalMast); 508 } 509 510 @Nonnull 511 public String getSignalBMastName() { 512 return turnout.getSignalBMastName(); 513 } 514 515 @CheckForNull 516 public SignalMast getSignalBMast() { 517 return turnout.getSignalBMast(); 518 } 519 520 public void setSignalBMast(@CheckForNull String signalMast) { 521 turnout.setSignalBMast(signalMast); 522 } 523 524 @Nonnull 525 public String getSignalCMastName() { 526 return turnout.getSignalCMastName(); 527 } 528 529 @CheckForNull 530 public SignalMast getSignalCMast() { 531 return turnout.getSignalCMast(); 532 } 533 534 public void setSignalCMast(@CheckForNull String signalMast) { 535 turnout.setSignalCMast(signalMast); 536 } 537 538 @Nonnull 539 public String getSignalDMastName() { 540 return turnout.getSignalDMastName(); 541 } 542 543 @CheckForNull 544 public SignalMast getSignalDMast() { 545 return turnout.getSignalDMast(); 546 } 547 548 public void setSignalDMast(@CheckForNull String signalMast) { 549 turnout.setSignalDMast(signalMast); 550 } 551 552 @Nonnull 553 public String getSensorAName() { 554 return turnout.getSensorAName(); 555 } 556 557 @CheckForNull 558 public Sensor getSensorA() { 559 return turnout.getSensorA(); 560 } 561 562 public void setSensorA(@CheckForNull String sensorName) { 563 turnout.setSensorA(sensorName); 564 } 565 566 @Nonnull 567 public String getSensorBName() { 568 return turnout.getSensorBName(); 569 } 570 571 @CheckForNull 572 public Sensor getSensorB() { 573 return turnout.getSensorB(); 574 } 575 576 public void setSensorB(@CheckForNull String sensorName) { 577 turnout.setSensorB(sensorName); 578 } 579 580 @Nonnull 581 public String getSensorCName() { 582 return turnout.getSensorCName(); 583 } 584 585 @CheckForNull 586 public Sensor getSensorC() { 587 return turnout.getSensorC(); 588 } 589 590 public void setSensorC(@CheckForNull String sensorName) { 591 turnout.setSensorC(sensorName); 592 } 593 594 @Nonnull 595 public String getSensorDName() { 596 return turnout.getSensorDName(); 597 } 598 599 @CheckForNull 600 public Sensor getSensorD() { 601 return turnout.getSensorD(); 602 } 603 604 public void setSensorD(@CheckForNull String sensorName) { 605 turnout.setSensorD(sensorName); 606 } 607 608 public String getLinkedTurnoutName() { 609 return turnout.getLinkedTurnoutName(); 610 } 611 612 public void setLinkedTurnoutName(@Nonnull String s) { 613 turnout.setSensorD(s); 614 } // Could be done with changing over to a NamedBeanHandle 615 616 public LinkType getLinkType() { 617 return turnout.getLinkType(); 618 } 619 620 public void setLinkType(LinkType ltype) { 621 turnout.setLinkType(ltype); 622 } 623 624 public TurnoutType getTurnoutType() { 625 return turnout.getTurnoutType(); 626 } 627 628 public LayoutTrack getConnectA() { 629 return turnout.getConnectA(); 630 } 631 632 public LayoutTrack getConnectB() { 633 return turnout.getConnectB(); 634 } 635 636 public LayoutTrack getConnectC() { 637 return turnout.getConnectC(); 638 } 639 640 public LayoutTrack getConnectD() { 641 return turnout.getConnectD(); 642 } 643 644 /** 645 * @return null if no turnout set // temporary? Might want to run all calls 646 * through this class; but this is getModel equiv 647 */ 648 // @CheckForNull temporary 649 public Turnout getTurnout() { 650 return turnout.getTurnout(); 651 } 652 653 public int getContinuingSense() { 654 return turnout.getContinuingSense(); 655 } 656 657 /** 658 * 659 * @return true is the continuingSense matches the known state 660 */ 661 public boolean isInContinuingSenseState() { 662 return turnout.isInContinuingSenseState(); 663 } 664 665 public void setTurnout(@Nonnull String tName) { 666 turnout.setTurnout(tName); 667 } 668 669 // @CheckForNull - need to have a better way to handle null case 670 public Turnout getSecondTurnout() { 671 return turnout.getSecondTurnout(); 672 } 673 674 public void setSecondTurnout(@Nonnull String tName) { 675 turnout.setSecondTurnout(tName); 676 } 677 678 public void setSecondTurnoutInverted(boolean inverted) { 679 turnout.setSecondTurnoutInverted(inverted); 680 } 681 682 public void setContinuingSense(int sense) { 683 turnout.setContinuingSense(sense); 684 } 685 686 public void setDisabled(boolean state) { 687 turnout.setDisabled(state); 688 if (layoutEditor != null) { 689 layoutEditor.redrawPanel(); 690 } 691 } 692 693 public boolean isDisabled() { 694 return turnout.isDisabled(); 695 } 696 697 public void setDisableWhenOccupied(boolean state) { 698 turnout.setDisableWhenOccupied(state); 699 if (layoutEditor != null) { 700 layoutEditor.redrawPanel(); 701 } 702 } 703 704 public boolean isDisabledWhenOccupied() { 705 return turnout.isDisabledWhenOccupied(); 706 } 707 708 /** 709 * {@inheritDoc} 710 */ 711 @Override 712 @CheckForNull 713 public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException { 714 return turnout.getConnection(connectionType); 715 } 716 717 /** 718 * {@inheritDoc} 719 */ 720 @Override 721 public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException { 722 turnout.setConnection(connectionType, o, type); 723 } 724 725 public void setConnectA(@CheckForNull LayoutTrack o, HitPointType type) { 726 turnout.setConnectA(o, type); 727 } 728 729 public void setConnectB(@CheckForNull LayoutTrack o, HitPointType type) { 730 turnout.setConnectB(o, type); 731 } 732 733 public void setConnectC(@CheckForNull LayoutTrack o, HitPointType type) { 734 turnout.setConnectC(o, type); 735 } 736 737 public void setConnectD(@CheckForNull LayoutTrack o, HitPointType type) { 738 turnout.setConnectD(o, type); 739 } 740 741 // @CheckForNull - temporary while we work on centralized protection 742 public LayoutBlock getLayoutBlock() { 743 return turnout.getLayoutBlock(); 744 } 745 746 // @CheckForNull - temporary while we work on centralized protection 747 public LayoutBlock getLayoutBlockB() { 748 return turnout.getLayoutBlockB(); 749 } 750 751 // @CheckForNull - temporary while we work on centralized protection 752 public LayoutBlock getLayoutBlockC() { 753 return turnout.getLayoutBlockC(); 754 } 755 756 // @CheckForNull - temporary while we work on centralized protection 757 public LayoutBlock getLayoutBlockD() { 758 return turnout.getLayoutBlockD(); 759 } 760 761 @Nonnull 762 public Point2D getCoordsA() { 763 if (isTurnoutTypeXover()) { 764 if (version == 2) { 765 return pointA; 766 } 767 return MathUtil.subtract(getCoordsCenter(), dispA); 768 } else if (getTurnoutType() == TurnoutType.WYE_TURNOUT) { 769 return MathUtil.subtract(getCoordsCenter(), MathUtil.midPoint(dispB, dispA)); 770 } else { 771 return MathUtil.subtract(getCoordsCenter(), dispB); 772 } 773 } 774 775 @Nonnull 776 public Point2D getCoordsB() { 777 if ((version == 2) && isTurnoutTypeXover()) { 778 return pointB; 779 } 780 return MathUtil.add(getCoordsCenter(), dispB); 781 } 782 783 @Nonnull 784 public Point2D getCoordsC() { 785 if ((version == 2) && isTurnoutTypeXover()) { 786 return pointC; 787 } 788 return MathUtil.add(getCoordsCenter(), dispA); 789 } 790 791 @Nonnull 792 public Point2D getCoordsD() { 793 if ((version == 2) && isTurnoutTypeXover()) { 794 return pointD; 795 } 796 // only allowed for single and double crossovers 797 return MathUtil.subtract(getCoordsCenter(), dispB); 798 } 799 800 /** 801 * {@inheritDoc} 802 */ 803 @Override 804 @Nonnull 805 public Point2D getCoordsForConnectionType(HitPointType connectionType) { 806 Point2D result = getCoordsCenter(); 807 switch (connectionType) { 808 case TURNOUT_CENTER: 809 break; 810 case TURNOUT_A: 811 result = getCoordsA(); 812 break; 813 case TURNOUT_B: 814 result = getCoordsB(); 815 break; 816 case TURNOUT_C: 817 result = getCoordsC(); 818 break; 819 case TURNOUT_D: 820 result = getCoordsD(); 821 break; 822 default: 823 log.error("{}.getCoordsForConnectionType({}); Invalid Connection Type", 824 getName(), connectionType); // NOI18N 825 } 826 return result; 827 } 828 829 /** 830 * {@inheritDoc} 831 */ 832 @Override 833 @Nonnull 834 public Rectangle2D getBounds() { 835 Rectangle2D result; 836 837 Point2D pointA = getCoordsA(); 838 result = new Rectangle2D.Double(pointA.getX(), pointA.getY(), 0, 0); 839 result.add(getCoordsB()); 840 result.add(getCoordsC()); 841 if (isTurnoutTypeXover() || isTurnoutTypeSlip()) { 842 result.add(getCoordsD()); 843 } 844 return result; 845 } 846 847 // updates connectivity for blocks assigned to this turnout and connected track segments 848 public void updateBlockInfo() { 849 turnout.updateBlockInfo(); 850 } 851 852 /** 853 * Set default size parameters to correspond to this turnout's size. 854 * <p> 855 * note: only protected so LayoutTurnoutTest can call it 856 */ 857 protected void setUpDefaultSize() { 858 // remove the overall scale factor 859 double bX = dispB.getX() / layoutEditor.gContext.getXScale(); 860 double bY = dispB.getY() / layoutEditor.gContext.getYScale(); 861 double cX = dispA.getX() / layoutEditor.gContext.getXScale(); 862 double cY = dispA.getY() / layoutEditor.gContext.getYScale(); 863 // calculate default parameters according to type of turnout 864 double lenB = Math.hypot(bX, bY); 865 double lenC = Math.hypot(cX, cY); 866 double distBC = Math.hypot(bX - cX, bY - cY); 867 if ((getTurnoutType() == TurnoutType.LH_TURNOUT) 868 || (getTurnoutType() == TurnoutType.RH_TURNOUT)) { 869 870 layoutEditor.setTurnoutBX(Math.round(lenB + 0.1)); 871 double xc = ((bX * cX) + (bY * cY)) / lenB; 872 layoutEditor.setTurnoutCX(Math.round(xc + 0.1)); 873 layoutEditor.setTurnoutWid(Math.round(Math.sqrt((lenC * lenC) - (xc * xc)) + 0.1)); 874 } else if (getTurnoutType() == TurnoutType.WYE_TURNOUT) { 875 double xx = Math.sqrt((lenB * lenB) - (0.25 * (distBC * distBC))); 876 layoutEditor.setTurnoutBX(Math.round(xx + 0.1)); 877 layoutEditor.setTurnoutCX(Math.round(xx + 0.1)); 878 layoutEditor.setTurnoutWid(Math.round(distBC + 0.1)); 879 } else { 880 if (version == 2) { 881 double aX = pointA.getX() / layoutEditor.gContext.getXScale(); 882 double aY = pointA.getY() / layoutEditor.gContext.getYScale(); 883 bX = pointB.getX() / layoutEditor.gContext.getXScale(); 884 bY = pointB.getY() / layoutEditor.gContext.getYScale(); 885 cX = pointC.getX() / layoutEditor.gContext.getXScale(); 886 cY = pointC.getY() / layoutEditor.gContext.getYScale(); 887 double lenAB = Math.hypot(bX - aX, bY - aY); 888 if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) { 889 double lenBC = Math.hypot(bX - cX, bY - cY); 890 layoutEditor.setXOverLong(Math.round(lenAB / 2)); // set to half to be backwardly compatible 891 layoutEditor.setXOverHWid(Math.round(lenBC / 2)); 892 layoutEditor.setXOverShort(Math.round((0.5 * lenAB) / 2)); 893 } else if (getTurnoutType() == TurnoutType.RH_XOVER) { 894 lenAB = lenAB / 3; 895 layoutEditor.setXOverShort(Math.round(lenAB)); 896 layoutEditor.setXOverLong(Math.round(lenAB * 2)); 897 double opp = (aY - bY); 898 double ang = Math.asin(opp / (lenAB * 3)); 899 opp = Math.sin(ang) * lenAB; 900 bY = bY + opp; 901 double adj = Math.cos(ang) * lenAB; 902 bX = bX + adj; 903 double lenBC = Math.hypot(bX - cX, bY - cY); 904 layoutEditor.setXOverHWid(Math.round(lenBC / 2)); 905 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 906 double dY = pointD.getY() / layoutEditor.gContext.getYScale(); 907 lenAB = lenAB / 3; 908 layoutEditor.setXOverShort(Math.round(lenAB)); 909 layoutEditor.setXOverLong(Math.round(lenAB * 2)); 910 double opp = (dY - cY); 911 double ang = Math.asin(opp / (lenAB * 3)); // Length of AB should be the same as CD 912 opp = Math.sin(ang) * lenAB; 913 cY = cY + opp; 914 double adj = Math.cos(ang) * lenAB; 915 cX = cX + adj; 916 double lenBC = Math.hypot(bX - cX, bY - cY); 917 layoutEditor.setXOverHWid(Math.round(lenBC / 2)); 918 } 919 } else if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) { 920 double lng = Math.sqrt((lenB * lenB) - (0.25 * (distBC * distBC))); 921 layoutEditor.setXOverLong(Math.round(lng + 0.1)); 922 layoutEditor.setXOverHWid(Math.round((0.5 * distBC) + 0.1)); 923 layoutEditor.setXOverShort(Math.round((0.5 * lng) + 0.1)); 924 } else if (getTurnoutType() == TurnoutType.RH_XOVER) { 925 double distDC = Math.hypot(bX + cX, bY + cY); 926 layoutEditor.setXOverShort(Math.round((0.25 * distDC) + 0.1)); 927 layoutEditor.setXOverLong(Math.round((0.75 * distDC) + 0.1)); 928 double hwid = Math.sqrt((lenC * lenC) - (0.5625 * distDC * distDC)); 929 layoutEditor.setXOverHWid(Math.round(hwid + 0.1)); 930 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 931 double distDC = Math.hypot(bX + cX, bY + cY); 932 layoutEditor.setXOverShort(Math.round((0.25 * distDC) + 0.1)); 933 layoutEditor.setXOverLong(Math.round((0.75 * distDC) + 0.1)); 934 double hwid = Math.sqrt((lenC * lenC) - (0.0625 * distDC * distDC)); 935 layoutEditor.setXOverHWid(Math.round(hwid + 0.1)); 936 } 937 } 938 } 939 940 /** 941 * Set up Layout Block(s) for this Turnout. 942 * 943 * @param newLayoutBlock See {@link LayoutTurnout#setLayoutBlock} for 944 * definition 945 */ 946 public void setLayoutBlock(LayoutBlock newLayoutBlock) { 947 turnout.setLayoutBlock(newLayoutBlock); 948 // correct any graphical artifacts 949 setTrackSegmentBlocks(); 950 } 951 952 public void setLayoutBlockB(LayoutBlock newLayoutBlock) { 953 turnout.setLayoutBlockB(newLayoutBlock); 954 // correct any graphical artifacts 955 setTrackSegmentBlocks(); 956 } 957 958 public void setLayoutBlockC(LayoutBlock newLayoutBlock) { 959 turnout.setLayoutBlockC(newLayoutBlock); 960 // correct any graphical artifacts 961 setTrackSegmentBlocks(); 962 } 963 964 public void setLayoutBlockD(LayoutBlock newLayoutBlock) { 965 turnout.setLayoutBlockD(newLayoutBlock); 966 // correct any graphical artifacts 967 setTrackSegmentBlocks(); 968 } 969 970 public void setLayoutBlockByName(@Nonnull String name) { 971 turnout.setLayoutBlockByName(name); 972 } 973 974 public void setLayoutBlockBByName(@Nonnull String name) { 975 turnout.setLayoutBlockByName(name); 976 } 977 978 public void setLayoutBlockCByName(@Nonnull String name) { 979 turnout.setLayoutBlockByName(name); 980 } 981 982 public void setLayoutBlockDByName(@Nonnull String name) { 983 turnout.setLayoutBlockByName(name); 984 } 985 986 /** 987 * Check each connection point and update the block value for very short 988 * track segments. 989 * 990 * @since 4.11.6 991 */ 992 void setTrackSegmentBlocks() { 993 setTrackSegmentBlock(HitPointType.TURNOUT_A, false); 994 setTrackSegmentBlock(HitPointType.TURNOUT_B, false); 995 setTrackSegmentBlock(HitPointType.TURNOUT_C, false); 996 if (hasEnteringDoubleTrack()) { 997 setTrackSegmentBlock(HitPointType.TURNOUT_D, false); 998 } 999 } 1000 1001 /** 1002 * Update the block for a track segment that provides a (graphically) short 1003 * connection between a turnout and another object, normally another 1004 * turnout. These are hard to see and are frequently missed. 1005 * <p> 1006 * Skip block changes if signal heads, masts or sensors have been assigned. 1007 * Only track segments with a length less than the turnout circle radius 1008 * will be changed. 1009 * 1010 * @since 4.11.6 1011 * @param pointType The point type which indicates which turnout 1012 * connection. 1013 * @param isAutomatic True for the automatically generated track segment 1014 * created by the drag-n-drop process. False for existing 1015 * connections which require a track segment length 1016 * calculation. 1017 */ 1018 void setTrackSegmentBlock(HitPointType pointType, boolean isAutomatic) { 1019 TrackSegment trkSeg; 1020 Point2D pointCoord; 1021 LayoutBlock blockA = getLayoutBlock(); 1022 LayoutBlock blockB = getLayoutBlock(); 1023 LayoutBlock blockC = getLayoutBlock(); 1024 LayoutBlock blockD = getLayoutBlock(); 1025 LayoutBlock currBlk = blockA; 1026 1027 switch (pointType) { 1028 case TURNOUT_A: 1029 case SLIP_A: 1030 if (signalA1HeadNamed != null) { 1031 return; 1032 } 1033 if (signalA2HeadNamed != null) { 1034 return; 1035 } 1036 if (signalA3HeadNamed != null) { 1037 return; 1038 } 1039 if (getSignalAMast() != null) { 1040 return; 1041 } 1042 if (getSensorA() != null) { 1043 return; 1044 } 1045 trkSeg = (TrackSegment) getConnectA(); 1046 pointCoord = getCoordsA(); 1047 break; 1048 case TURNOUT_B: 1049 case SLIP_B: 1050 if (signalB1HeadNamed != null) { 1051 return; 1052 } 1053 if (signalB2HeadNamed != null) { 1054 return; 1055 } 1056 if (getSignalBMast() != null) { 1057 return; 1058 } 1059 if (getSensorB() != null) { 1060 return; 1061 } 1062 trkSeg = (TrackSegment) getConnectB(); 1063 pointCoord = getCoordsB(); 1064 if (isTurnoutTypeXover()) { 1065 currBlk = blockB != null ? blockB : blockA; 1066 } 1067 break; 1068 case TURNOUT_C: 1069 case SLIP_C: 1070 if (signalC1HeadNamed != null) { 1071 return; 1072 } 1073 if (signalC2HeadNamed != null) { 1074 return; 1075 } 1076 if (getSignalCMast() != null) { 1077 return; 1078 } 1079 if (getSensorC() != null) { 1080 return; 1081 } 1082 trkSeg = (TrackSegment) getConnectC(); 1083 pointCoord = getCoordsC(); 1084 if (isTurnoutTypeXover()) { 1085 currBlk = blockC != null ? blockC : blockA; 1086 } 1087 break; 1088 case TURNOUT_D: 1089 case SLIP_D: 1090 if (signalD1HeadNamed != null) { 1091 return; 1092 } 1093 if (signalD2HeadNamed != null) { 1094 return; 1095 } 1096 if (getSignalDMast() != null) { 1097 return; 1098 } 1099 if (getSensorD() != null) { 1100 return; 1101 } 1102 trkSeg = (TrackSegment) getConnectD(); 1103 pointCoord = getCoordsD(); 1104 if (isTurnoutTypeXover()) { 1105 currBlk = blockD != null ? blockD : blockA; 1106 } 1107 break; 1108 default: 1109 log.error("{}.setTrackSegmentBlock({}, {}); Invalid pointType", 1110 getName(), pointType, isAutomatic ? "AUTO" : "NON-AUTO"); 1111 return; 1112 } 1113 if (trkSeg != null) { 1114 double chkSize = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize(); 1115 double segLength = 0; 1116 if (!isAutomatic) { 1117 Point2D segCenter = getCoordsCenter(); 1118 segLength = MathUtil.distance(pointCoord, segCenter) * 2; 1119 } 1120 if (segLength < chkSize) { 1121 1122 log.debug("Set block:"); 1123 log.debug(" seg: {}", trkSeg); 1124 log.debug(" cor: {}", pointCoord); 1125 log.debug(" blk: {}", (currBlk == null) ? "null" : currBlk.getDisplayName()); 1126 log.debug(" len: {}", segLength); 1127 1128 trkSeg.setLayoutBlock(currBlk); 1129 layoutEditor.getLEAuxTools().setBlockConnectivityChanged(); 1130 } 1131 } 1132 } 1133 1134 /** 1135 * Test if turnout legs are mainline track or not. 1136 * 1137 * @return true if connecting track segment is mainline; Defaults to not 1138 * mainline if connecting track segment is missing 1139 */ 1140 public boolean isMainlineA() { 1141 return turnout.isMainlineA(); 1142 } 1143 1144 public boolean isMainlineB() { 1145 return turnout.isMainlineB(); 1146 } 1147 1148 public boolean isMainlineC() { 1149 return turnout.isMainlineC(); 1150 } 1151 1152 public boolean isMainlineD() { 1153 return turnout.isMainlineD(); 1154 } 1155 1156 /** 1157 * {@inheritDoc} 1158 */ 1159 @Override 1160 protected HitPointType findHitPointType(@Nonnull Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) { 1161 HitPointType result = HitPointType.NONE; // assume point not on connection 1162 // note: optimization here: instead of creating rectangles for all the 1163 // points to check below, we create a rectangle for the test point 1164 // and test if the points below are in that rectangle instead. 1165 Rectangle2D r = layoutEditor.layoutEditorControlCircleRectAt(hitPoint); 1166 Point2D p, minPoint = MathUtil.zeroPoint2D; 1167 1168 double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize(); 1169 double distance, minDistance = POSITIVE_INFINITY; 1170 1171 // check center coordinates 1172 if (!requireUnconnected) { 1173 p = getCoordsCenter(); 1174 distance = MathUtil.distance(p, hitPoint); 1175 if (distance < minDistance) { 1176 minDistance = distance; 1177 minPoint = p; 1178 result = HitPointType.TURNOUT_CENTER; 1179 } 1180 } 1181 1182 // check the A connection point 1183 if (!requireUnconnected || (getConnectA() == null)) { 1184 p = getCoordsA(); 1185 distance = MathUtil.distance(p, hitPoint); 1186 if (distance < minDistance) { 1187 minDistance = distance; 1188 minPoint = p; 1189 result = HitPointType.TURNOUT_A; 1190 } 1191 } 1192 1193 // check the B connection point 1194 if (!requireUnconnected || (getConnectB() == null)) { 1195 p = getCoordsB(); 1196 distance = MathUtil.distance(p, hitPoint); 1197 if (distance < minDistance) { 1198 minDistance = distance; 1199 minPoint = p; 1200 result = HitPointType.TURNOUT_B; 1201 } 1202 } 1203 1204 // check the C connection point 1205 if (!requireUnconnected || (getConnectC() == null)) { 1206 p = getCoordsC(); 1207 distance = MathUtil.distance(p, hitPoint); 1208 if (distance < minDistance) { 1209 minDistance = distance; 1210 minPoint = p; 1211 result = HitPointType.TURNOUT_C; 1212 } 1213 } 1214 1215 // check the D connection point 1216 if (isTurnoutTypeXover()) { 1217 if (!requireUnconnected || (getConnectD() == null)) { 1218 p = getCoordsD(); 1219 distance = MathUtil.distance(p, hitPoint); 1220 if (distance < minDistance) { 1221 minDistance = distance; 1222 minPoint = p; 1223 result = HitPointType.TURNOUT_D; 1224 } 1225 } 1226 } 1227 if ((useRectangles && !r.contains(minPoint)) 1228 || (!useRectangles && (minDistance > circleRadius))) { 1229 result = HitPointType.NONE; 1230 } 1231 return result; 1232 } // findHitPointType 1233 1234 /* 1235 * Modify coordinates methods 1236 */ 1237 /** 1238 * {@inheritDoc} 1239 */ 1240 @Override 1241 public void setCoordsCenter(@Nonnull Point2D p) { 1242 Point2D offset = MathUtil.subtract(p, getCoordsCenter()); 1243 pointA = MathUtil.add(pointA, offset); 1244 pointB = MathUtil.add(pointB, offset); 1245 pointC = MathUtil.add(pointC, offset); 1246 pointD = MathUtil.add(pointD, offset); 1247 super.setCoordsCenter(p); 1248 } 1249 1250 // temporary should be private once LayoutTurnout no longer needs it 1251 void reCalculateCenter() { 1252 super.setCoordsCenter(MathUtil.midPoint(pointA, pointC)); 1253 } 1254 1255 public void setCoordsA(@Nonnull Point2D p) { 1256 pointA = p; 1257 if (version == 2) { 1258 reCalculateCenter(); 1259 } 1260 double x = getCoordsCenter().getX() - p.getX(); 1261 double y = getCoordsCenter().getY() - p.getY(); 1262 if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) { 1263 dispA = new Point2D.Double(x, y); 1264 // adjust to maintain rectangle 1265 double oldLength = MathUtil.length(dispB); 1266 double newLength = Math.hypot(x, y); 1267 dispB = MathUtil.multiply(dispB, newLength / oldLength); 1268 } else if ((getTurnoutType() == TurnoutType.RH_XOVER) 1269 || (getTurnoutType() == TurnoutType.LH_XOVER)) { 1270 dispA = new Point2D.Double(x, y); 1271 // adjust to maintain the parallelogram 1272 double b = -y; 1273 double xi = 0.0; 1274 double yi = b; 1275 if ((dispB.getX() + x) != 0.0) { 1276 double a = (dispB.getY() + y) / (dispB.getX() + x); 1277 b = -y + (a * x); 1278 xi = -b / (a + (1.0 / a)); 1279 yi = (a * xi) + b; 1280 } 1281 if (getTurnoutType() == TurnoutType.RH_XOVER) { 1282 x = xi - (0.333333 * (-x - xi)); 1283 y = yi - (0.333333 * (-y - yi)); 1284 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 1285 x = xi - (3.0 * (-x - xi)); 1286 y = yi - (3.0 * (-y - yi)); 1287 } 1288 dispB = new Point2D.Double(x, y); 1289 } else if (getTurnoutType() == TurnoutType.WYE_TURNOUT) { 1290 // modify both to maintain same angle at wye 1291 double temX = (dispB.getX() + dispA.getX()); 1292 double temY = (dispB.getY() + dispA.getY()); 1293 double temXx = (dispB.getX() - dispA.getX()); 1294 double temYy = (dispB.getY() - dispA.getY()); 1295 double tan = Math.sqrt(((temX * temX) + (temY * temY)) 1296 / ((temXx * temXx) + (temYy * temYy))); 1297 double xx = x + (y / tan); 1298 double yy = y - (x / tan); 1299 dispA = new Point2D.Double(xx, yy); 1300 xx = x - (y / tan); 1301 yy = y + (x / tan); 1302 dispB = new Point2D.Double(xx, yy); 1303 } else { 1304 dispB = new Point2D.Double(x, y); 1305 } 1306 } 1307 1308 public void setCoordsB(Point2D p) { 1309 pointB = p; 1310 double x = getCoordsCenter().getX() - p.getX(); 1311 double y = getCoordsCenter().getY() - p.getY(); 1312 dispB = new Point2D.Double(-x, -y); 1313 if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER) 1314 || (getTurnoutType() == TurnoutType.WYE_TURNOUT)) { 1315 // adjust to maintain rectangle or wye shape 1316 double oldLength = MathUtil.length(dispA); 1317 double newLength = Math.hypot(x, y); 1318 dispA = MathUtil.multiply(dispA, newLength / oldLength); 1319 } else if ((getTurnoutType() == TurnoutType.RH_XOVER) 1320 || (getTurnoutType() == TurnoutType.LH_XOVER)) { 1321 // adjust to maintain the parallelogram 1322 double b = y; 1323 double xi = 0.0; 1324 double yi = b; 1325 if ((dispA.getX() - x) != 0.0) { 1326 if ((-dispA.getX() + x) == 0) { 1327 /* we can in some situations eg 90' vertical end up with a 0 value, 1328 so hence remove a small amount so that we 1329 don't have a divide by zero issue */ 1330 x = x - 0.0000000001; 1331 } 1332 double a = (dispA.getY() - y) / (dispA.getX() - x); 1333 b = y - (a * x); 1334 xi = -b / (a + (1.0 / a)); 1335 yi = (a * xi) + b; 1336 } 1337 if (getTurnoutType() == TurnoutType.LH_XOVER) { 1338 x = xi - (0.333333 * (x - xi)); 1339 y = yi - (0.333333 * (y - yi)); 1340 } else if (getTurnoutType() == TurnoutType.RH_XOVER) { 1341 x = xi - (3.0 * (x - xi)); 1342 y = yi - (3.0 * (y - yi)); 1343 } 1344 dispA = new Point2D.Double(x, y); 1345 } 1346 } 1347 1348 public void setCoordsC(Point2D p) { 1349 pointC = p; 1350 if (version == 2) { 1351 reCalculateCenter(); 1352 } 1353 double x = getCoordsCenter().getX() - p.getX(); 1354 double y = getCoordsCenter().getY() - p.getY(); 1355 dispA = new Point2D.Double(-x, -y); 1356 if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER) 1357 || (getTurnoutType() == TurnoutType.WYE_TURNOUT)) { 1358 // adjust to maintain rectangle or wye shape 1359 double oldLength = MathUtil.length(dispB); 1360 double newLength = Math.hypot(x, y); 1361 dispB = MathUtil.multiply(dispB, newLength / oldLength); 1362 } else if ((getTurnoutType() == TurnoutType.RH_XOVER) 1363 || (getTurnoutType() == TurnoutType.LH_XOVER)) { 1364 double b = -y; 1365 double xi = 0.0; 1366 double yi = b; 1367 if ((dispB.getX() + x) != 0.0) { 1368 if ((-dispB.getX() + x) == 0) { 1369 /* we can in some situations eg 90' vertical end up with a 0 value, 1370 so hence remove a small amount so that we 1371 don't have a divide by zero issue */ 1372 1373 x = x - 0.0000000001; 1374 } 1375 double a = (-dispB.getY() + y) / (-dispB.getX() + x); 1376 b = -y + (a * x); 1377 xi = -b / (a + (1.0 / a)); 1378 yi = (a * xi) + b; 1379 } 1380 if (getTurnoutType() == TurnoutType.RH_XOVER) { 1381 x = xi - (0.333333 * (-x - xi)); 1382 y = yi - (0.333333 * (-y - yi)); 1383 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 1384 x = xi - (3.0 * (-x - xi)); 1385 y = yi - (3.0 * (-y - yi)); 1386 } 1387 dispB = new Point2D.Double(-x, -y); 1388 } 1389 } 1390 1391 public void setCoordsD(Point2D p) { 1392 pointD = p; 1393 1394 // only used for crossovers 1395 double x = getCoordsCenter().getX() - p.getX(); 1396 double y = getCoordsCenter().getY() - p.getY(); 1397 dispB = new Point2D.Double(x, y); 1398 if (getTurnoutType() == TurnoutType.DOUBLE_XOVER) { 1399 // adjust to maintain rectangle 1400 double oldLength = MathUtil.length(dispA); 1401 double newLength = Math.hypot(x, y); 1402 dispA = MathUtil.multiply(dispA, newLength / oldLength); 1403 } else if ((getTurnoutType() == TurnoutType.RH_XOVER) 1404 || (getTurnoutType() == TurnoutType.LH_XOVER)) { 1405 // adjust to maintain the parallelogram 1406 double b = y; 1407 double xi = 0.0; 1408 double yi = b; 1409 if ((dispA.getX() + x) != 0.0) { 1410 double a = (dispA.getY() + y) / (dispA.getX() + x); 1411 b = -y + (a * x); 1412 xi = -b / (a + (1.0 / a)); 1413 yi = (a * xi) + b; 1414 } 1415 if (getTurnoutType() == TurnoutType.LH_XOVER) { 1416 x = xi - (0.333333 * (-x - xi)); 1417 y = yi - (0.333333 * (-y - yi)); 1418 } else if (getTurnoutType() == TurnoutType.RH_XOVER) { 1419 x = xi - (3.0 * (-x - xi)); 1420 y = yi - (3.0 * (-y - yi)); 1421 } 1422 dispA = new Point2D.Double(x, y); 1423 } 1424 } 1425 1426 /** 1427 * {@inheritDoc} 1428 */ 1429 @Override 1430 public void scaleCoords(double xFactor, double yFactor) { 1431 Point2D factor = new Point2D.Double(xFactor, yFactor); 1432 super.setCoordsCenter(MathUtil.granulize(MathUtil.multiply(getCoordsCenter(), factor), 1.0)); 1433 1434 dispA = MathUtil.granulize(MathUtil.multiply(dispA, factor), 1.0); 1435 dispB = MathUtil.granulize(MathUtil.multiply(dispB, factor), 1.0); 1436 1437 pointA = MathUtil.granulize(MathUtil.multiply(pointA, factor), 1.0); 1438 pointB = MathUtil.granulize(MathUtil.multiply(pointB, factor), 1.0); 1439 pointC = MathUtil.granulize(MathUtil.multiply(pointC, factor), 1.0); 1440 pointD = MathUtil.granulize(MathUtil.multiply(pointD, factor), 1.0); 1441 } 1442 1443 /** 1444 * {@inheritDoc} 1445 */ 1446 @Override 1447 public void translateCoords(double xFactor, double yFactor) { 1448 Point2D factor = new Point2D.Double(xFactor, yFactor); 1449 super.setCoordsCenter(MathUtil.add(getCoordsCenter(), factor)); 1450 pointA = MathUtil.add(pointA, factor); 1451 pointB = MathUtil.add(pointB, factor); 1452 pointC = MathUtil.add(pointC, factor); 1453 pointD = MathUtil.add(pointD, factor); 1454 } 1455 1456 /** 1457 * {@inheritDoc} 1458 */ 1459 @Override 1460 public void rotateCoords(double angleDEG) { 1461 // rotate coordinates 1462 double rotRAD = Math.toRadians(angleDEG); 1463 double sineRot = Math.sin(rotRAD); 1464 double cosineRot = Math.cos(rotRAD); 1465 1466 // rotate displacements around origin {0, 0} 1467 Point2D center_temp = getCoordsCenter(); 1468 super.setCoordsCenter(MathUtil.zeroPoint2D); 1469 dispA = rotatePoint(dispA, sineRot, cosineRot); 1470 dispB = rotatePoint(dispB, sineRot, cosineRot); 1471 super.setCoordsCenter(center_temp); 1472 1473 pointA = rotatePoint(pointA, sineRot, cosineRot); 1474 pointB = rotatePoint(pointB, sineRot, cosineRot); 1475 pointC = rotatePoint(pointC, sineRot, cosineRot); 1476 pointD = rotatePoint(pointD, sineRot, cosineRot); 1477 } 1478 1479 public double getRotationDEG() { 1480 double result = 0; 1481 switch (getTurnoutType()) { 1482 case RH_TURNOUT: 1483 case LH_TURNOUT: 1484 case WYE_TURNOUT: { 1485 result = 90 - MathUtil.computeAngleDEG(getCoordsA(), getCoordsCenter()); 1486 break; 1487 } 1488 case DOUBLE_XOVER: 1489 case RH_XOVER: 1490 case LH_XOVER: { 1491 result = 90 - MathUtil.computeAngleDEG(getCoordsA(), getCoordsB()); 1492 break; 1493 } 1494 default: { 1495 break; 1496 } 1497 } 1498 return result; 1499 } 1500 1501 /** 1502 * Toggle turnout if clicked on, physical turnout exists, and not disabled. 1503 */ 1504 public void toggleTurnout() { 1505 turnout.toggleTurnout(); 1506 } 1507 1508 /** 1509 * Set the LayoutTurnout state. Used for sending the toggle command Checks 1510 * not disabled, disable when occupied Also sets secondary Turnout commanded 1511 * state 1512 * 1513 * @param state New state to set, eg Turnout.CLOSED 1514 */ 1515 public void setState(int state) { 1516 turnout.setState(state); 1517 } 1518 1519 /** 1520 * Get the LayoutTurnout state 1521 * <p> 1522 * Ensures the secondary Turnout state matches the primary 1523 * 1524 * @return the state, eg Turnout.CLOSED or Turnout.INCONSISTENT 1525 */ 1526 public int getState() { 1527 return turnout.getState(); 1528 } 1529 1530 /** 1531 * Is this turnout occupied? 1532 * 1533 * @return true if occupied 1534 */ 1535 private boolean isOccupied() { 1536 return turnout.isOccupied(); 1537 } 1538 1539 // initialization instance variables (used when loading a LayoutEditor) 1540 public String connectAName = ""; 1541 public String connectBName = ""; 1542 public String connectCName = ""; 1543 public String connectDName = ""; 1544 1545 public String tBlockAName = ""; 1546 public String tBlockBName = ""; 1547 public String tBlockCName = ""; 1548 public String tBlockDName = ""; 1549 1550 private JPopupMenu popup = null; 1551 1552 /** 1553 * {@inheritDoc} 1554 */ 1555 @Override 1556 @Nonnull 1557 protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent) { 1558 if (popup != null) { 1559 popup.removeAll(); 1560 } else { 1561 popup = new JPopupMenu(); 1562 } 1563 1564 if (layoutEditor.isEditable()) { 1565 String label = ""; 1566 switch (getTurnoutType()) { 1567 case RH_TURNOUT: 1568 label = Bundle.getMessage("RightTurnout"); 1569 break; 1570 case LH_TURNOUT: 1571 label = Bundle.getMessage("LeftTurnout"); 1572 break; 1573 case WYE_TURNOUT: 1574 label = Bundle.getMessage("WYETurnout"); 1575 break; 1576 case DOUBLE_XOVER: 1577 label = Bundle.getMessage("DoubleCrossover"); 1578 break; 1579 case RH_XOVER: 1580 label = Bundle.getMessage("RightCrossover"); 1581 break; 1582 case LH_XOVER: 1583 label = Bundle.getMessage("LeftCrossover"); 1584 break; 1585 default: 1586 break; 1587 } 1588 JMenuItem jmi = popup.add(Bundle.getMessage("MakeLabel", label) + getName()); 1589 jmi.setEnabled(false); 1590 1591 if (getTurnout() == null) { 1592 jmi = popup.add(Bundle.getMessage("NoTurnout")); 1593 } else { 1594 String stateString = getTurnoutStateString(getTurnout().getKnownState()); 1595 stateString = String.format(" (%s)", stateString); 1596 jmi = popup.add(Bundle.getMessage("BeanNameTurnout") 1597 + ": " + getTurnoutName() + stateString); 1598 } 1599 jmi.setEnabled(false); 1600 1601 if (getSecondTurnout() != null) { 1602 String stateString = getTurnoutStateString(getSecondTurnout().getKnownState()); 1603 stateString = String.format(" (%s)", stateString); 1604 jmi = popup.add(Bundle.getMessage("Supporting", 1605 Bundle.getMessage("BeanNameTurnout")) 1606 + ": " + getSecondTurnoutName() + stateString); 1607 } 1608 jmi.setEnabled(false); 1609 1610 if (getBlockName().isEmpty()) { 1611 jmi = popup.add(Bundle.getMessage("NoBlock")); 1612 jmi.setEnabled(false); 1613 } else { 1614 jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + getLayoutBlock().getDisplayName()); 1615 jmi.setEnabled(false); 1616 if (isTurnoutTypeXover()) { 1617 // check if extra blocks have been entered 1618 if ((getLayoutBlockB() != null) && (getLayoutBlockB() != getLayoutBlock())) { 1619 jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "B")) + getLayoutBlockB().getDisplayName()); 1620 jmi.setEnabled(false); 1621 } 1622 if ((getLayoutBlockC() != null) && (getLayoutBlockC() != getLayoutBlock())) { 1623 jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "C")) + getLayoutBlockC().getDisplayName()); 1624 jmi.setEnabled(false); 1625 } 1626 if ((getLayoutBlockD() != null) && (getLayoutBlockD() != getLayoutBlock())) { 1627 jmi = popup.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("Block_ID", "D")) + getLayoutBlockD().getDisplayName()); 1628 jmi.setEnabled(false); 1629 } 1630 } 1631 } 1632 1633 // if there are any track connections 1634 if ((getConnectA() != null) || (getConnectB() != null) 1635 || (getConnectC() != null) || (getConnectD() != null)) { 1636 JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections")); // there is no pane opening (which is what ... implies) 1637 if (getConnectA() != null) { 1638 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "A") + getConnectA().getName()) { 1639 @Override 1640 public void actionPerformed(ActionEvent e) { 1641 LayoutEditorFindItems lf = layoutEditor.getFinder(); 1642 LayoutTrack lt = lf.findObjectByName(getConnectA().getName()); 1643 // this shouldn't ever be null... however... 1644 if (lt != null) { 1645 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 1646 layoutEditor.setSelectionRect(ltv.getBounds()); 1647 ltv.showPopup(); 1648 } 1649 } 1650 }); 1651 } 1652 if (getConnectB() != null) { 1653 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "B") + getConnectB().getName()) { 1654 @Override 1655 public void actionPerformed(ActionEvent e) { 1656 LayoutEditorFindItems lf = layoutEditor.getFinder(); 1657 LayoutTrack lt = lf.findObjectByName(getConnectB().getName()); 1658 // this shouldn't ever be null... however... 1659 if (lt != null) { 1660 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 1661 layoutEditor.setSelectionRect(ltv.getBounds()); 1662 ltv.showPopup(); 1663 } 1664 } 1665 }); 1666 } 1667 if (getConnectC() != null) { 1668 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "C") + getConnectC().getName()) { 1669 @Override 1670 public void actionPerformed(ActionEvent e) { 1671 LayoutEditorFindItems lf = layoutEditor.getFinder(); 1672 LayoutTrack lt = lf.findObjectByName(getConnectC().getName()); 1673 // this shouldn't ever be null... however... 1674 if (lt != null) { 1675 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 1676 layoutEditor.setSelectionRect(ltv.getBounds()); 1677 ltv.showPopup(); 1678 } 1679 } 1680 }); 1681 } 1682 if (getConnectD() != null) { 1683 connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "D") + getConnectD().getName()) { 1684 @Override 1685 public void actionPerformed(ActionEvent e) { 1686 LayoutEditorFindItems lf = layoutEditor.getFinder(); 1687 LayoutTrack lt = lf.findObjectByName(getConnectD().getName()); 1688 // this shouldn't ever be null... however... 1689 if (lt != null) { 1690 LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt); 1691 layoutEditor.setSelectionRect(ltv.getBounds()); 1692 ltv.showPopup(); 1693 } 1694 } 1695 }); 1696 } 1697 popup.add(connectionsMenu); 1698 } 1699 popup.add(new JSeparator(JSeparator.HORIZONTAL)); 1700 1701 JCheckBoxMenuItem hiddenCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("Hidden")); 1702 hiddenCheckBoxMenuItem.setSelected(isHidden()); 1703 popup.add(hiddenCheckBoxMenuItem); 1704 hiddenCheckBoxMenuItem.addActionListener( e1 -> { 1705 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e1.getSource(); 1706 setHidden(o.isSelected()); 1707 }); 1708 1709 JCheckBoxMenuItem showUnknownCheckBoxMenuItem = new JCheckBoxMenuItem(Bundle.getMessage("ShowUnknown")); 1710 hiddenCheckBoxMenuItem.setSelected(getShowUnknown()); 1711 popup.add(showUnknownCheckBoxMenuItem); 1712 showUnknownCheckBoxMenuItem.addActionListener( e1 -> { 1713 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e1.getSource(); 1714 setShowUnknown(o.isSelected()); 1715 }); 1716 1717 JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(Bundle.getMessage("Disabled")); 1718 cbmi.setSelected(isDisabled()); 1719 popup.add(cbmi); 1720 cbmi.addActionListener( e2 -> { 1721 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e2.getSource(); 1722 setDisabled(o.isSelected()); 1723 }); 1724 1725 cbmi = new JCheckBoxMenuItem(Bundle.getMessage("DisabledWhenOccupied")); 1726 if (getTurnout() == null || getBlockName().isEmpty()) { 1727 cbmi.setEnabled(false); 1728 } 1729 cbmi.setSelected(isDisabledWhenOccupied()); 1730 popup.add(cbmi); 1731 cbmi.addActionListener( e3 -> { 1732 JCheckBoxMenuItem o = (JCheckBoxMenuItem) e3.getSource(); 1733 setDisableWhenOccupied(o.isSelected()); 1734 }); 1735 1736 // Rotate if there are no track connections 1737// if ((getConnectA() == null) && (getConnectB() == null) 1738// && (getConnectC() == null) 1739// && (getConnectD() == null)) 1740 1741 JMenuItem rotateItem = new JMenuItem(Bundle.getMessage("Rotate_", 1742 String.format(Locale.getDefault(), "%.1f", getRotationDEG())) + "..."); 1743 popup.add(rotateItem); 1744 rotateItem.addActionListener( event -> displayRotationDialog()); 1745 1746 popup.add(new AbstractAction(Bundle.getMessage("UseSizeAsDefault")) { 1747 @Override 1748 public void actionPerformed(ActionEvent e) { 1749 setUpDefaultSize(); 1750 } 1751 }); 1752 1753 popup.add(new AbstractAction(Bundle.getMessage("ButtonEdit")) { 1754 @Override 1755 public void actionPerformed(ActionEvent e) { 1756 editor.editLayoutTrack(LayoutTurnoutView.this); 1757 } 1758 }); 1759 popup.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) { 1760 @Override 1761 public void actionPerformed(ActionEvent e) { 1762 if (canRemove() && removeInlineLogixNG() 1763 && layoutEditor.removeLayoutTurnout(turnout)) { 1764 // Returned true if user did not cancel 1765 remove(); 1766 dispose(); 1767 } 1768 } 1769 }); 1770 1771 if (getTurnout() != null) { 1772 AbstractAction ssaa = new AbstractAction(Bundle.getMessage("SetSignals")) { 1773 @Override 1774 public void actionPerformed(ActionEvent e) { 1775 LayoutEditorTools tools = layoutEditor.getLETools(); 1776 LayoutEditorToolBarPanel letbp = getLayoutEditorToolBarPanel(); 1777 if (isTurnoutTypeXover()) { 1778 tools.setSignalsAtXoverTurnoutFromMenu(turnout, 1779 letbp.signalIconEditor, letbp.signalFrame); 1780 } else if (getLinkType() == LinkType.NO_LINK) { 1781 tools.setSignalsAtTurnoutFromMenu(turnout, 1782 letbp.signalIconEditor, letbp.signalFrame); 1783 } else if (getLinkType() == LinkType.THROAT_TO_THROAT) { 1784 tools.setSignalsAtThroatToThroatTurnoutsFromMenu(turnout, getLinkedTurnoutName(), 1785 letbp.signalIconEditor, letbp.signalFrame); 1786 } else if (getLinkType() == LinkType.FIRST_3_WAY) { 1787 tools.setSignalsAt3WayTurnoutFromMenu(getTurnoutName(), getLinkedTurnoutName(), 1788 letbp.signalIconEditor, letbp.signalFrame); 1789 } else if (getLinkType() == LinkType.SECOND_3_WAY) { 1790 tools.setSignalsAt3WayTurnoutFromMenu(getLinkedTurnoutName(), getTurnoutName(), 1791 letbp.signalIconEditor, letbp.signalFrame); 1792 } 1793 } 1794 }; 1795 1796 JMenu jm = new JMenu(Bundle.getMessage("SignalHeads")); 1797 if (layoutEditor.getLETools().addLayoutTurnoutSignalHeadInfoToMenu( 1798 getTurnoutName(), getLinkedTurnoutName(), jm)) { 1799 jm.add(ssaa); 1800 popup.add(jm); 1801 } else { 1802 popup.add(ssaa); 1803 } 1804 } 1805 if (!getBlockName().isEmpty()) { 1806 final String[] boundaryBetween = getBlockBoundaries(); 1807 boolean blockBoundaries = false; 1808 for (int i = 0; i < 4; i++) { 1809 if (boundaryBetween[i] != null) { 1810 blockBoundaries = true; 1811 1812 } 1813 } 1814 1815 if (blockBoundaries) { 1816 popup.add(new AbstractAction(Bundle.getMessage("SetSignalMasts")) { 1817 @Override 1818 public void actionPerformed(ActionEvent e) { 1819 layoutEditor.getLETools().setSignalMastsAtTurnoutFromMenu(turnout, 1820 boundaryBetween); 1821 } 1822 }); 1823 popup.add(new AbstractAction(Bundle.getMessage("SetSensors")) { 1824 @Override 1825 public void actionPerformed(ActionEvent e) { 1826 LayoutEditorToolBarPanel letbp = getLayoutEditorToolBarPanel(); 1827 layoutEditor.getLETools().setSensorsAtTurnoutFromMenu( 1828 turnout, 1829 boundaryBetween, 1830 letbp.sensorIconEditor, 1831 letbp.sensorFrame); 1832 } 1833 }); 1834 1835 } 1836 1837 if (InstanceManager.getDefault(LayoutBlockManager.class 1838 ).isAdvancedRoutingEnabled()) { 1839 Map<String, LayoutBlock> map = new HashMap<>(); 1840 if (!getBlockName().isEmpty()) { 1841 map.put(getBlockName(), getLayoutBlock()); 1842 } 1843 if (!getBlockBName().isEmpty()) { 1844 map.put(getBlockBName(), getLayoutBlockB()); 1845 } 1846 if (!getBlockCName().isEmpty()) { 1847 map.put(getBlockCName(), getLayoutBlockC()); 1848 } 1849 if (!getBlockDName().isEmpty()) { 1850 map.put(getBlockDName(), getLayoutBlockD()); 1851 } 1852 if (blockBoundaries) { 1853 if (map.size() == 1) { 1854 popup.add(new AbstractAction(Bundle.getMessage("ViewBlockRouting")) { 1855 @Override 1856 public void actionPerformed(ActionEvent e) { 1857 AbstractAction routeTableAction = new LayoutBlockRouteTableAction("ViewRouting", getLayoutBlock()); 1858 routeTableAction.actionPerformed(e); 1859 } 1860 }); 1861 } else if (map.size() > 1) { 1862 JMenu viewRouting = new JMenu(Bundle.getMessage("ViewBlockRouting")); 1863 for (Map.Entry<String, LayoutBlock> entry : map.entrySet()) { 1864 String blockName = entry.getKey(); 1865 LayoutBlock layoutBlock = entry.getValue(); 1866 viewRouting.add(new AbstractActionImpl(blockName, getBlockBName(), layoutBlock)); 1867 } 1868 popup.add(viewRouting); 1869 } 1870 } // if (blockBoundaries) 1871 } // .isAdvancedRoutingEnabled() 1872 } // getBlockName().isEmpty() 1873 setAdditionalEditPopUpMenu(popup); 1874 layoutEditor.setShowAlignmentMenu(popup); 1875 addCommonPopupItems(mouseEvent, popup); 1876 popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY()); 1877 } else if (!viewAdditionalMenu.isEmpty()) { 1878 setAdditionalViewPopUpMenu(popup); 1879 addCommonPopupItems(mouseEvent, popup); 1880 popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY()); 1881 } 1882 return popup; 1883 } // showPopup 1884 1885 private void displayRotationDialog(){ 1886 boolean entering = true; 1887 while (entering) { 1888 // prompt for rotation angle 1889 String newAngle = JmriJOptionPane.showInputDialog(layoutEditor, 1890 Bundle.getMessage("MakeLabel", Bundle.getMessage("EnterRotation")),""); 1891 if (newAngle==null || newAngle.isEmpty()) { 1892 return; // cancelled 1893 } 1894 try { 1895 double rot = IntlUtilities.doubleValue(newAngle); 1896 entering = false; 1897 if ( Double.compare(rot, 0.0d) != 0 ) { // i.e. rot != 0 1898 rotateCoords(rot); 1899 layoutEditor.redrawPanel(); 1900 } 1901 } catch (ParseException e1) { 1902 JmriJOptionPane.showMessageDialog(layoutEditor, Bundle.getMessage("Error3") 1903 + " " + e1, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 1904 } 1905 } 1906 } 1907 1908 public String[] getBlockBoundaries() { 1909 return turnout.getBlockBoundaries(); 1910 } 1911 1912 public ArrayList<LayoutBlock> getProtectedBlocks(jmri.NamedBean bean) { 1913 return turnout.getProtectedBlocks(bean); 1914 } 1915 1916 protected void removeSML(SignalMast signalMast) { 1917 turnout.removeSML(signalMast); 1918 } 1919 1920 /** 1921 * Clean up when this object is no longer needed. Should not be called while 1922 * the object is still displayed; see {@link #remove()} 1923 */ 1924 public void dispose() { 1925 if (popup != null) { 1926 popup.removeAll(); 1927 } 1928 popup = null; 1929 } 1930 1931 /** 1932 * Remove this object from display and persistance. 1933 */ 1934 public void remove() { 1935 turnout.remove(); 1936 } 1937 1938 /** 1939 * "active" means that the object is still displayed, and should be stored. 1940 * 1941 * @return true if active 1942 */ 1943 public boolean isActive() { 1944 return turnout.isActive(); 1945 } 1946 1947 ArrayList<JMenuItem> editAdditionalMenu = new ArrayList<>(0); 1948 ArrayList<JMenuItem> viewAdditionalMenu = new ArrayList<>(0); 1949 1950 public void addEditPopUpMenu(JMenuItem menu) { 1951 if (!editAdditionalMenu.contains(menu)) { 1952 editAdditionalMenu.add(menu); 1953 } 1954 } 1955 1956 public void addViewPopUpMenu(JMenuItem menu) { 1957 if (!viewAdditionalMenu.contains(menu)) { 1958 viewAdditionalMenu.add(menu); 1959 } 1960 } 1961 1962 public void setAdditionalEditPopUpMenu(JPopupMenu popup) { 1963 if (editAdditionalMenu.isEmpty()) { 1964 return; 1965 } 1966 popup.addSeparator(); 1967 for (JMenuItem mi : editAdditionalMenu) { 1968 popup.add(mi); 1969 } 1970 } 1971 1972 public void setAdditionalViewPopUpMenu(JPopupMenu popup) { 1973 if (viewAdditionalMenu.isEmpty()) { 1974 return; 1975 } 1976 popup.addSeparator(); 1977 for (JMenuItem mi : viewAdditionalMenu) { 1978 popup.add(mi); 1979 } 1980 } 1981 1982 /** 1983 * Draw track decorations. 1984 * <p> 1985 * This type of track has none, so this method is empty. 1986 */ 1987 @Override 1988 protected void drawDecorations(Graphics2D g2) { 1989 } 1990 1991 /** 1992 * {@inheritDoc} 1993 */ 1994 @Override 1995 protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) { 1996 if (isBlock && getLayoutBlock() == null) { 1997 // Skip the block layer if there is no block assigned. 1998 return; 1999 } 2000 2001 Point2D pA = getCoordsA(); 2002 Point2D pB = getCoordsB(); 2003 Point2D pC = getCoordsC(); 2004 Point2D pD = getCoordsD(); 2005 2006 boolean mainlineA = isMainlineA(); 2007 boolean mainlineB = isMainlineB(); 2008 boolean mainlineC = isMainlineC(); 2009 boolean mainlineD = isMainlineD(); 2010 2011 boolean drawUnselectedLeg = layoutEditor.isTurnoutDrawUnselectedLeg(); 2012 2013 Color color = g2.getColor(); 2014 2015 // if this isn't a block line all these will be the same color 2016 Color colorA = color; 2017 Color colorB = color; 2018 Color colorC = color; 2019 Color colorD = color; 2020 2021 if (isBlock) { 2022 LayoutBlock lb = getLayoutBlock(); 2023 colorA = (lb == null) ? color : lb.getBlockColor(); 2024 lb = getLayoutBlockB(); 2025 colorB = (lb == null) ? color : lb.getBlockColor(); 2026 lb = getLayoutBlockC(); 2027 colorC = (lb == null) ? color : lb.getBlockColor(); 2028 lb = getLayoutBlockD(); 2029 colorD = (lb == null) ? color : lb.getBlockColor(); 2030 } 2031 2032 // middles 2033 Point2D pM = getCoordsCenter(); 2034 Point2D pABM = MathUtil.midPoint(pA, pB); 2035 Point2D pAM = MathUtil.lerp(pA, pABM, 5.0 / 8.0); 2036 Point2D pAMP = MathUtil.midPoint(pAM, pABM); 2037 Point2D pBM = MathUtil.lerp(pB, pABM, 5.0 / 8.0); 2038 Point2D pBMP = MathUtil.midPoint(pBM, pABM); 2039 2040 Point2D pCDM = MathUtil.midPoint(pC, pD); 2041 Point2D pCM = MathUtil.lerp(pC, pCDM, 5.0 / 8.0); 2042 Point2D pCMP = MathUtil.midPoint(pCM, pCDM); 2043 Point2D pDM = MathUtil.lerp(pD, pCDM, 5.0 / 8.0); 2044 Point2D pDMP = MathUtil.midPoint(pDM, pCDM); 2045 2046 Point2D pAF = MathUtil.midPoint(pAM, pM); 2047 Point2D pBF = MathUtil.midPoint(pBM, pM); 2048 Point2D pCF = MathUtil.midPoint(pCM, pM); 2049 Point2D pDF = MathUtil.midPoint(pDM, pM); 2050 2051 int state = UNKNOWN; 2052 if (layoutEditor.isAnimating()) { 2053 state = getState(); 2054 } 2055 2056 TurnoutType type = getTurnoutType(); 2057 2058 // Just "?" if UNKNOWN and showUnknown requesting 2059 if (showUnknown && state == UNKNOWN) { 2060 drawForShowUnknown(g2, pM, g2.getColor(), false); 2061 return; 2062 } 2063 2064 if (type == TurnoutType.DOUBLE_XOVER) { 2065 if (state != Turnout.THROWN && state != INCONSISTENT) { // unknown or continuing path - not crossed over 2066 if (isMain == mainlineA) { 2067 g2.setColor(colorA); 2068 g2.draw(new Line2D.Double(pA, pABM)); 2069 if (!isBlock || drawUnselectedLeg) { 2070 g2.draw(new Line2D.Double(pAF, pM)); 2071 } 2072 } 2073 if (isMain == mainlineB) { 2074 g2.setColor(colorB); 2075 g2.draw(new Line2D.Double(pB, pABM)); 2076 if (!isBlock || drawUnselectedLeg) { 2077 g2.draw(new Line2D.Double(pBF, pM)); 2078 } 2079 } 2080 if (isMain == mainlineC) { 2081 g2.setColor(colorC); 2082 g2.draw(new Line2D.Double(pC, pCDM)); 2083 if (!isBlock || drawUnselectedLeg) { 2084 g2.draw(new Line2D.Double(pCF, pM)); 2085 } 2086 } 2087 if (isMain == mainlineD) { 2088 g2.setColor(colorD); 2089 g2.draw(new Line2D.Double(pD, pCDM)); 2090 if (!isBlock || drawUnselectedLeg) { 2091 g2.draw(new Line2D.Double(pDF, pM)); 2092 } 2093 } 2094 } 2095 if (state != Turnout.CLOSED && state != INCONSISTENT) { // unknown or diverting path - crossed over 2096 if (isMain == mainlineA) { 2097 g2.setColor(colorA); 2098 g2.draw(new Line2D.Double(pA, pAM)); 2099 g2.draw(new Line2D.Double(pAM, pM)); 2100 if (!isBlock || drawUnselectedLeg) { 2101 g2.draw(new Line2D.Double(pAMP, pABM)); 2102 } 2103 } 2104 if (isMain == mainlineB) { 2105 g2.setColor(colorB); 2106 g2.draw(new Line2D.Double(pB, pBM)); 2107 g2.draw(new Line2D.Double(pBM, pM)); 2108 if (!isBlock || drawUnselectedLeg) { 2109 g2.draw(new Line2D.Double(pBMP, pABM)); 2110 } 2111 } 2112 if (isMain == mainlineC) { 2113 g2.setColor(colorC); 2114 g2.draw(new Line2D.Double(pC, pCM)); 2115 g2.draw(new Line2D.Double(pCM, pM)); 2116 if (!isBlock || drawUnselectedLeg) { 2117 g2.draw(new Line2D.Double(pCMP, pCDM)); 2118 } 2119 } 2120 if (isMain == mainlineD) { 2121 g2.setColor(colorD); 2122 g2.draw(new Line2D.Double(pD, pDM)); 2123 g2.draw(new Line2D.Double(pDM, pM)); 2124 if (!isBlock || drawUnselectedLeg) { 2125 g2.draw(new Line2D.Double(pDMP, pCDM)); 2126 } 2127 } 2128 } 2129 if (state == INCONSISTENT) { 2130 if (isMain == mainlineA) { 2131 g2.setColor(colorA); 2132 g2.draw(new Line2D.Double(pA, pAM)); 2133 } 2134 if (isMain == mainlineB) { 2135 g2.setColor(colorB); 2136 g2.draw(new Line2D.Double(pB, pBM)); 2137 } 2138 if (isMain == mainlineC) { 2139 g2.setColor(colorC); 2140 g2.draw(new Line2D.Double(pC, pCM)); 2141 } 2142 if (isMain == mainlineD) { 2143 g2.setColor(colorD); 2144 g2.draw(new Line2D.Double(pD, pDM)); 2145 } 2146 if (!isBlock || drawUnselectedLeg) { 2147 if (isMain == mainlineA) { 2148 g2.setColor(colorA); 2149 g2.draw(new Line2D.Double(pAF, pM)); 2150 } 2151 if (isMain == mainlineC) { 2152 g2.setColor(colorC); 2153 g2.draw(new Line2D.Double(pCF, pM)); 2154 } 2155 if (isMain == mainlineB) { 2156 g2.setColor(colorB); 2157 g2.draw(new Line2D.Double(pBF, pM)); 2158 } 2159 if (isMain == mainlineD) { 2160 g2.setColor(colorD); 2161 g2.draw(new Line2D.Double(pDF, pM)); 2162 } 2163 } 2164 } 2165 } else if ((type == TurnoutType.RH_XOVER) 2166 || (type == TurnoutType.LH_XOVER)) { // draw (rh & lh) cross overs 2167 pAF = MathUtil.midPoint(pABM, pM); 2168 pBF = MathUtil.midPoint(pABM, pM); 2169 pCF = MathUtil.midPoint(pCDM, pM); 2170 pDF = MathUtil.midPoint(pCDM, pM); 2171 if (state != Turnout.THROWN && state != INCONSISTENT) { // unknown or continuing path - not crossed over 2172 if (isMain == mainlineA) { 2173 g2.setColor(colorA); 2174 g2.draw(new Line2D.Double(pA, pABM)); 2175 } 2176 if (isMain == mainlineB) { 2177 g2.setColor(colorB); 2178 g2.draw(new Line2D.Double(pABM, pB)); 2179 } 2180 if (isMain == mainlineC) { 2181 g2.setColor(colorC); 2182 g2.draw(new Line2D.Double(pC, pCDM)); 2183 } 2184 if (isMain == mainlineD) { 2185 g2.setColor(colorD); 2186 g2.draw(new Line2D.Double(pCDM, pD)); 2187 } 2188 if (!isBlock || drawUnselectedLeg) { 2189 if (getTurnoutType() == TurnoutType.RH_XOVER) { 2190 if (isMain == mainlineA) { 2191 g2.setColor(colorA); 2192 g2.draw(new Line2D.Double(pAF, pM)); 2193 } 2194 if (isMain == mainlineC) { 2195 g2.setColor(colorC); 2196 g2.draw(new Line2D.Double(pCF, pM)); 2197 } 2198 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 2199 if (isMain == mainlineB) { 2200 g2.setColor(colorB); 2201 g2.draw(new Line2D.Double(pBF, pM)); 2202 } 2203 if (isMain == mainlineD) { 2204 g2.setColor(colorD); 2205 g2.draw(new Line2D.Double(pDF, pM)); 2206 } 2207 } 2208 } 2209 } 2210 if (state != Turnout.CLOSED && state != INCONSISTENT) { // unknown or diverting path - crossed over 2211 if (getTurnoutType() == TurnoutType.RH_XOVER) { 2212 if (isMain == mainlineA) { 2213 g2.setColor(colorA); 2214 g2.draw(new Line2D.Double(pA, pABM)); 2215 g2.draw(new Line2D.Double(pABM, pM)); 2216 } 2217 if (!isBlock || drawUnselectedLeg) { 2218 if (isMain == mainlineB) { 2219 g2.setColor(colorB); 2220 g2.draw(new Line2D.Double(pBM, pB)); 2221 } 2222 } 2223 if (isMain == mainlineC) { 2224 g2.setColor(colorC); 2225 g2.draw(new Line2D.Double(pC, pCDM)); 2226 g2.draw(new Line2D.Double(pCDM, pM)); 2227 } 2228 if (!isBlock || drawUnselectedLeg) { 2229 if (isMain == mainlineD) { 2230 g2.setColor(colorD); 2231 g2.draw(new Line2D.Double(pDM, pD)); 2232 } 2233 } 2234 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 2235 if (!isBlock || drawUnselectedLeg) { 2236 if (isMain == mainlineA) { 2237 g2.setColor(colorA); 2238 g2.draw(new Line2D.Double(pA, pAM)); 2239 } 2240 } 2241 if (isMain == mainlineB) { 2242 g2.setColor(colorB); 2243 g2.draw(new Line2D.Double(pB, pABM)); 2244 g2.draw(new Line2D.Double(pABM, pM)); 2245 } 2246 if (!isBlock || drawUnselectedLeg) { 2247 if (isMain == mainlineC) { 2248 g2.setColor(colorC); 2249 g2.draw(new Line2D.Double(pC, pCM)); 2250 } 2251 } 2252 if (isMain == mainlineD) { 2253 g2.setColor(colorD); 2254 g2.draw(new Line2D.Double(pD, pCDM)); 2255 g2.draw(new Line2D.Double(pCDM, pM)); 2256 } 2257 } 2258 } 2259 if (state == INCONSISTENT) { 2260 if (isMain == mainlineA) { 2261 g2.setColor(colorA); 2262 g2.draw(new Line2D.Double(pA, pAM)); 2263 } 2264 if (isMain == mainlineB) { 2265 g2.setColor(colorB); 2266 g2.draw(new Line2D.Double(pB, pBM)); 2267 } 2268 if (isMain == mainlineC) { 2269 g2.setColor(colorC); 2270 g2.draw(new Line2D.Double(pC, pCM)); 2271 } 2272 if (isMain == mainlineD) { 2273 g2.setColor(colorD); 2274 g2.draw(new Line2D.Double(pD, pDM)); 2275 } 2276 if (!isBlock || drawUnselectedLeg) { 2277 if (getTurnoutType() == TurnoutType.RH_XOVER) { 2278 if (isMain == mainlineA) { 2279 g2.setColor(colorA); 2280 g2.draw(new Line2D.Double(pAF, pM)); 2281 } 2282 if (isMain == mainlineC) { 2283 g2.setColor(colorC); 2284 g2.draw(new Line2D.Double(pCF, pM)); 2285 } 2286 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 2287 if (isMain == mainlineB) { 2288 g2.setColor(colorB); 2289 g2.draw(new Line2D.Double(pBF, pM)); 2290 } 2291 if (isMain == mainlineD) { 2292 g2.setColor(colorD); 2293 g2.draw(new Line2D.Double(pDF, pM)); 2294 } 2295 } 2296 } 2297 } 2298 } else if (isTurnoutTypeSlip()) { 2299 log.error("{}.draw1(...); slips should be being drawn by LayoutSlip sub-class", getName()); 2300 } else { // LH, RH, or WYE Turnouts 2301 2302 // draw A<===>center 2303 if (isMain == mainlineA) { 2304 g2.setColor(colorA); 2305 g2.draw(new Line2D.Double(pA, pM)); 2306 } 2307 2308 if (state == UNKNOWN || (getContinuingSense() == state && state != INCONSISTENT)) { // unknown or continuing path 2309 // draw center<===>B 2310 if (isMain == mainlineB) { 2311 g2.setColor(colorB); 2312 g2.draw(new Line2D.Double(pM, pB)); 2313 } 2314 } else if (!isBlock || drawUnselectedLeg) { 2315 // draw center<--=>B 2316 if (isMain == mainlineB) { 2317 g2.setColor(colorB); 2318 g2.draw(new Line2D.Double(MathUtil.twoThirdsPoint(pM, pB), pB)); 2319 } 2320 } 2321 2322 if (state == UNKNOWN || (getContinuingSense() != state && state != INCONSISTENT)) { // unknown or diverting path 2323 // draw center<===>C 2324 if (isMain == mainlineC) { 2325 g2.setColor(colorC); 2326 g2.draw(new Line2D.Double(pM, pC)); 2327 } 2328 } else if (!isBlock || drawUnselectedLeg) { 2329 // draw center<--=>C 2330 if (isMain == mainlineC) { 2331 g2.setColor(colorC); 2332 g2.draw(new Line2D.Double(MathUtil.twoThirdsPoint(pM, pC), pC)); 2333 } 2334 } 2335 } 2336 } // draw1 2337 2338 /** 2339 * Draw a "?" for the UNKNOWN state. 2340 * 2341 * To be invoked if getShowUnknown() is true 2342 * 2343 * @param g2 the graphics context to draw in 2344 * @param center where to center the "?" 2345 * @param color the base color for drawing 2346 * @param complement true means select black or white to complement the base color 2347 */ 2348 private void drawForShowUnknown(Graphics2D g2, Point2D center, Color color, boolean complement) { 2349 var originalFont = g2.getFont(); 2350 var originalColor = g2.getColor(); 2351 2352 // convert color to HSV to get intensity 2353 int v = Math.max(Math.max(color.getBlue(), color.getGreen()), color.getRed()); 2354 2355 Color drawColor = color; 2356 2357 if (complement) { 2358 drawColor = Color.BLACK; 2359 if ( v < 255*0.5) { 2360 drawColor = Color.WHITE; 2361 } 2362 } 2363 2364 g2.setColor(drawColor); 2365 2366 int size = (int) layoutEditor.circleDiameter; 2367 2368 g2.setFont(new Font("SansSerif", Font.BOLD, size)); 2369 2370 var metrics = g2.getFontMetrics(); 2371 double x = center.getX() - ((double) metrics.charWidth('?'))/2; // - to move left 2372 double y = center.getY() + (metrics.getAscent()*0.9)/2; // + to move down 2373 2374 g2.drawString("?", (float) x, (float) y); 2375 2376 g2.setColor(originalColor); 2377 g2.setFont(originalFont); 2378 } 2379 2380 /** 2381 * {@inheritDoc} 2382 */ 2383 @Override 2384 protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) { 2385 TurnoutType type = getTurnoutType(); 2386 2387 Point2D pA = getCoordsA(); 2388 Point2D pB = getCoordsB(); 2389 Point2D pC = getCoordsC(); 2390 Point2D pD = getCoordsD(); 2391 Point2D pM = getCoordsCenter(); 2392 2393 Point2D vAM = MathUtil.normalize(MathUtil.subtract(pM, pA)); 2394 Point2D vAMo = MathUtil.orthogonal(MathUtil.normalize(vAM, railDisplacement)); 2395 2396 Point2D pAL = MathUtil.subtract(pA, vAMo); 2397 Point2D pAR = MathUtil.add(pA, vAMo); 2398 2399 Point2D vBM = MathUtil.normalize(MathUtil.subtract(pB, pM)); 2400 double dirBM_DEG = MathUtil.computeAngleDEG(vBM); 2401 Point2D vBMo = MathUtil.normalize(MathUtil.orthogonal(vBM), railDisplacement); 2402 Point2D pBL = MathUtil.subtract(pB, vBMo); 2403 Point2D pBR = MathUtil.add(pB, vBMo); 2404 Point2D pMR = MathUtil.add(pM, vBMo); 2405 2406 Point2D vCM = MathUtil.normalize(MathUtil.subtract(pC, pM)); 2407 double dirCM_DEG = MathUtil.computeAngleDEG(vCM); 2408 2409 Point2D vCMo = MathUtil.normalize(MathUtil.orthogonal(vCM), railDisplacement); 2410 Point2D pCL = MathUtil.subtract(pC, vCMo); 2411 Point2D pCR = MathUtil.add(pC, vCMo); 2412 Point2D pML = MathUtil.subtract(pM, vBMo); 2413 2414 double deltaBMC_DEG = MathUtil.absDiffAngleDEG(dirBM_DEG, dirCM_DEG); 2415 double deltaBMC_RAD = Math.toRadians(deltaBMC_DEG); 2416 2417 double hypotF = railDisplacement / Math.sin(deltaBMC_RAD / 2.0); 2418 2419 Point2D vDisF = MathUtil.normalize(MathUtil.add(vAM, vCM), hypotF); 2420 if (type == TurnoutType.WYE_TURNOUT) { 2421 vDisF = MathUtil.normalize(vAM, hypotF); 2422 } 2423 Point2D pF = MathUtil.add(pM, vDisF); 2424 2425 Point2D pFR = MathUtil.add(pF, MathUtil.multiply(vBMo, 2.0)); 2426 Point2D pFL = MathUtil.subtract(pF, MathUtil.multiply(vCMo, 2.0)); 2427 2428 // Point2D pFPR = MathUtil.add(pF, MathUtil.normalize(vBMo, 2.0)); 2429 // Point2D pFPL = MathUtil.subtract(pF, MathUtil.normalize(vCMo, 2.0)); 2430 Point2D vDisAP = MathUtil.normalize(vAM, hypotF); 2431 Point2D pAP = MathUtil.subtract(pM, vDisAP); 2432 Point2D pAPR = MathUtil.add(pAP, vAMo); 2433 Point2D pAPL = MathUtil.subtract(pAP, vAMo); 2434 2435 // Point2D vSo = MathUtil.normalize(vAMo, 2.0); 2436 // Point2D pSL = MathUtil.add(pAPL, vSo); 2437 // Point2D pSR = MathUtil.subtract(pAPR, vSo); 2438 boolean mainlineA = isMainlineA(); 2439 boolean mainlineB = isMainlineB(); 2440 boolean mainlineC = isMainlineC(); 2441 boolean mainlineD = isMainlineD(); 2442 2443 int state = UNKNOWN; 2444 if (layoutEditor.isAnimating()) { 2445 state = getState(); 2446 } 2447 2448 switch (type) { 2449 case RH_TURNOUT: { 2450 if (isMain == mainlineA) { 2451 g2.draw(new Line2D.Double(pAL, pML)); 2452 g2.draw(new Line2D.Double(pAR, pAPR)); 2453 } 2454 if (isMain == mainlineB) { 2455 g2.draw(new Line2D.Double(pML, pBL)); 2456 g2.draw(new Line2D.Double(pF, pBR)); 2457 if (getContinuingSense() == state) { // unknown or diverting path 2458// g2.draw(new Line2D.Double(pSR, pFPR)); 2459// } else { 2460 g2.draw(new Line2D.Double(pAPR, pF)); 2461 } 2462 } 2463 if (isMain == mainlineC) { 2464 g2.draw(new Line2D.Double(pF, pCL)); 2465 g2.draw(new Line2D.Double(pFR, pCR)); 2466 GeneralPath path = new GeneralPath(); 2467 path.moveTo(pAPR.getX(), pAPR.getY()); 2468 path.quadTo(pMR.getX(), pMR.getY(), pFR.getX(), pFR.getY()); 2469 path.lineTo(pCR.getX(), pCR.getY()); 2470 g2.draw(path); 2471 if (getContinuingSense() != state) { // unknown or diverting path 2472 path = new GeneralPath(); 2473 path.moveTo(pAPL.getX(), pAPL.getY()); 2474 path.quadTo(pML.getX(), pML.getY(), pF.getX(), pF.getY()); 2475 g2.draw(path); 2476// } else { 2477// path = new GeneralPath(); 2478// path.moveTo(pSL.getX(), pSL.getY()); 2479// path.quadTo(pML.getX(), pML.getY(), pFPL.getX(), pFPL.getY()); 2480// g2.draw(path); 2481 } 2482 } 2483 break; 2484 } // case RH_TURNOUT 2485 2486 case LH_TURNOUT: { 2487 if (isMain == mainlineA) { 2488 g2.draw(new Line2D.Double(pAR, pMR)); 2489 g2.draw(new Line2D.Double(pAL, pAPL)); 2490 } 2491 if (isMain == mainlineB) { 2492 g2.draw(new Line2D.Double(pMR, pBR)); 2493 g2.draw(new Line2D.Double(pF, pBL)); 2494 if (getContinuingSense() == state) { // straight path 2495// g2.draw(new Line2D.Double(pSL, pFPL)); Offset problem 2496// } else { 2497 g2.draw(new Line2D.Double(pAPL, pF)); 2498 } 2499 } 2500 if (isMain == mainlineC) { 2501 g2.draw(new Line2D.Double(pF, pCR)); 2502 GeneralPath path = new GeneralPath(); 2503 path.moveTo(pAPL.getX(), pAPL.getY()); 2504 path.quadTo(pML.getX(), pML.getY(), pFL.getX(), pFL.getY()); 2505 path.lineTo(pCL.getX(), pCL.getY()); 2506 g2.draw(path); 2507 if (getContinuingSense() != state) { // unknown or diverting path 2508 path = new GeneralPath(); 2509 path.moveTo(pAPR.getX(), pAPR.getY()); 2510 path.quadTo(pMR.getX(), pMR.getY(), pF.getX(), pF.getY()); 2511 g2.draw(path); 2512// } else { 2513// path = new GeneralPath(); 2514// path.moveTo(pSR.getX(), pSR.getY()); 2515// path.quadTo(pMR.getX(), pMR.getY(), pFPR.getX(), pFPR.getY()); 2516// g2.draw(path); 2517 } 2518 } 2519 break; 2520 } // case LH_TURNOUT 2521 2522 case WYE_TURNOUT: { 2523 if (isMain == mainlineA) { 2524 g2.draw(new Line2D.Double(pAL, pAPL)); 2525 g2.draw(new Line2D.Double(pAR, pAPR)); 2526 } 2527 if (isMain == mainlineB) { 2528 g2.draw(new Line2D.Double(pF, pBL)); 2529 GeneralPath path = new GeneralPath(); 2530 path.moveTo(pAPR.getX(), pAPR.getY()); 2531 path.quadTo(pMR.getX(), pMR.getY(), pFR.getX(), pFR.getY()); 2532 path.lineTo(pBR.getX(), pBR.getY()); 2533 g2.draw(path); 2534 if (getContinuingSense() != state) { // unknown or diverting path 2535 path = new GeneralPath(); 2536 path.moveTo(pAPR.getX(), pAPR.getY()); 2537 path.quadTo(pMR.getX(), pMR.getY(), pF.getX(), pF.getY()); 2538 g2.draw(path); 2539// } else { 2540// path = new GeneralPath(); 2541// path.moveTo(pSR.getX(), pSR.getY()); 2542// path.quadTo(pMR.getX(), pMR.getY(), pFPR.getX(), pFPR.getY()); 2543// bad g2.draw(path); 2544 } 2545 } 2546 if (isMain == mainlineC) { 2547 pML = MathUtil.subtract(pM, vCMo); 2548 GeneralPath path = new GeneralPath(); 2549 path.moveTo(pAPL.getX(), pAPL.getY()); 2550 path.quadTo(pML.getX(), pML.getY(), pFL.getX(), pFL.getY()); 2551 path.lineTo(pCL.getX(), pCL.getY()); 2552 g2.draw(path); 2553 g2.draw(new Line2D.Double(pF, pCR)); 2554 if (getContinuingSense() != state) { // unknown or diverting path 2555// path = new GeneralPath(); 2556// path.moveTo(pSL.getX(), pSL.getY()); 2557// path.quadTo(pML.getX(), pML.getY(), pFPL.getX(), pFPL.getY()); 2558// bad g2.draw(path); 2559 } else { 2560 path = new GeneralPath(); 2561 path.moveTo(pAPL.getX(), pAPL.getY()); 2562 path.quadTo(pML.getX(), pML.getY(), pF.getX(), pF.getY()); 2563 g2.draw(path); 2564 } 2565 } 2566 break; 2567 } // case WYE_TURNOUT 2568 2569 case DOUBLE_XOVER: { 2570 // A, B, C, D end points (left and right) 2571 Point2D vAB = MathUtil.normalize(MathUtil.subtract(pB, pA), railDisplacement); 2572 double dirAB_DEG = MathUtil.computeAngleDEG(vAB); 2573 Point2D vABo = MathUtil.orthogonal(MathUtil.normalize(vAB, railDisplacement)); 2574 pAL = MathUtil.subtract(pA, vABo); 2575 pAR = MathUtil.add(pA, vABo); 2576 pBL = MathUtil.subtract(pB, vABo); 2577 pBR = MathUtil.add(pB, vABo); 2578 Point2D vCD = MathUtil.normalize(MathUtil.subtract(pD, pC), railDisplacement); 2579 Point2D vCDo = MathUtil.orthogonal(MathUtil.normalize(vCD, railDisplacement)); 2580 pCL = MathUtil.add(pC, vCDo); 2581 pCR = MathUtil.subtract(pC, vCDo); 2582 Point2D pDL = MathUtil.add(pD, vCDo); 2583 Point2D pDR = MathUtil.subtract(pD, vCDo); 2584 2585 // AB, CD mid points (left and right) 2586 Point2D pABM = MathUtil.midPoint(pA, pB); 2587 Point2D pABL = MathUtil.midPoint(pAL, pBL); 2588 Point2D pABR = MathUtil.midPoint(pAR, pBR); 2589 Point2D pCDM = MathUtil.midPoint(pC, pD); 2590 Point2D pCDL = MathUtil.midPoint(pCL, pDL); 2591 Point2D pCDR = MathUtil.midPoint(pCR, pDR); 2592 2593 // A, B, C, D mid points 2594 double halfParallelDistance = MathUtil.distance(pABM, pCDM) / 2.0; 2595 Point2D pAM = MathUtil.subtract(pABM, MathUtil.normalize(vAB, halfParallelDistance)); 2596 Point2D pAML = MathUtil.subtract(pAM, vABo); 2597 Point2D pAMR = MathUtil.add(pAM, vABo); 2598 Point2D pBM = MathUtil.add(pABM, MathUtil.normalize(vAB, halfParallelDistance)); 2599 Point2D pBML = MathUtil.subtract(pBM, vABo); 2600 Point2D pBMR = MathUtil.add(pBM, vABo); 2601 Point2D pCM = MathUtil.subtract(pCDM, MathUtil.normalize(vCD, halfParallelDistance)); 2602 Point2D pCML = MathUtil.subtract(pCM, vABo); 2603 Point2D pCMR = MathUtil.add(pCM, vABo); 2604 Point2D pDM = MathUtil.add(pCDM, MathUtil.normalize(vCD, halfParallelDistance)); 2605 Point2D pDML = MathUtil.subtract(pDM, vABo); 2606 Point2D pDMR = MathUtil.add(pDM, vABo); 2607 2608 // crossing points 2609 Point2D vACM = MathUtil.normalize(MathUtil.subtract(pCM, pAM), railDisplacement); 2610 Point2D vACMo = MathUtil.orthogonal(vACM); 2611 Point2D vBDM = MathUtil.normalize(MathUtil.subtract(pDM, pBM), railDisplacement); 2612 Point2D vBDMo = MathUtil.orthogonal(vBDM); 2613 Point2D pBDR = MathUtil.add(pM, vACM); 2614 Point2D pBDL = MathUtil.subtract(pM, vACM); 2615 2616 // crossing diamond point (no gaps) 2617 Point2D pVR = MathUtil.add(pBDL, vBDM); 2618 Point2D pKL = MathUtil.subtract(pBDL, vBDM); 2619 Point2D pKR = MathUtil.add(pBDR, vBDM); 2620 Point2D pVL = MathUtil.subtract(pBDR, vBDM); 2621 2622 // crossing diamond points (with gaps) 2623 Point2D vACM2 = MathUtil.normalize(vACM, 2.0); 2624 Point2D vBDM2 = MathUtil.normalize(vBDM, 2.0); 2625 // (syntax of "pKLtC" is "point LK toward C", etc.) 2626 Point2D pKLtC = MathUtil.add(pKL, vACM2); 2627 Point2D pKLtD = MathUtil.add(pKL, vBDM2); 2628 Point2D pVLtA = MathUtil.subtract(pVL, vACM2); 2629 Point2D pVLtD = MathUtil.add(pVL, vBDM2); 2630 Point2D pKRtA = MathUtil.subtract(pKR, vACM2); 2631 Point2D pKRtB = MathUtil.subtract(pKR, vBDM2); 2632 Point2D pVRtB = MathUtil.subtract(pVR, vBDM2); 2633 Point2D pVRtC = MathUtil.add(pVR, vACM2); 2634 2635 // A, B, C, D frog points 2636 vCM = MathUtil.normalize(MathUtil.subtract(pCM, pM)); 2637 dirCM_DEG = MathUtil.computeAngleDEG(vCM); 2638 double deltaBAC_DEG = MathUtil.absDiffAngleDEG(dirAB_DEG, dirCM_DEG); 2639 double deltaBAC_RAD = Math.toRadians(deltaBAC_DEG); 2640 hypotF = railDisplacement / Math.sin(deltaBAC_RAD / 2.0); 2641 Point2D vACF = MathUtil.normalize(MathUtil.add(vACM, vAB), hypotF); 2642 Point2D pAFL = MathUtil.add(pAM, vACF); 2643 Point2D pCFR = MathUtil.subtract(pCM, vACF); 2644 Point2D vBDF = MathUtil.normalize(MathUtil.add(vBDM, vCD), hypotF); 2645 Point2D pBFL = MathUtil.add(pBM, vBDF); 2646 Point2D pDFR = MathUtil.subtract(pDM, vBDF); 2647 2648 // A, B, C, D frog points 2649 Point2D pAFR = MathUtil.add(MathUtil.add(pAFL, vACMo), vACMo); 2650 Point2D pBFR = MathUtil.subtract(MathUtil.subtract(pBFL, vBDMo), vBDMo); 2651 Point2D pCFL = MathUtil.subtract(MathUtil.subtract(pCFR, vACMo), vACMo); 2652 Point2D pDFL = MathUtil.add(MathUtil.add(pDFR, vBDMo), vBDMo); 2653 2654 // end of switch rails (closed) 2655 Point2D vABF = MathUtil.normalize(vAB, hypotF); 2656 pAP = MathUtil.subtract(pAM, vABF); 2657 pAPL = MathUtil.subtract(pAP, vABo); 2658 pAPR = MathUtil.add(pAP, vABo); 2659 Point2D pBP = MathUtil.add(pBM, vABF); 2660 Point2D pBPL = MathUtil.subtract(pBP, vABo); 2661 Point2D pBPR = MathUtil.add(pBP, vABo); 2662 2663 Point2D vCDF = MathUtil.normalize(vCD, hypotF); 2664 Point2D pCP = MathUtil.subtract(pCM, vCDF); 2665 Point2D pCPL = MathUtil.add(pCP, vCDo); 2666 Point2D pCPR = MathUtil.subtract(pCP, vCDo); 2667 Point2D pDP = MathUtil.add(pDM, vCDF); 2668 Point2D pDPL = MathUtil.add(pDP, vCDo); 2669 Point2D pDPR = MathUtil.subtract(pDP, vCDo); 2670 2671 // end of switch rails (open) 2672 Point2D vS = MathUtil.normalize(vABo, 2.0); 2673 Point2D pASL = MathUtil.add(pAPL, vS); 2674 // Point2D pASR = MathUtil.subtract(pAPR, vS); 2675 Point2D pBSL = MathUtil.add(pBPL, vS); 2676 // Point2D pBSR = MathUtil.subtract(pBPR, vS); 2677 Point2D pCSR = MathUtil.subtract(pCPR, vS); 2678 // Point2D pCSL = MathUtil.add(pCPL, vS); 2679 Point2D pDSR = MathUtil.subtract(pDPR, vS); 2680 // Point2D pDSL = MathUtil.add(pDPL, vS); 2681 2682 // end of switch rails (open at frogs) 2683 Point2D pAFS = MathUtil.subtract(pAFL, vS); 2684 Point2D pBFS = MathUtil.subtract(pBFL, vS); 2685 Point2D pCFS = MathUtil.add(pCFR, vS); 2686 Point2D pDFS = MathUtil.add(pDFR, vS); 2687 2688 // vSo = MathUtil.orthogonal(vS); 2689 // Point2D pAFSR = MathUtil.add(pAFL, vSo); 2690 // Point2D pBFSR = MathUtil.subtract(pBFL, vSo); 2691 // Point2D pCFSL = MathUtil.subtract(pCFR, vSo); 2692 // Point2D pDFSL = MathUtil.add(pDFR, vSo); 2693 if (isMain == mainlineA) { 2694 g2.draw(new Line2D.Double(pAL, pABL)); 2695 g2.draw(new Line2D.Double(pVRtB, pKLtD)); 2696 g2.draw(new Line2D.Double(pAFL, pABR)); 2697 g2.draw(new Line2D.Double(pAFL, pKL)); 2698 GeneralPath path = new GeneralPath(); 2699 path.moveTo(pAR.getX(), pAR.getY()); 2700 path.lineTo(pAPR.getX(), pAPR.getY()); 2701 path.quadTo(pAMR.getX(), pAMR.getY(), pAFR.getX(), pAFR.getY()); 2702 path.lineTo(pVR.getX(), pVR.getY()); 2703 g2.draw(path); 2704 if (state != Turnout.CLOSED) { // unknown or diverting path 2705 path = new GeneralPath(); 2706 path.moveTo(pAPL.getX(), pAPL.getY()); 2707 path.quadTo(pAML.getX(), pAML.getY(), pAFL.getX(), pAFL.getY()); 2708 g2.draw(path); 2709// g2.draw(new Line2D.Double(pASR, pAFSR)); 2710 } else { // continuing path 2711 g2.draw(new Line2D.Double(pAPR, pAFL)); 2712 path = new GeneralPath(); 2713 path.moveTo(pASL.getX(), pASL.getY()); 2714 path.quadTo(pAML.getX(), pAML.getY(), pAFS.getX(), pAFS.getY()); 2715// g2.draw(path); 2716 } 2717 } 2718 if (isMain == mainlineB) { 2719 g2.draw(new Line2D.Double(pABL, pBL)); 2720 g2.draw(new Line2D.Double(pKLtC, pVLtA)); 2721 g2.draw(new Line2D.Double(pBFL, pABR)); 2722 g2.draw(new Line2D.Double(pBFL, pKL)); 2723 GeneralPath path = new GeneralPath(); 2724 path.moveTo(pBR.getX(), pBR.getY()); 2725 path.lineTo(pBPR.getX(), pBPR.getY()); 2726 path.quadTo(pBMR.getX(), pBMR.getY(), pBFR.getX(), pBFR.getY()); 2727 path.lineTo(pVL.getX(), pVL.getY()); 2728 g2.draw(path); 2729 if (state != Turnout.CLOSED) { // unknown or diverting path 2730 path = new GeneralPath(); 2731 path.moveTo(pBPL.getX(), pBPL.getY()); 2732 path.quadTo(pBML.getX(), pBML.getY(), pBFL.getX(), pBFL.getY()); 2733 g2.draw(path); 2734// g2.draw(new Line2D.Double(pBSR, pBFSR)); 2735 } else { 2736 g2.draw(new Line2D.Double(pBPR, pBFL)); 2737 path = new GeneralPath(); 2738 path.moveTo(pBSL.getX(), pBSL.getY()); 2739 path.quadTo(pBML.getX(), pBML.getY(), pBFS.getX(), pBFS.getY()); 2740// g2.draw(path); 2741 } 2742 } 2743 if (isMain == mainlineC) { 2744 g2.draw(new Line2D.Double(pCR, pCDR)); 2745 g2.draw(new Line2D.Double(pKRtB, pVLtD)); 2746 g2.draw(new Line2D.Double(pCFR, pCDL)); 2747 g2.draw(new Line2D.Double(pCFR, pKR)); 2748 GeneralPath path = new GeneralPath(); 2749 path.moveTo(pCL.getX(), pCL.getY()); 2750 path.lineTo(pCPL.getX(), pCPL.getY()); 2751 path.quadTo(pCML.getX(), pCML.getY(), pCFL.getX(), pCFL.getY()); 2752 path.lineTo(pVL.getX(), pVL.getY()); 2753 g2.draw(path); 2754 if (state != Turnout.CLOSED) { // unknown or diverting path 2755 path = new GeneralPath(); 2756 path.moveTo(pCPR.getX(), pCPR.getY()); 2757 path.quadTo(pCMR.getX(), pCMR.getY(), pCFR.getX(), pCFR.getY()); 2758 g2.draw(path); 2759// g2.draw(new Line2D.Double(pCSL, pCFSL)); 2760 } else { 2761 g2.draw(new Line2D.Double(pCPL, pCFR)); 2762 path = new GeneralPath(); 2763 path.moveTo(pCSR.getX(), pCSR.getY()); 2764 path.quadTo(pCMR.getX(), pCMR.getY(), pCFS.getX(), pCFS.getY()); 2765// g2.draw(path); 2766 } 2767 } 2768 if (isMain == mainlineD) { 2769 g2.draw(new Line2D.Double(pCDR, pDR)); 2770 g2.draw(new Line2D.Double(pKRtA, pVRtC)); 2771 g2.draw(new Line2D.Double(pDFR, pCDL)); 2772 g2.draw(new Line2D.Double(pDFR, pKR)); 2773 GeneralPath path = new GeneralPath(); 2774 path.moveTo(pDL.getX(), pDL.getY()); 2775 path.lineTo(pDPL.getX(), pDPL.getY()); 2776 path.quadTo(pDML.getX(), pDML.getY(), pDFL.getX(), pDFL.getY()); 2777 path.lineTo(pVR.getX(), pVR.getY()); 2778 g2.draw(path); 2779 if (state != Turnout.CLOSED) { // unknown or diverting path 2780 path = new GeneralPath(); 2781 path.moveTo(pDPR.getX(), pDPR.getY()); 2782 path.quadTo(pDMR.getX(), pDMR.getY(), pDFR.getX(), pDFR.getY()); 2783 g2.draw(path); 2784// g2.draw(new Line2D.Double(pDSL, pDFSL)); 2785 } else { 2786 g2.draw(new Line2D.Double(pDPL, pDFR)); 2787 path = new GeneralPath(); 2788 path.moveTo(pDSR.getX(), pDSR.getY()); 2789 path.quadTo(pDMR.getX(), pDMR.getY(), pDFS.getX(), pDFS.getY()); 2790// g2.draw(path); 2791 } 2792 } 2793 break; 2794 } // case DOUBLE_XOVER 2795 2796 case RH_XOVER: { 2797 // A, B, C, D end points (left and right) 2798 Point2D vAB = MathUtil.normalize(MathUtil.subtract(pB, pA), railDisplacement); 2799 double dirAB_DEG = MathUtil.computeAngleDEG(vAB); 2800 Point2D vABo = MathUtil.orthogonal(MathUtil.normalize(vAB, railDisplacement)); 2801 pAL = MathUtil.subtract(pA, vABo); 2802 pAR = MathUtil.add(pA, vABo); 2803 pBL = MathUtil.subtract(pB, vABo); 2804 pBR = MathUtil.add(pB, vABo); 2805 Point2D vCD = MathUtil.normalize(MathUtil.subtract(pD, pC), railDisplacement); 2806 Point2D vCDo = MathUtil.orthogonal(MathUtil.normalize(vCD, railDisplacement)); 2807 pCL = MathUtil.add(pC, vCDo); 2808 pCR = MathUtil.subtract(pC, vCDo); 2809 Point2D pDL = MathUtil.add(pD, vCDo); 2810 Point2D pDR = MathUtil.subtract(pD, vCDo); 2811 2812 // AB and CD mid points 2813 Point2D pABM = MathUtil.midPoint(pA, pB); 2814 Point2D pABL = MathUtil.subtract(pABM, vABo); 2815 Point2D pABR = MathUtil.add(pABM, vABo); 2816 Point2D pCDM = MathUtil.midPoint(pC, pD); 2817 Point2D pCDL = MathUtil.subtract(pCDM, vABo); 2818 Point2D pCDR = MathUtil.add(pCDM, vABo); 2819 2820 // directions 2821 Point2D vAC = MathUtil.normalize(MathUtil.subtract(pCDM, pABM), railDisplacement); 2822 Point2D vACo = MathUtil.orthogonal(MathUtil.normalize(vAC, railDisplacement)); 2823 double dirAC_DEG = MathUtil.computeAngleDEG(vAC); 2824 double deltaBAC_DEG = MathUtil.absDiffAngleDEG(dirAB_DEG, dirAC_DEG); 2825 double deltaBAC_RAD = Math.toRadians(deltaBAC_DEG); 2826 2827 // AC mid points 2828 Point2D pACL = MathUtil.subtract(pM, vACo); 2829 Point2D pACR = MathUtil.add(pM, vACo); 2830 2831 // frogs 2832 hypotF = railDisplacement / Math.sin(deltaBAC_RAD / 2.0); 2833 Point2D vF = MathUtil.normalize(MathUtil.add(vAB, vAC), hypotF); 2834 Point2D pABF = MathUtil.add(pABM, vF); 2835 Point2D pCDF = MathUtil.subtract(pCDM, vF); 2836 2837 // frog primes 2838 Point2D pABFP = MathUtil.add(MathUtil.add(pABF, vACo), vACo); 2839 Point2D pCDFP = MathUtil.subtract(MathUtil.subtract(pCDF, vACo), vACo); 2840 2841 // end of switch rails (closed) 2842 Point2D vABF = MathUtil.normalize(vAB, hypotF); 2843 pAP = MathUtil.subtract(pABM, vABF); 2844 pAPL = MathUtil.subtract(pAP, vABo); 2845 pAPR = MathUtil.add(pAP, vABo); 2846 Point2D pCP = MathUtil.add(pCDM, vABF); 2847 Point2D pCPL = MathUtil.add(pCP, vCDo); 2848 Point2D pCPR = MathUtil.subtract(pCP, vCDo); 2849 2850 // end of switch rails (open) 2851 Point2D vS = MathUtil.normalize(vAB, 2.0); 2852 Point2D vSo = MathUtil.orthogonal(vS); 2853 Point2D pASL = MathUtil.add(pAPL, vSo); 2854 // Point2D pASR = MathUtil.subtract(pAPR, vSo); 2855 // Point2D pCSL = MathUtil.add(pCPL, vSo); 2856 Point2D pCSR = MathUtil.subtract(pCPR, vSo); 2857 2858 // end of switch rails (open at frogs) 2859 Point2D pABFS = MathUtil.subtract(pABF, vSo); 2860 // Point2D pABFSP = MathUtil.subtract(pABF, vS); 2861 Point2D pCDFS = MathUtil.add(pCDF, vSo); 2862 // Point2D pCDFSP = MathUtil.add(pCDF, vS); 2863 2864 if (isMain == mainlineA) { 2865 g2.draw(new Line2D.Double(pAL, pABL)); 2866 GeneralPath path = new GeneralPath(); 2867 path.moveTo(pAR.getX(), pAR.getY()); 2868 path.lineTo(pAPR.getX(), pAPR.getY()); 2869 path.quadTo(pABR.getX(), pABR.getY(), pABFP.getX(), pABFP.getY()); 2870 path.lineTo(pACR.getX(), pACR.getY()); 2871 g2.draw(path); 2872 g2.draw(new Line2D.Double(pABF, pACL)); 2873 if (state != Turnout.CLOSED) { // unknown or diverting path 2874 path = new GeneralPath(); 2875 path.moveTo(pAPL.getX(), pAPL.getY()); 2876 path.quadTo(pABL.getX(), pABL.getY(), pABF.getX(), pABF.getY()); 2877 g2.draw(path); 2878// g2.draw(new Line2D.Double(pASR, pABFSP)); 2879 } else { // continuing path 2880 g2.draw(new Line2D.Double(pAPR, pABF)); 2881 path = new GeneralPath(); 2882 path.moveTo(pASL.getX(), pASL.getY()); 2883 path.quadTo(pABL.getX(), pABL.getY(), pABFS.getX(), pABFS.getY()); 2884// g2.draw(path); 2885 } 2886 } 2887 if (isMain == mainlineB) { 2888 g2.draw(new Line2D.Double(pABL, pBL)); 2889 g2.draw(new Line2D.Double(pABF, pBR)); 2890 } 2891 if (isMain == mainlineC) { 2892 g2.draw(new Line2D.Double(pCR, pCDR)); 2893 GeneralPath path = new GeneralPath(); 2894 path.moveTo(pCL.getX(), pCL.getY()); 2895 path.lineTo(pCPL.getX(), pCPL.getY()); 2896 path.quadTo(pCDL.getX(), pCDL.getY(), pCDFP.getX(), pCDFP.getY()); 2897 path.lineTo(pACL.getX(), pACL.getY()); 2898 g2.draw(path); 2899 g2.draw(new Line2D.Double(pCDF, pACR)); 2900 if (state != Turnout.CLOSED) { // unknown or diverting path 2901 path = new GeneralPath(); 2902 path.moveTo(pCPR.getX(), pCPR.getY()); 2903 path.quadTo(pCDR.getX(), pCDR.getY(), pCDF.getX(), pCDF.getY()); 2904 g2.draw(path); 2905// g2.draw(new Line2D.Double(pCSL, pCDFSP)); 2906 } else { // continuing path 2907 g2.draw(new Line2D.Double(pCPL, pCDF)); 2908 path = new GeneralPath(); 2909 path.moveTo(pCSR.getX(), pCSR.getY()); 2910 path.quadTo(pCDR.getX(), pCDR.getY(), pCDFS.getX(), pCDFS.getY()); 2911// g2.draw(path); 2912 } 2913 } 2914 if (isMain == mainlineD) { 2915 g2.draw(new Line2D.Double(pCDR, pDR)); 2916 g2.draw(new Line2D.Double(pCDF, pDL)); 2917 } 2918 break; 2919 } // case RH_XOVER 2920 2921 case LH_XOVER: { 2922 // B, A, D, C end points (left and right) 2923 Point2D vBA = MathUtil.normalize(MathUtil.subtract(pA, pB), railDisplacement); 2924 double dirBA_DEG = MathUtil.computeAngleDEG(vBA); 2925 Point2D vBAo = MathUtil.orthogonal(MathUtil.normalize(vBA, railDisplacement)); 2926 pBL = MathUtil.add(pB, vBAo); 2927 pBR = MathUtil.subtract(pB, vBAo); 2928 pAL = MathUtil.add(pA, vBAo); 2929 pAR = MathUtil.subtract(pA, vBAo); 2930 Point2D vDC = MathUtil.normalize(MathUtil.subtract(pC, pD), railDisplacement); 2931 Point2D vDCo = MathUtil.orthogonal(MathUtil.normalize(vDC, railDisplacement)); 2932 Point2D pDL = MathUtil.subtract(pD, vDCo); 2933 Point2D pDR = MathUtil.add(pD, vDCo); 2934 pCL = MathUtil.subtract(pC, vDCo); 2935 pCR = MathUtil.add(pC, vDCo); 2936 2937 // BA and DC mid points 2938 Point2D pBAM = MathUtil.midPoint(pB, pA); 2939 Point2D pBAL = MathUtil.add(pBAM, vBAo); 2940 Point2D pBAR = MathUtil.subtract(pBAM, vBAo); 2941 Point2D pDCM = MathUtil.midPoint(pD, pC); 2942 Point2D pDCL = MathUtil.add(pDCM, vBAo); 2943 Point2D pDCR = MathUtil.subtract(pDCM, vBAo); 2944 2945 // directions 2946 Point2D vBD = MathUtil.normalize(MathUtil.subtract(pDCM, pBAM), railDisplacement); 2947 Point2D vBDo = MathUtil.orthogonal(MathUtil.normalize(vBD, railDisplacement)); 2948 double dirBD_DEG = MathUtil.computeAngleDEG(vBD); 2949 double deltaABD_DEG = MathUtil.absDiffAngleDEG(dirBA_DEG, dirBD_DEG); 2950 double deltaABD_RAD = Math.toRadians(deltaABD_DEG); 2951 2952 // BD mid points 2953 Point2D pBDL = MathUtil.add(pM, vBDo); 2954 Point2D pBDR = MathUtil.subtract(pM, vBDo); 2955 2956 // frogs 2957 hypotF = railDisplacement / Math.sin(deltaABD_RAD / 2.0); 2958 Point2D vF = MathUtil.normalize(MathUtil.add(vBA, vBD), hypotF); 2959 Point2D pBFL = MathUtil.add(pBAM, vF); 2960 Point2D pBF = MathUtil.subtract(pBFL, vBDo); 2961 Point2D pBFR = MathUtil.subtract(pBF, vBDo); 2962 Point2D pDFR = MathUtil.subtract(pDCM, vF); 2963 Point2D pDF = MathUtil.add(pDFR, vBDo); 2964 Point2D pDFL = MathUtil.add(pDF, vBDo); 2965 2966 // end of switch rails (closed) 2967 Point2D vBAF = MathUtil.normalize(vBA, hypotF); 2968 Point2D pBP = MathUtil.subtract(pBAM, vBAF); 2969 Point2D pBPL = MathUtil.add(pBP, vBAo); 2970 Point2D pBPR = MathUtil.subtract(pBP, vBAo); 2971 Point2D pDP = MathUtil.add(pDCM, vBAF); 2972 Point2D pDPL = MathUtil.subtract(pDP, vDCo); 2973 Point2D pDPR = MathUtil.add(pDP, vDCo); 2974 2975 // end of switch rails (open) 2976 Point2D vS = MathUtil.normalize(vBA, 2.0); 2977 Point2D vSo = MathUtil.orthogonal(vS); 2978 Point2D pBSL = MathUtil.subtract(pBPL, vSo); 2979 // Point2D pBSR = MathUtil.add(pBPR, vSo); 2980 // Point2D pDSL = MathUtil.subtract(pDPL, vSo); 2981 Point2D pDSR = MathUtil.add(pDPR, vSo); 2982 2983 // end of switch rails (open at frogs) 2984 Point2D pBAFS = MathUtil.add(pBFL, vSo); 2985 // Point2D pBAFSP = MathUtil.subtract(pBFL, vS); 2986 Point2D pDCFS = MathUtil.subtract(pDFR, vSo); 2987 // Point2D pDCFSP = MathUtil.add(pDFR, vS); 2988 2989 if (isMain == mainlineA) { 2990 g2.draw(new Line2D.Double(pBAL, pAL)); 2991 g2.draw(new Line2D.Double(pBFL, pAR)); 2992 } 2993 if (isMain == mainlineB) { 2994 g2.draw(new Line2D.Double(pBL, pBAL)); 2995 GeneralPath path = new GeneralPath(); 2996 path.moveTo(pBR.getX(), pBR.getY()); 2997 path.lineTo(pBPR.getX(), pBPR.getY()); 2998 path.quadTo(pBAR.getX(), pBAR.getY(), pBFR.getX(), pBFR.getY()); 2999 path.lineTo(pBDR.getX(), pBDR.getY()); 3000 g2.draw(path); 3001 g2.draw(new Line2D.Double(pBFL, pBDL)); 3002 if (state != Turnout.CLOSED) { // unknown or diverting path 3003 path = new GeneralPath(); 3004 path.moveTo(pBPL.getX(), pBPL.getY()); 3005 path.quadTo(pBAL.getX(), pBAL.getY(), pBFL.getX(), pBFL.getY()); 3006 g2.draw(path); 3007// g2.draw(new Line2D.Double(pBSR, pBAFSP)); 3008 } else { // continuing path 3009 g2.draw(new Line2D.Double(pBPR, pBFL)); 3010 path = new GeneralPath(); 3011 path.moveTo(pBSL.getX(), pBSL.getY()); 3012 path.quadTo(pBAL.getX(), pBAL.getY(), pBAFS.getX(), pBAFS.getY()); 3013// g2.draw(path); 3014 } 3015 } 3016 if (isMain == mainlineC) { 3017 g2.draw(new Line2D.Double(pDCR, pCR)); 3018 g2.draw(new Line2D.Double(pDFR, pCL)); 3019 } 3020 if (isMain == mainlineD) { 3021 g2.draw(new Line2D.Double(pDR, pDCR)); 3022 GeneralPath path = new GeneralPath(); 3023 path.moveTo(pDL.getX(), pDL.getY()); 3024 path.lineTo(pDPL.getX(), pDPL.getY()); 3025 path.quadTo(pDCL.getX(), pDCL.getY(), pDFL.getX(), pDFL.getY()); 3026 path.lineTo(pBDL.getX(), pBDL.getY()); 3027 g2.draw(path); 3028 g2.draw(new Line2D.Double(pDFR, pBDR)); 3029 if (state != Turnout.CLOSED) { // unknown or diverting path 3030 path = new GeneralPath(); 3031 path.moveTo(pDPR.getX(), pDPR.getY()); 3032 path.quadTo(pDCR.getX(), pDCR.getY(), pDFR.getX(), pDFR.getY()); 3033 g2.draw(path); 3034// g2.draw(new Line2D.Double(pDSL, pDCFSP)); 3035 } else { // continuing path 3036 g2.draw(new Line2D.Double(pDPL, pDFR)); 3037 path = new GeneralPath(); 3038 path.moveTo(pDSR.getX(), pDSR.getY()); 3039 path.quadTo(pDCR.getX(), pDCR.getY(), pDCFS.getX(), pDCFS.getY()); 3040// g2.draw(path); 3041 } 3042 } 3043 break; 3044 } // case LH_XOVER 3045 case SINGLE_SLIP: 3046 case DOUBLE_SLIP: { 3047 log.error("{}.draw2(...); slips should be being drawn by LayoutSlip sub-class", getName()); 3048 break; 3049 } 3050 default: { 3051 // this should never happen... but... 3052 log.error("{}.draw2(...); Unknown turnout type {}", getName(), type); 3053 break; 3054 } 3055 } 3056 } // draw2 3057 3058 /** 3059 * {@inheritDoc} 3060 */ 3061 @Override 3062 protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) { 3063 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_A)) 3064 && (getConnectA() == null)) { 3065 g2.fill(trackControlCircleAt(getCoordsA())); 3066 } 3067 3068 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_B)) 3069 && (getConnectB() == null)) { 3070 g2.fill(trackControlCircleAt(getCoordsB())); 3071 } 3072 3073 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_C)) 3074 && (getConnectC() == null)) { 3075 g2.fill(trackControlCircleAt(getCoordsC())); 3076 } 3077 if (isTurnoutTypeXover()) { 3078 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_D)) 3079 && (getConnectD() == null)) { 3080 g2.fill(trackControlCircleAt(getCoordsD())); 3081 } 3082 } 3083 } 3084 3085 /** 3086 * {@inheritDoc} 3087 */ 3088 @Override 3089 protected void drawTurnoutControls(Graphics2D g2) { 3090 if (!isDisabled() && !(isDisabledWhenOccupied() && isOccupied())) { 3091 Color foregroundColor = g2.getColor(); 3092 3093 if (getState() != Turnout.CLOSED) { 3094 // then switch to background (thrown) color 3095 g2.setColor(g2.getBackground()); 3096 } 3097 3098 if (layoutEditor.isTurnoutFillControlCircles()) { 3099 g2.fill(trackControlCircleAt(getCoordsCenter())); 3100 // do we need to draw a ? for unknown over the circle? 3101 if (showUnknown && getState() == UNKNOWN) { 3102 drawForShowUnknown(g2, getCoordsCenter(), g2.getBackground(), true); 3103 return; 3104 } 3105 } else { 3106 g2.draw(trackControlCircleAt(getCoordsCenter())); 3107 } 3108 3109 if (getState() != Turnout.CLOSED) { 3110 // then restore foreground color 3111 g2.setColor(foregroundColor); 3112 } 3113 3114 3115 } 3116 } 3117 3118 /** 3119 * {@inheritDoc} 3120 */ 3121 @Override 3122 protected void drawEditControls(Graphics2D g2) { 3123 Point2D pt = getCoordsA(); 3124 if (isTurnoutTypeXover() || isTurnoutTypeSlip()) { 3125 if (getConnectA() == null) { 3126 g2.setColor(Color.magenta); 3127 } else { 3128 g2.setColor(Color.blue); 3129 } 3130 } else { 3131 if (getConnectA() == null) { 3132 g2.setColor(Color.red); 3133 } else { 3134 g2.setColor(Color.green); 3135 } 3136 } 3137 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3138 3139 pt = getCoordsB(); 3140 if (getConnectB() == null) { 3141 g2.setColor(Color.red); 3142 } else { 3143 g2.setColor(Color.green); 3144 } 3145 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3146 3147 pt = getCoordsC(); 3148 if (getConnectC() == null) { 3149 g2.setColor(Color.red); 3150 } else { 3151 g2.setColor(Color.green); 3152 } 3153 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3154 3155 if (isTurnoutTypeXover() || isTurnoutTypeSlip()) { 3156 pt = getCoordsD(); 3157 if (getConnectD() == null) { 3158 g2.setColor(Color.red); 3159 } else { 3160 g2.setColor(Color.green); 3161 } 3162 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3163 } 3164 } 3165 3166 /* 3167 * Used by ConnectivityUtil to determine the turnout state necessary to get 3168 * from prevLayoutBlock ==> currLayoutBlock ==> nextLayoutBlock 3169 */ 3170 protected int getConnectivityStateForLayoutBlocks( 3171 LayoutBlock currLayoutBlock, 3172 LayoutBlock prevLayoutBlock, 3173 LayoutBlock nextLayoutBlock, 3174 boolean suppress) { 3175 3176 return turnout.getConnectivityStateForLayoutBlocks(currLayoutBlock, 3177 prevLayoutBlock, 3178 nextLayoutBlock, 3179 suppress); 3180 } 3181 3182 /** 3183 * {@inheritDoc} 3184 */ 3185 // TODO: on the cross-overs, check the internal boundary details. 3186 @Override 3187 public void reCheckBlockBoundary() { 3188 3189 turnout.reCheckBlockBoundary(); 3190 3191 } 3192 3193 /** 3194 * {@inheritDoc} 3195 */ 3196 @Override 3197 protected List<LayoutConnectivity> getLayoutConnectivity() { 3198 return turnout.getLayoutConnectivity(); 3199 } 3200 3201 /** 3202 * {@inheritDoc} 3203 */ 3204 @Override 3205 public @Nonnull 3206 List<HitPointType> checkForFreeConnections() { 3207 return turnout.checkForFreeConnections(); 3208 } 3209 3210 /** 3211 * {@inheritDoc} 3212 */ 3213 @Override 3214 public boolean checkForUnAssignedBlocks() { 3215 // because getLayoutBlock[BCD] will return block [A] if they're null 3216 // we only need to test block [A] 3217 return turnout.checkForUnAssignedBlocks(); 3218 } 3219 3220 /** 3221 * {@inheritDoc} 3222 */ 3223 @Override 3224 public void checkForNonContiguousBlocks( 3225 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 3226 3227 turnout.checkForNonContiguousBlocks(blockNamesToTrackNameSetsMap); 3228 } 3229 3230 /** 3231 * {@inheritDoc} 3232 */ 3233 @Override 3234 public void collectContiguousTracksNamesInBlockNamed( 3235 @Nonnull String blockName, 3236 @Nonnull Set<String> TrackNameSet) { 3237 3238 turnout.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 3239 } 3240 3241 /** 3242 * {@inheritDoc} 3243 */ 3244 @Override 3245 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 3246 turnout.setAllLayoutBlocks(layoutBlock); 3247 } 3248 3249 private static class AbstractActionImpl extends AbstractAction { 3250 3251 private final String blockName; 3252 private final LayoutBlock layoutBlock; 3253 3254 AbstractActionImpl(String name, String blockName, LayoutBlock layoutBlock) { 3255 super(name); 3256 this.blockName = blockName; 3257 this.layoutBlock = layoutBlock; 3258 } 3259 3260 @Override 3261 public void actionPerformed(ActionEvent e) { 3262 AbstractAction routeTableAction = new LayoutBlockRouteTableAction(blockName, layoutBlock); 3263 routeTableAction.actionPerformed(e); 3264 } 3265 } 3266 3267 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurnoutView.class); 3268}