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 showUnknownCheckBoxMenuItem.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 if (type == TurnoutType.DOUBLE_XOVER) { 2059 if (state != Turnout.THROWN && state != INCONSISTENT) { // unknown or continuing path - not crossed over 2060 if (isMain == mainlineA) { 2061 g2.setColor(colorA); 2062 g2.draw(new Line2D.Double(pA, pABM)); 2063 if (!isBlock || drawUnselectedLeg) { 2064 g2.draw(new Line2D.Double(pAF, pM)); 2065 } 2066 } 2067 if (isMain == mainlineB) { 2068 g2.setColor(colorB); 2069 g2.draw(new Line2D.Double(pB, pABM)); 2070 if (!isBlock || drawUnselectedLeg) { 2071 g2.draw(new Line2D.Double(pBF, pM)); 2072 } 2073 } 2074 if (isMain == mainlineC) { 2075 g2.setColor(colorC); 2076 g2.draw(new Line2D.Double(pC, pCDM)); 2077 if (!isBlock || drawUnselectedLeg) { 2078 g2.draw(new Line2D.Double(pCF, pM)); 2079 } 2080 } 2081 if (isMain == mainlineD) { 2082 g2.setColor(colorD); 2083 g2.draw(new Line2D.Double(pD, pCDM)); 2084 if (!isBlock || drawUnselectedLeg) { 2085 g2.draw(new Line2D.Double(pDF, pM)); 2086 } 2087 } 2088 } 2089 if (state != Turnout.CLOSED && state != INCONSISTENT) { // unknown or diverting path - crossed over 2090 if (isMain == mainlineA) { 2091 g2.setColor(colorA); 2092 g2.draw(new Line2D.Double(pA, pAM)); 2093 g2.draw(new Line2D.Double(pAM, pM)); 2094 if (!isBlock || drawUnselectedLeg) { 2095 g2.draw(new Line2D.Double(pAMP, pABM)); 2096 } 2097 } 2098 if (isMain == mainlineB) { 2099 g2.setColor(colorB); 2100 g2.draw(new Line2D.Double(pB, pBM)); 2101 g2.draw(new Line2D.Double(pBM, pM)); 2102 if (!isBlock || drawUnselectedLeg) { 2103 g2.draw(new Line2D.Double(pBMP, pABM)); 2104 } 2105 } 2106 if (isMain == mainlineC) { 2107 g2.setColor(colorC); 2108 g2.draw(new Line2D.Double(pC, pCM)); 2109 g2.draw(new Line2D.Double(pCM, pM)); 2110 if (!isBlock || drawUnselectedLeg) { 2111 g2.draw(new Line2D.Double(pCMP, pCDM)); 2112 } 2113 } 2114 if (isMain == mainlineD) { 2115 g2.setColor(colorD); 2116 g2.draw(new Line2D.Double(pD, pDM)); 2117 g2.draw(new Line2D.Double(pDM, pM)); 2118 if (!isBlock || drawUnselectedLeg) { 2119 g2.draw(new Line2D.Double(pDMP, pCDM)); 2120 } 2121 } 2122 } 2123 if (state == INCONSISTENT) { 2124 if (isMain == mainlineA) { 2125 g2.setColor(colorA); 2126 g2.draw(new Line2D.Double(pA, pAM)); 2127 } 2128 if (isMain == mainlineB) { 2129 g2.setColor(colorB); 2130 g2.draw(new Line2D.Double(pB, pBM)); 2131 } 2132 if (isMain == mainlineC) { 2133 g2.setColor(colorC); 2134 g2.draw(new Line2D.Double(pC, pCM)); 2135 } 2136 if (isMain == mainlineD) { 2137 g2.setColor(colorD); 2138 g2.draw(new Line2D.Double(pD, pDM)); 2139 } 2140 if (!isBlock || drawUnselectedLeg) { 2141 if (isMain == mainlineA) { 2142 g2.setColor(colorA); 2143 g2.draw(new Line2D.Double(pAF, pM)); 2144 } 2145 if (isMain == mainlineC) { 2146 g2.setColor(colorC); 2147 g2.draw(new Line2D.Double(pCF, pM)); 2148 } 2149 if (isMain == mainlineB) { 2150 g2.setColor(colorB); 2151 g2.draw(new Line2D.Double(pBF, pM)); 2152 } 2153 if (isMain == mainlineD) { 2154 g2.setColor(colorD); 2155 g2.draw(new Line2D.Double(pDF, pM)); 2156 } 2157 } 2158 } 2159 } else if ((type == TurnoutType.RH_XOVER) 2160 || (type == TurnoutType.LH_XOVER)) { // draw (rh & lh) cross overs 2161 pAF = MathUtil.midPoint(pABM, pM); 2162 pBF = MathUtil.midPoint(pABM, pM); 2163 pCF = MathUtil.midPoint(pCDM, pM); 2164 pDF = MathUtil.midPoint(pCDM, pM); 2165 if (state != Turnout.THROWN && state != INCONSISTENT) { // unknown or continuing path - not crossed over 2166 if (isMain == mainlineA) { 2167 g2.setColor(colorA); 2168 g2.draw(new Line2D.Double(pA, pABM)); 2169 } 2170 if (isMain == mainlineB) { 2171 g2.setColor(colorB); 2172 g2.draw(new Line2D.Double(pABM, pB)); 2173 } 2174 if (isMain == mainlineC) { 2175 g2.setColor(colorC); 2176 g2.draw(new Line2D.Double(pC, pCDM)); 2177 } 2178 if (isMain == mainlineD) { 2179 g2.setColor(colorD); 2180 g2.draw(new Line2D.Double(pCDM, pD)); 2181 } 2182 if (!isBlock || drawUnselectedLeg) { 2183 if (getTurnoutType() == TurnoutType.RH_XOVER) { 2184 if (isMain == mainlineA) { 2185 g2.setColor(colorA); 2186 g2.draw(new Line2D.Double(pAF, pM)); 2187 } 2188 if (isMain == mainlineC) { 2189 g2.setColor(colorC); 2190 g2.draw(new Line2D.Double(pCF, pM)); 2191 } 2192 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 2193 if (isMain == mainlineB) { 2194 g2.setColor(colorB); 2195 g2.draw(new Line2D.Double(pBF, pM)); 2196 } 2197 if (isMain == mainlineD) { 2198 g2.setColor(colorD); 2199 g2.draw(new Line2D.Double(pDF, pM)); 2200 } 2201 } 2202 } 2203 } 2204 if (state != Turnout.CLOSED && state != INCONSISTENT) { // unknown or diverting path - crossed over 2205 if (getTurnoutType() == TurnoutType.RH_XOVER) { 2206 if (isMain == mainlineA) { 2207 g2.setColor(colorA); 2208 g2.draw(new Line2D.Double(pA, pABM)); 2209 g2.draw(new Line2D.Double(pABM, pM)); 2210 } 2211 if (!isBlock || drawUnselectedLeg) { 2212 if (isMain == mainlineB) { 2213 g2.setColor(colorB); 2214 g2.draw(new Line2D.Double(pBM, pB)); 2215 } 2216 } 2217 if (isMain == mainlineC) { 2218 g2.setColor(colorC); 2219 g2.draw(new Line2D.Double(pC, pCDM)); 2220 g2.draw(new Line2D.Double(pCDM, pM)); 2221 } 2222 if (!isBlock || drawUnselectedLeg) { 2223 if (isMain == mainlineD) { 2224 g2.setColor(colorD); 2225 g2.draw(new Line2D.Double(pDM, pD)); 2226 } 2227 } 2228 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 2229 if (!isBlock || drawUnselectedLeg) { 2230 if (isMain == mainlineA) { 2231 g2.setColor(colorA); 2232 g2.draw(new Line2D.Double(pA, pAM)); 2233 } 2234 } 2235 if (isMain == mainlineB) { 2236 g2.setColor(colorB); 2237 g2.draw(new Line2D.Double(pB, pABM)); 2238 g2.draw(new Line2D.Double(pABM, pM)); 2239 } 2240 if (!isBlock || drawUnselectedLeg) { 2241 if (isMain == mainlineC) { 2242 g2.setColor(colorC); 2243 g2.draw(new Line2D.Double(pC, pCM)); 2244 } 2245 } 2246 if (isMain == mainlineD) { 2247 g2.setColor(colorD); 2248 g2.draw(new Line2D.Double(pD, pCDM)); 2249 g2.draw(new Line2D.Double(pCDM, pM)); 2250 } 2251 } 2252 } 2253 if (state == INCONSISTENT) { 2254 if (isMain == mainlineA) { 2255 g2.setColor(colorA); 2256 g2.draw(new Line2D.Double(pA, pAM)); 2257 } 2258 if (isMain == mainlineB) { 2259 g2.setColor(colorB); 2260 g2.draw(new Line2D.Double(pB, pBM)); 2261 } 2262 if (isMain == mainlineC) { 2263 g2.setColor(colorC); 2264 g2.draw(new Line2D.Double(pC, pCM)); 2265 } 2266 if (isMain == mainlineD) { 2267 g2.setColor(colorD); 2268 g2.draw(new Line2D.Double(pD, pDM)); 2269 } 2270 if (!isBlock || drawUnselectedLeg) { 2271 if (getTurnoutType() == TurnoutType.RH_XOVER) { 2272 if (isMain == mainlineA) { 2273 g2.setColor(colorA); 2274 g2.draw(new Line2D.Double(pAF, pM)); 2275 } 2276 if (isMain == mainlineC) { 2277 g2.setColor(colorC); 2278 g2.draw(new Line2D.Double(pCF, pM)); 2279 } 2280 } else if (getTurnoutType() == TurnoutType.LH_XOVER) { 2281 if (isMain == mainlineB) { 2282 g2.setColor(colorB); 2283 g2.draw(new Line2D.Double(pBF, pM)); 2284 } 2285 if (isMain == mainlineD) { 2286 g2.setColor(colorD); 2287 g2.draw(new Line2D.Double(pDF, pM)); 2288 } 2289 } 2290 } 2291 } 2292 } else if (isTurnoutTypeSlip()) { 2293 log.error("{}.draw1(...); slips should be being drawn by LayoutSlip sub-class", getName()); 2294 } else { // LH, RH, or WYE Turnouts 2295 2296 // draw A<===>center 2297 if (isMain == mainlineA) { 2298 g2.setColor(colorA); 2299 g2.draw(new Line2D.Double(pA, pM)); 2300 } 2301 2302 if (state == UNKNOWN || (getContinuingSense() == state && state != INCONSISTENT)) { // unknown or continuing path 2303 // draw center<===>B 2304 if (isMain == mainlineB) { 2305 g2.setColor(colorB); 2306 g2.draw(new Line2D.Double(pM, pB)); 2307 } 2308 } else if (!isBlock || drawUnselectedLeg) { 2309 // draw center<--=>B 2310 if (isMain == mainlineB) { 2311 g2.setColor(colorB); 2312 g2.draw(new Line2D.Double(MathUtil.twoThirdsPoint(pM, pB), pB)); 2313 } 2314 } 2315 2316 if (state == UNKNOWN || (getContinuingSense() != state && state != INCONSISTENT)) { // unknown or diverting path 2317 // draw center<===>C 2318 if (isMain == mainlineC) { 2319 g2.setColor(colorC); 2320 g2.draw(new Line2D.Double(pM, pC)); 2321 } 2322 } else if (!isBlock || drawUnselectedLeg) { 2323 // draw center<--=>C 2324 if (isMain == mainlineC) { 2325 g2.setColor(colorC); 2326 g2.draw(new Line2D.Double(MathUtil.twoThirdsPoint(pM, pC), pC)); 2327 } 2328 } 2329 } 2330 2331 // Overwrite with "?" if UNKNOWN and showUnknown requesting 2332 if (showUnknown && state == UNKNOWN) { 2333 // Draw the circle over the track drawing 2334 //Color previousColor = g2.getColor(); 2335 //g2.setColor(g2.getBackground()); 2336 g2.fill(trackControlCircleAt(getCoordsCenter())); 2337 //g2.setColor(previousColor); 2338 2339 drawForShowUnknown(g2, pM, g2.getColor(), true); 2340 return; 2341 } 2342 } // draw1 2343 2344 /** 2345 * Draw a "?" for the UNKNOWN state. 2346 * 2347 * To be invoked if getShowUnknown() is true 2348 * 2349 * @param g2 the graphics context to draw in 2350 * @param center where to center the "?" 2351 * @param color the base color for drawing 2352 * @param complement true means select black or white to complement the base color 2353 */ 2354 private void drawForShowUnknown(Graphics2D g2, Point2D center, Color color, boolean complement) { 2355 var originalFont = g2.getFont(); 2356 var originalColor = g2.getColor(); 2357 2358 // convert color to HSV to get intensity 2359 int v = Math.max(Math.max(color.getBlue(), color.getGreen()), color.getRed()); 2360 2361 Color drawColor = color; 2362 2363 if (complement) { 2364 drawColor = Color.BLACK; 2365 if ( v < 255*0.5) { 2366 drawColor = Color.WHITE; 2367 } 2368 } 2369 2370 g2.setColor(drawColor); 2371 2372 int size = (int) layoutEditor.circleDiameter; 2373 2374 g2.setFont(new Font("SansSerif", Font.BOLD, size)); 2375 2376 var metrics = g2.getFontMetrics(); 2377 double x = center.getX() - ((double) metrics.charWidth('?'))/2; // - to move left 2378 double y = center.getY() + (metrics.getAscent()*0.9)/2; // + to move down 2379 2380 g2.drawString("?", (float) x, (float) y); 2381 2382 g2.setColor(originalColor); 2383 g2.setFont(originalFont); 2384 } 2385 2386 /** 2387 * {@inheritDoc} 2388 */ 2389 @Override 2390 protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) { 2391 TurnoutType type = getTurnoutType(); 2392 2393 Point2D pA = getCoordsA(); 2394 Point2D pB = getCoordsB(); 2395 Point2D pC = getCoordsC(); 2396 Point2D pD = getCoordsD(); 2397 Point2D pM = getCoordsCenter(); 2398 2399 Point2D vAM = MathUtil.normalize(MathUtil.subtract(pM, pA)); 2400 Point2D vAMo = MathUtil.orthogonal(MathUtil.normalize(vAM, railDisplacement)); 2401 2402 Point2D pAL = MathUtil.subtract(pA, vAMo); 2403 Point2D pAR = MathUtil.add(pA, vAMo); 2404 2405 Point2D vBM = MathUtil.normalize(MathUtil.subtract(pB, pM)); 2406 double dirBM_DEG = MathUtil.computeAngleDEG(vBM); 2407 Point2D vBMo = MathUtil.normalize(MathUtil.orthogonal(vBM), railDisplacement); 2408 Point2D pBL = MathUtil.subtract(pB, vBMo); 2409 Point2D pBR = MathUtil.add(pB, vBMo); 2410 Point2D pMR = MathUtil.add(pM, vBMo); 2411 2412 Point2D vCM = MathUtil.normalize(MathUtil.subtract(pC, pM)); 2413 double dirCM_DEG = MathUtil.computeAngleDEG(vCM); 2414 2415 Point2D vCMo = MathUtil.normalize(MathUtil.orthogonal(vCM), railDisplacement); 2416 Point2D pCL = MathUtil.subtract(pC, vCMo); 2417 Point2D pCR = MathUtil.add(pC, vCMo); 2418 Point2D pML = MathUtil.subtract(pM, vBMo); 2419 2420 double deltaBMC_DEG = MathUtil.absDiffAngleDEG(dirBM_DEG, dirCM_DEG); 2421 double deltaBMC_RAD = Math.toRadians(deltaBMC_DEG); 2422 2423 double hypotF = railDisplacement / Math.sin(deltaBMC_RAD / 2.0); 2424 2425 Point2D vDisF = MathUtil.normalize(MathUtil.add(vAM, vCM), hypotF); 2426 if (type == TurnoutType.WYE_TURNOUT) { 2427 vDisF = MathUtil.normalize(vAM, hypotF); 2428 } 2429 Point2D pF = MathUtil.add(pM, vDisF); 2430 2431 Point2D pFR = MathUtil.add(pF, MathUtil.multiply(vBMo, 2.0)); 2432 Point2D pFL = MathUtil.subtract(pF, MathUtil.multiply(vCMo, 2.0)); 2433 2434 // Point2D pFPR = MathUtil.add(pF, MathUtil.normalize(vBMo, 2.0)); 2435 // Point2D pFPL = MathUtil.subtract(pF, MathUtil.normalize(vCMo, 2.0)); 2436 Point2D vDisAP = MathUtil.normalize(vAM, hypotF); 2437 Point2D pAP = MathUtil.subtract(pM, vDisAP); 2438 Point2D pAPR = MathUtil.add(pAP, vAMo); 2439 Point2D pAPL = MathUtil.subtract(pAP, vAMo); 2440 2441 // Point2D vSo = MathUtil.normalize(vAMo, 2.0); 2442 // Point2D pSL = MathUtil.add(pAPL, vSo); 2443 // Point2D pSR = MathUtil.subtract(pAPR, vSo); 2444 boolean mainlineA = isMainlineA(); 2445 boolean mainlineB = isMainlineB(); 2446 boolean mainlineC = isMainlineC(); 2447 boolean mainlineD = isMainlineD(); 2448 2449 int state = UNKNOWN; 2450 if (layoutEditor.isAnimating()) { 2451 state = getState(); 2452 } 2453 2454 switch (type) { 2455 case RH_TURNOUT: { 2456 if (isMain == mainlineA) { 2457 g2.draw(new Line2D.Double(pAL, pML)); 2458 g2.draw(new Line2D.Double(pAR, pAPR)); 2459 } 2460 if (isMain == mainlineB) { 2461 g2.draw(new Line2D.Double(pML, pBL)); 2462 g2.draw(new Line2D.Double(pF, pBR)); 2463 if (getContinuingSense() == state) { // unknown or diverting path 2464// g2.draw(new Line2D.Double(pSR, pFPR)); 2465// } else { 2466 g2.draw(new Line2D.Double(pAPR, pF)); 2467 } 2468 } 2469 if (isMain == mainlineC) { 2470 g2.draw(new Line2D.Double(pF, pCL)); 2471 g2.draw(new Line2D.Double(pFR, pCR)); 2472 GeneralPath path = new GeneralPath(); 2473 path.moveTo(pAPR.getX(), pAPR.getY()); 2474 path.quadTo(pMR.getX(), pMR.getY(), pFR.getX(), pFR.getY()); 2475 path.lineTo(pCR.getX(), pCR.getY()); 2476 g2.draw(path); 2477 if (getContinuingSense() != state) { // unknown or diverting path 2478 path = new GeneralPath(); 2479 path.moveTo(pAPL.getX(), pAPL.getY()); 2480 path.quadTo(pML.getX(), pML.getY(), pF.getX(), pF.getY()); 2481 g2.draw(path); 2482// } else { 2483// path = new GeneralPath(); 2484// path.moveTo(pSL.getX(), pSL.getY()); 2485// path.quadTo(pML.getX(), pML.getY(), pFPL.getX(), pFPL.getY()); 2486// g2.draw(path); 2487 } 2488 } 2489 break; 2490 } // case RH_TURNOUT 2491 2492 case LH_TURNOUT: { 2493 if (isMain == mainlineA) { 2494 g2.draw(new Line2D.Double(pAR, pMR)); 2495 g2.draw(new Line2D.Double(pAL, pAPL)); 2496 } 2497 if (isMain == mainlineB) { 2498 g2.draw(new Line2D.Double(pMR, pBR)); 2499 g2.draw(new Line2D.Double(pF, pBL)); 2500 if (getContinuingSense() == state) { // straight path 2501// g2.draw(new Line2D.Double(pSL, pFPL)); Offset problem 2502// } else { 2503 g2.draw(new Line2D.Double(pAPL, pF)); 2504 } 2505 } 2506 if (isMain == mainlineC) { 2507 g2.draw(new Line2D.Double(pF, pCR)); 2508 GeneralPath path = new GeneralPath(); 2509 path.moveTo(pAPL.getX(), pAPL.getY()); 2510 path.quadTo(pML.getX(), pML.getY(), pFL.getX(), pFL.getY()); 2511 path.lineTo(pCL.getX(), pCL.getY()); 2512 g2.draw(path); 2513 if (getContinuingSense() != state) { // unknown or diverting path 2514 path = new GeneralPath(); 2515 path.moveTo(pAPR.getX(), pAPR.getY()); 2516 path.quadTo(pMR.getX(), pMR.getY(), pF.getX(), pF.getY()); 2517 g2.draw(path); 2518// } else { 2519// path = new GeneralPath(); 2520// path.moveTo(pSR.getX(), pSR.getY()); 2521// path.quadTo(pMR.getX(), pMR.getY(), pFPR.getX(), pFPR.getY()); 2522// g2.draw(path); 2523 } 2524 } 2525 break; 2526 } // case LH_TURNOUT 2527 2528 case WYE_TURNOUT: { 2529 if (isMain == mainlineA) { 2530 g2.draw(new Line2D.Double(pAL, pAPL)); 2531 g2.draw(new Line2D.Double(pAR, pAPR)); 2532 } 2533 if (isMain == mainlineB) { 2534 g2.draw(new Line2D.Double(pF, pBL)); 2535 GeneralPath path = new GeneralPath(); 2536 path.moveTo(pAPR.getX(), pAPR.getY()); 2537 path.quadTo(pMR.getX(), pMR.getY(), pFR.getX(), pFR.getY()); 2538 path.lineTo(pBR.getX(), pBR.getY()); 2539 g2.draw(path); 2540 if (getContinuingSense() != state) { // unknown or diverting path 2541 path = new GeneralPath(); 2542 path.moveTo(pAPR.getX(), pAPR.getY()); 2543 path.quadTo(pMR.getX(), pMR.getY(), pF.getX(), pF.getY()); 2544 g2.draw(path); 2545// } else { 2546// path = new GeneralPath(); 2547// path.moveTo(pSR.getX(), pSR.getY()); 2548// path.quadTo(pMR.getX(), pMR.getY(), pFPR.getX(), pFPR.getY()); 2549// bad g2.draw(path); 2550 } 2551 } 2552 if (isMain == mainlineC) { 2553 pML = MathUtil.subtract(pM, vCMo); 2554 GeneralPath path = new GeneralPath(); 2555 path.moveTo(pAPL.getX(), pAPL.getY()); 2556 path.quadTo(pML.getX(), pML.getY(), pFL.getX(), pFL.getY()); 2557 path.lineTo(pCL.getX(), pCL.getY()); 2558 g2.draw(path); 2559 g2.draw(new Line2D.Double(pF, pCR)); 2560 if (getContinuingSense() != state) { // unknown or diverting path 2561// path = new GeneralPath(); 2562// path.moveTo(pSL.getX(), pSL.getY()); 2563// path.quadTo(pML.getX(), pML.getY(), pFPL.getX(), pFPL.getY()); 2564// bad g2.draw(path); 2565 } else { 2566 path = new GeneralPath(); 2567 path.moveTo(pAPL.getX(), pAPL.getY()); 2568 path.quadTo(pML.getX(), pML.getY(), pF.getX(), pF.getY()); 2569 g2.draw(path); 2570 } 2571 } 2572 break; 2573 } // case WYE_TURNOUT 2574 2575 case DOUBLE_XOVER: { 2576 // A, B, C, D end points (left and right) 2577 Point2D vAB = MathUtil.normalize(MathUtil.subtract(pB, pA), railDisplacement); 2578 double dirAB_DEG = MathUtil.computeAngleDEG(vAB); 2579 Point2D vABo = MathUtil.orthogonal(MathUtil.normalize(vAB, railDisplacement)); 2580 pAL = MathUtil.subtract(pA, vABo); 2581 pAR = MathUtil.add(pA, vABo); 2582 pBL = MathUtil.subtract(pB, vABo); 2583 pBR = MathUtil.add(pB, vABo); 2584 Point2D vCD = MathUtil.normalize(MathUtil.subtract(pD, pC), railDisplacement); 2585 Point2D vCDo = MathUtil.orthogonal(MathUtil.normalize(vCD, railDisplacement)); 2586 pCL = MathUtil.add(pC, vCDo); 2587 pCR = MathUtil.subtract(pC, vCDo); 2588 Point2D pDL = MathUtil.add(pD, vCDo); 2589 Point2D pDR = MathUtil.subtract(pD, vCDo); 2590 2591 // AB, CD mid points (left and right) 2592 Point2D pABM = MathUtil.midPoint(pA, pB); 2593 Point2D pABL = MathUtil.midPoint(pAL, pBL); 2594 Point2D pABR = MathUtil.midPoint(pAR, pBR); 2595 Point2D pCDM = MathUtil.midPoint(pC, pD); 2596 Point2D pCDL = MathUtil.midPoint(pCL, pDL); 2597 Point2D pCDR = MathUtil.midPoint(pCR, pDR); 2598 2599 // A, B, C, D mid points 2600 double halfParallelDistance = MathUtil.distance(pABM, pCDM) / 2.0; 2601 Point2D pAM = MathUtil.subtract(pABM, MathUtil.normalize(vAB, halfParallelDistance)); 2602 Point2D pAML = MathUtil.subtract(pAM, vABo); 2603 Point2D pAMR = MathUtil.add(pAM, vABo); 2604 Point2D pBM = MathUtil.add(pABM, MathUtil.normalize(vAB, halfParallelDistance)); 2605 Point2D pBML = MathUtil.subtract(pBM, vABo); 2606 Point2D pBMR = MathUtil.add(pBM, vABo); 2607 Point2D pCM = MathUtil.subtract(pCDM, MathUtil.normalize(vCD, halfParallelDistance)); 2608 Point2D pCML = MathUtil.subtract(pCM, vABo); 2609 Point2D pCMR = MathUtil.add(pCM, vABo); 2610 Point2D pDM = MathUtil.add(pCDM, MathUtil.normalize(vCD, halfParallelDistance)); 2611 Point2D pDML = MathUtil.subtract(pDM, vABo); 2612 Point2D pDMR = MathUtil.add(pDM, vABo); 2613 2614 // crossing points 2615 Point2D vACM = MathUtil.normalize(MathUtil.subtract(pCM, pAM), railDisplacement); 2616 Point2D vACMo = MathUtil.orthogonal(vACM); 2617 Point2D vBDM = MathUtil.normalize(MathUtil.subtract(pDM, pBM), railDisplacement); 2618 Point2D vBDMo = MathUtil.orthogonal(vBDM); 2619 Point2D pBDR = MathUtil.add(pM, vACM); 2620 Point2D pBDL = MathUtil.subtract(pM, vACM); 2621 2622 // crossing diamond point (no gaps) 2623 Point2D pVR = MathUtil.add(pBDL, vBDM); 2624 Point2D pKL = MathUtil.subtract(pBDL, vBDM); 2625 Point2D pKR = MathUtil.add(pBDR, vBDM); 2626 Point2D pVL = MathUtil.subtract(pBDR, vBDM); 2627 2628 // crossing diamond points (with gaps) 2629 Point2D vACM2 = MathUtil.normalize(vACM, 2.0); 2630 Point2D vBDM2 = MathUtil.normalize(vBDM, 2.0); 2631 // (syntax of "pKLtC" is "point LK toward C", etc.) 2632 Point2D pKLtC = MathUtil.add(pKL, vACM2); 2633 Point2D pKLtD = MathUtil.add(pKL, vBDM2); 2634 Point2D pVLtA = MathUtil.subtract(pVL, vACM2); 2635 Point2D pVLtD = MathUtil.add(pVL, vBDM2); 2636 Point2D pKRtA = MathUtil.subtract(pKR, vACM2); 2637 Point2D pKRtB = MathUtil.subtract(pKR, vBDM2); 2638 Point2D pVRtB = MathUtil.subtract(pVR, vBDM2); 2639 Point2D pVRtC = MathUtil.add(pVR, vACM2); 2640 2641 // A, B, C, D frog points 2642 vCM = MathUtil.normalize(MathUtil.subtract(pCM, pM)); 2643 dirCM_DEG = MathUtil.computeAngleDEG(vCM); 2644 double deltaBAC_DEG = MathUtil.absDiffAngleDEG(dirAB_DEG, dirCM_DEG); 2645 double deltaBAC_RAD = Math.toRadians(deltaBAC_DEG); 2646 hypotF = railDisplacement / Math.sin(deltaBAC_RAD / 2.0); 2647 Point2D vACF = MathUtil.normalize(MathUtil.add(vACM, vAB), hypotF); 2648 Point2D pAFL = MathUtil.add(pAM, vACF); 2649 Point2D pCFR = MathUtil.subtract(pCM, vACF); 2650 Point2D vBDF = MathUtil.normalize(MathUtil.add(vBDM, vCD), hypotF); 2651 Point2D pBFL = MathUtil.add(pBM, vBDF); 2652 Point2D pDFR = MathUtil.subtract(pDM, vBDF); 2653 2654 // A, B, C, D frog points 2655 Point2D pAFR = MathUtil.add(MathUtil.add(pAFL, vACMo), vACMo); 2656 Point2D pBFR = MathUtil.subtract(MathUtil.subtract(pBFL, vBDMo), vBDMo); 2657 Point2D pCFL = MathUtil.subtract(MathUtil.subtract(pCFR, vACMo), vACMo); 2658 Point2D pDFL = MathUtil.add(MathUtil.add(pDFR, vBDMo), vBDMo); 2659 2660 // end of switch rails (closed) 2661 Point2D vABF = MathUtil.normalize(vAB, hypotF); 2662 pAP = MathUtil.subtract(pAM, vABF); 2663 pAPL = MathUtil.subtract(pAP, vABo); 2664 pAPR = MathUtil.add(pAP, vABo); 2665 Point2D pBP = MathUtil.add(pBM, vABF); 2666 Point2D pBPL = MathUtil.subtract(pBP, vABo); 2667 Point2D pBPR = MathUtil.add(pBP, vABo); 2668 2669 Point2D vCDF = MathUtil.normalize(vCD, hypotF); 2670 Point2D pCP = MathUtil.subtract(pCM, vCDF); 2671 Point2D pCPL = MathUtil.add(pCP, vCDo); 2672 Point2D pCPR = MathUtil.subtract(pCP, vCDo); 2673 Point2D pDP = MathUtil.add(pDM, vCDF); 2674 Point2D pDPL = MathUtil.add(pDP, vCDo); 2675 Point2D pDPR = MathUtil.subtract(pDP, vCDo); 2676 2677 // end of switch rails (open) 2678 Point2D vS = MathUtil.normalize(vABo, 2.0); 2679 Point2D pASL = MathUtil.add(pAPL, vS); 2680 // Point2D pASR = MathUtil.subtract(pAPR, vS); 2681 Point2D pBSL = MathUtil.add(pBPL, vS); 2682 // Point2D pBSR = MathUtil.subtract(pBPR, vS); 2683 Point2D pCSR = MathUtil.subtract(pCPR, vS); 2684 // Point2D pCSL = MathUtil.add(pCPL, vS); 2685 Point2D pDSR = MathUtil.subtract(pDPR, vS); 2686 // Point2D pDSL = MathUtil.add(pDPL, vS); 2687 2688 // end of switch rails (open at frogs) 2689 Point2D pAFS = MathUtil.subtract(pAFL, vS); 2690 Point2D pBFS = MathUtil.subtract(pBFL, vS); 2691 Point2D pCFS = MathUtil.add(pCFR, vS); 2692 Point2D pDFS = MathUtil.add(pDFR, vS); 2693 2694 // vSo = MathUtil.orthogonal(vS); 2695 // Point2D pAFSR = MathUtil.add(pAFL, vSo); 2696 // Point2D pBFSR = MathUtil.subtract(pBFL, vSo); 2697 // Point2D pCFSL = MathUtil.subtract(pCFR, vSo); 2698 // Point2D pDFSL = MathUtil.add(pDFR, vSo); 2699 if (isMain == mainlineA) { 2700 g2.draw(new Line2D.Double(pAL, pABL)); 2701 g2.draw(new Line2D.Double(pVRtB, pKLtD)); 2702 g2.draw(new Line2D.Double(pAFL, pABR)); 2703 g2.draw(new Line2D.Double(pAFL, pKL)); 2704 GeneralPath path = new GeneralPath(); 2705 path.moveTo(pAR.getX(), pAR.getY()); 2706 path.lineTo(pAPR.getX(), pAPR.getY()); 2707 path.quadTo(pAMR.getX(), pAMR.getY(), pAFR.getX(), pAFR.getY()); 2708 path.lineTo(pVR.getX(), pVR.getY()); 2709 g2.draw(path); 2710 if (state != Turnout.CLOSED) { // unknown or diverting path 2711 path = new GeneralPath(); 2712 path.moveTo(pAPL.getX(), pAPL.getY()); 2713 path.quadTo(pAML.getX(), pAML.getY(), pAFL.getX(), pAFL.getY()); 2714 g2.draw(path); 2715// g2.draw(new Line2D.Double(pASR, pAFSR)); 2716 } else { // continuing path 2717 g2.draw(new Line2D.Double(pAPR, pAFL)); 2718 path = new GeneralPath(); 2719 path.moveTo(pASL.getX(), pASL.getY()); 2720 path.quadTo(pAML.getX(), pAML.getY(), pAFS.getX(), pAFS.getY()); 2721// g2.draw(path); 2722 } 2723 } 2724 if (isMain == mainlineB) { 2725 g2.draw(new Line2D.Double(pABL, pBL)); 2726 g2.draw(new Line2D.Double(pKLtC, pVLtA)); 2727 g2.draw(new Line2D.Double(pBFL, pABR)); 2728 g2.draw(new Line2D.Double(pBFL, pKL)); 2729 GeneralPath path = new GeneralPath(); 2730 path.moveTo(pBR.getX(), pBR.getY()); 2731 path.lineTo(pBPR.getX(), pBPR.getY()); 2732 path.quadTo(pBMR.getX(), pBMR.getY(), pBFR.getX(), pBFR.getY()); 2733 path.lineTo(pVL.getX(), pVL.getY()); 2734 g2.draw(path); 2735 if (state != Turnout.CLOSED) { // unknown or diverting path 2736 path = new GeneralPath(); 2737 path.moveTo(pBPL.getX(), pBPL.getY()); 2738 path.quadTo(pBML.getX(), pBML.getY(), pBFL.getX(), pBFL.getY()); 2739 g2.draw(path); 2740// g2.draw(new Line2D.Double(pBSR, pBFSR)); 2741 } else { 2742 g2.draw(new Line2D.Double(pBPR, pBFL)); 2743 path = new GeneralPath(); 2744 path.moveTo(pBSL.getX(), pBSL.getY()); 2745 path.quadTo(pBML.getX(), pBML.getY(), pBFS.getX(), pBFS.getY()); 2746// g2.draw(path); 2747 } 2748 } 2749 if (isMain == mainlineC) { 2750 g2.draw(new Line2D.Double(pCR, pCDR)); 2751 g2.draw(new Line2D.Double(pKRtB, pVLtD)); 2752 g2.draw(new Line2D.Double(pCFR, pCDL)); 2753 g2.draw(new Line2D.Double(pCFR, pKR)); 2754 GeneralPath path = new GeneralPath(); 2755 path.moveTo(pCL.getX(), pCL.getY()); 2756 path.lineTo(pCPL.getX(), pCPL.getY()); 2757 path.quadTo(pCML.getX(), pCML.getY(), pCFL.getX(), pCFL.getY()); 2758 path.lineTo(pVL.getX(), pVL.getY()); 2759 g2.draw(path); 2760 if (state != Turnout.CLOSED) { // unknown or diverting path 2761 path = new GeneralPath(); 2762 path.moveTo(pCPR.getX(), pCPR.getY()); 2763 path.quadTo(pCMR.getX(), pCMR.getY(), pCFR.getX(), pCFR.getY()); 2764 g2.draw(path); 2765// g2.draw(new Line2D.Double(pCSL, pCFSL)); 2766 } else { 2767 g2.draw(new Line2D.Double(pCPL, pCFR)); 2768 path = new GeneralPath(); 2769 path.moveTo(pCSR.getX(), pCSR.getY()); 2770 path.quadTo(pCMR.getX(), pCMR.getY(), pCFS.getX(), pCFS.getY()); 2771// g2.draw(path); 2772 } 2773 } 2774 if (isMain == mainlineD) { 2775 g2.draw(new Line2D.Double(pCDR, pDR)); 2776 g2.draw(new Line2D.Double(pKRtA, pVRtC)); 2777 g2.draw(new Line2D.Double(pDFR, pCDL)); 2778 g2.draw(new Line2D.Double(pDFR, pKR)); 2779 GeneralPath path = new GeneralPath(); 2780 path.moveTo(pDL.getX(), pDL.getY()); 2781 path.lineTo(pDPL.getX(), pDPL.getY()); 2782 path.quadTo(pDML.getX(), pDML.getY(), pDFL.getX(), pDFL.getY()); 2783 path.lineTo(pVR.getX(), pVR.getY()); 2784 g2.draw(path); 2785 if (state != Turnout.CLOSED) { // unknown or diverting path 2786 path = new GeneralPath(); 2787 path.moveTo(pDPR.getX(), pDPR.getY()); 2788 path.quadTo(pDMR.getX(), pDMR.getY(), pDFR.getX(), pDFR.getY()); 2789 g2.draw(path); 2790// g2.draw(new Line2D.Double(pDSL, pDFSL)); 2791 } else { 2792 g2.draw(new Line2D.Double(pDPL, pDFR)); 2793 path = new GeneralPath(); 2794 path.moveTo(pDSR.getX(), pDSR.getY()); 2795 path.quadTo(pDMR.getX(), pDMR.getY(), pDFS.getX(), pDFS.getY()); 2796// g2.draw(path); 2797 } 2798 } 2799 break; 2800 } // case DOUBLE_XOVER 2801 2802 case RH_XOVER: { 2803 // A, B, C, D end points (left and right) 2804 Point2D vAB = MathUtil.normalize(MathUtil.subtract(pB, pA), railDisplacement); 2805 double dirAB_DEG = MathUtil.computeAngleDEG(vAB); 2806 Point2D vABo = MathUtil.orthogonal(MathUtil.normalize(vAB, railDisplacement)); 2807 pAL = MathUtil.subtract(pA, vABo); 2808 pAR = MathUtil.add(pA, vABo); 2809 pBL = MathUtil.subtract(pB, vABo); 2810 pBR = MathUtil.add(pB, vABo); 2811 Point2D vCD = MathUtil.normalize(MathUtil.subtract(pD, pC), railDisplacement); 2812 Point2D vCDo = MathUtil.orthogonal(MathUtil.normalize(vCD, railDisplacement)); 2813 pCL = MathUtil.add(pC, vCDo); 2814 pCR = MathUtil.subtract(pC, vCDo); 2815 Point2D pDL = MathUtil.add(pD, vCDo); 2816 Point2D pDR = MathUtil.subtract(pD, vCDo); 2817 2818 // AB and CD mid points 2819 Point2D pABM = MathUtil.midPoint(pA, pB); 2820 Point2D pABL = MathUtil.subtract(pABM, vABo); 2821 Point2D pABR = MathUtil.add(pABM, vABo); 2822 Point2D pCDM = MathUtil.midPoint(pC, pD); 2823 Point2D pCDL = MathUtil.subtract(pCDM, vABo); 2824 Point2D pCDR = MathUtil.add(pCDM, vABo); 2825 2826 // directions 2827 Point2D vAC = MathUtil.normalize(MathUtil.subtract(pCDM, pABM), railDisplacement); 2828 Point2D vACo = MathUtil.orthogonal(MathUtil.normalize(vAC, railDisplacement)); 2829 double dirAC_DEG = MathUtil.computeAngleDEG(vAC); 2830 double deltaBAC_DEG = MathUtil.absDiffAngleDEG(dirAB_DEG, dirAC_DEG); 2831 double deltaBAC_RAD = Math.toRadians(deltaBAC_DEG); 2832 2833 // AC mid points 2834 Point2D pACL = MathUtil.subtract(pM, vACo); 2835 Point2D pACR = MathUtil.add(pM, vACo); 2836 2837 // frogs 2838 hypotF = railDisplacement / Math.sin(deltaBAC_RAD / 2.0); 2839 Point2D vF = MathUtil.normalize(MathUtil.add(vAB, vAC), hypotF); 2840 Point2D pABF = MathUtil.add(pABM, vF); 2841 Point2D pCDF = MathUtil.subtract(pCDM, vF); 2842 2843 // frog primes 2844 Point2D pABFP = MathUtil.add(MathUtil.add(pABF, vACo), vACo); 2845 Point2D pCDFP = MathUtil.subtract(MathUtil.subtract(pCDF, vACo), vACo); 2846 2847 // end of switch rails (closed) 2848 Point2D vABF = MathUtil.normalize(vAB, hypotF); 2849 pAP = MathUtil.subtract(pABM, vABF); 2850 pAPL = MathUtil.subtract(pAP, vABo); 2851 pAPR = MathUtil.add(pAP, vABo); 2852 Point2D pCP = MathUtil.add(pCDM, vABF); 2853 Point2D pCPL = MathUtil.add(pCP, vCDo); 2854 Point2D pCPR = MathUtil.subtract(pCP, vCDo); 2855 2856 // end of switch rails (open) 2857 Point2D vS = MathUtil.normalize(vAB, 2.0); 2858 Point2D vSo = MathUtil.orthogonal(vS); 2859 Point2D pASL = MathUtil.add(pAPL, vSo); 2860 // Point2D pASR = MathUtil.subtract(pAPR, vSo); 2861 // Point2D pCSL = MathUtil.add(pCPL, vSo); 2862 Point2D pCSR = MathUtil.subtract(pCPR, vSo); 2863 2864 // end of switch rails (open at frogs) 2865 Point2D pABFS = MathUtil.subtract(pABF, vSo); 2866 // Point2D pABFSP = MathUtil.subtract(pABF, vS); 2867 Point2D pCDFS = MathUtil.add(pCDF, vSo); 2868 // Point2D pCDFSP = MathUtil.add(pCDF, vS); 2869 2870 if (isMain == mainlineA) { 2871 g2.draw(new Line2D.Double(pAL, pABL)); 2872 GeneralPath path = new GeneralPath(); 2873 path.moveTo(pAR.getX(), pAR.getY()); 2874 path.lineTo(pAPR.getX(), pAPR.getY()); 2875 path.quadTo(pABR.getX(), pABR.getY(), pABFP.getX(), pABFP.getY()); 2876 path.lineTo(pACR.getX(), pACR.getY()); 2877 g2.draw(path); 2878 g2.draw(new Line2D.Double(pABF, pACL)); 2879 if (state != Turnout.CLOSED) { // unknown or diverting path 2880 path = new GeneralPath(); 2881 path.moveTo(pAPL.getX(), pAPL.getY()); 2882 path.quadTo(pABL.getX(), pABL.getY(), pABF.getX(), pABF.getY()); 2883 g2.draw(path); 2884// g2.draw(new Line2D.Double(pASR, pABFSP)); 2885 } else { // continuing path 2886 g2.draw(new Line2D.Double(pAPR, pABF)); 2887 path = new GeneralPath(); 2888 path.moveTo(pASL.getX(), pASL.getY()); 2889 path.quadTo(pABL.getX(), pABL.getY(), pABFS.getX(), pABFS.getY()); 2890// g2.draw(path); 2891 } 2892 } 2893 if (isMain == mainlineB) { 2894 g2.draw(new Line2D.Double(pABL, pBL)); 2895 g2.draw(new Line2D.Double(pABF, pBR)); 2896 } 2897 if (isMain == mainlineC) { 2898 g2.draw(new Line2D.Double(pCR, pCDR)); 2899 GeneralPath path = new GeneralPath(); 2900 path.moveTo(pCL.getX(), pCL.getY()); 2901 path.lineTo(pCPL.getX(), pCPL.getY()); 2902 path.quadTo(pCDL.getX(), pCDL.getY(), pCDFP.getX(), pCDFP.getY()); 2903 path.lineTo(pACL.getX(), pACL.getY()); 2904 g2.draw(path); 2905 g2.draw(new Line2D.Double(pCDF, pACR)); 2906 if (state != Turnout.CLOSED) { // unknown or diverting path 2907 path = new GeneralPath(); 2908 path.moveTo(pCPR.getX(), pCPR.getY()); 2909 path.quadTo(pCDR.getX(), pCDR.getY(), pCDF.getX(), pCDF.getY()); 2910 g2.draw(path); 2911// g2.draw(new Line2D.Double(pCSL, pCDFSP)); 2912 } else { // continuing path 2913 g2.draw(new Line2D.Double(pCPL, pCDF)); 2914 path = new GeneralPath(); 2915 path.moveTo(pCSR.getX(), pCSR.getY()); 2916 path.quadTo(pCDR.getX(), pCDR.getY(), pCDFS.getX(), pCDFS.getY()); 2917// g2.draw(path); 2918 } 2919 } 2920 if (isMain == mainlineD) { 2921 g2.draw(new Line2D.Double(pCDR, pDR)); 2922 g2.draw(new Line2D.Double(pCDF, pDL)); 2923 } 2924 break; 2925 } // case RH_XOVER 2926 2927 case LH_XOVER: { 2928 // B, A, D, C end points (left and right) 2929 Point2D vBA = MathUtil.normalize(MathUtil.subtract(pA, pB), railDisplacement); 2930 double dirBA_DEG = MathUtil.computeAngleDEG(vBA); 2931 Point2D vBAo = MathUtil.orthogonal(MathUtil.normalize(vBA, railDisplacement)); 2932 pBL = MathUtil.add(pB, vBAo); 2933 pBR = MathUtil.subtract(pB, vBAo); 2934 pAL = MathUtil.add(pA, vBAo); 2935 pAR = MathUtil.subtract(pA, vBAo); 2936 Point2D vDC = MathUtil.normalize(MathUtil.subtract(pC, pD), railDisplacement); 2937 Point2D vDCo = MathUtil.orthogonal(MathUtil.normalize(vDC, railDisplacement)); 2938 Point2D pDL = MathUtil.subtract(pD, vDCo); 2939 Point2D pDR = MathUtil.add(pD, vDCo); 2940 pCL = MathUtil.subtract(pC, vDCo); 2941 pCR = MathUtil.add(pC, vDCo); 2942 2943 // BA and DC mid points 2944 Point2D pBAM = MathUtil.midPoint(pB, pA); 2945 Point2D pBAL = MathUtil.add(pBAM, vBAo); 2946 Point2D pBAR = MathUtil.subtract(pBAM, vBAo); 2947 Point2D pDCM = MathUtil.midPoint(pD, pC); 2948 Point2D pDCL = MathUtil.add(pDCM, vBAo); 2949 Point2D pDCR = MathUtil.subtract(pDCM, vBAo); 2950 2951 // directions 2952 Point2D vBD = MathUtil.normalize(MathUtil.subtract(pDCM, pBAM), railDisplacement); 2953 Point2D vBDo = MathUtil.orthogonal(MathUtil.normalize(vBD, railDisplacement)); 2954 double dirBD_DEG = MathUtil.computeAngleDEG(vBD); 2955 double deltaABD_DEG = MathUtil.absDiffAngleDEG(dirBA_DEG, dirBD_DEG); 2956 double deltaABD_RAD = Math.toRadians(deltaABD_DEG); 2957 2958 // BD mid points 2959 Point2D pBDL = MathUtil.add(pM, vBDo); 2960 Point2D pBDR = MathUtil.subtract(pM, vBDo); 2961 2962 // frogs 2963 hypotF = railDisplacement / Math.sin(deltaABD_RAD / 2.0); 2964 Point2D vF = MathUtil.normalize(MathUtil.add(vBA, vBD), hypotF); 2965 Point2D pBFL = MathUtil.add(pBAM, vF); 2966 Point2D pBF = MathUtil.subtract(pBFL, vBDo); 2967 Point2D pBFR = MathUtil.subtract(pBF, vBDo); 2968 Point2D pDFR = MathUtil.subtract(pDCM, vF); 2969 Point2D pDF = MathUtil.add(pDFR, vBDo); 2970 Point2D pDFL = MathUtil.add(pDF, vBDo); 2971 2972 // end of switch rails (closed) 2973 Point2D vBAF = MathUtil.normalize(vBA, hypotF); 2974 Point2D pBP = MathUtil.subtract(pBAM, vBAF); 2975 Point2D pBPL = MathUtil.add(pBP, vBAo); 2976 Point2D pBPR = MathUtil.subtract(pBP, vBAo); 2977 Point2D pDP = MathUtil.add(pDCM, vBAF); 2978 Point2D pDPL = MathUtil.subtract(pDP, vDCo); 2979 Point2D pDPR = MathUtil.add(pDP, vDCo); 2980 2981 // end of switch rails (open) 2982 Point2D vS = MathUtil.normalize(vBA, 2.0); 2983 Point2D vSo = MathUtil.orthogonal(vS); 2984 Point2D pBSL = MathUtil.subtract(pBPL, vSo); 2985 // Point2D pBSR = MathUtil.add(pBPR, vSo); 2986 // Point2D pDSL = MathUtil.subtract(pDPL, vSo); 2987 Point2D pDSR = MathUtil.add(pDPR, vSo); 2988 2989 // end of switch rails (open at frogs) 2990 Point2D pBAFS = MathUtil.add(pBFL, vSo); 2991 // Point2D pBAFSP = MathUtil.subtract(pBFL, vS); 2992 Point2D pDCFS = MathUtil.subtract(pDFR, vSo); 2993 // Point2D pDCFSP = MathUtil.add(pDFR, vS); 2994 2995 if (isMain == mainlineA) { 2996 g2.draw(new Line2D.Double(pBAL, pAL)); 2997 g2.draw(new Line2D.Double(pBFL, pAR)); 2998 } 2999 if (isMain == mainlineB) { 3000 g2.draw(new Line2D.Double(pBL, pBAL)); 3001 GeneralPath path = new GeneralPath(); 3002 path.moveTo(pBR.getX(), pBR.getY()); 3003 path.lineTo(pBPR.getX(), pBPR.getY()); 3004 path.quadTo(pBAR.getX(), pBAR.getY(), pBFR.getX(), pBFR.getY()); 3005 path.lineTo(pBDR.getX(), pBDR.getY()); 3006 g2.draw(path); 3007 g2.draw(new Line2D.Double(pBFL, pBDL)); 3008 if (state != Turnout.CLOSED) { // unknown or diverting path 3009 path = new GeneralPath(); 3010 path.moveTo(pBPL.getX(), pBPL.getY()); 3011 path.quadTo(pBAL.getX(), pBAL.getY(), pBFL.getX(), pBFL.getY()); 3012 g2.draw(path); 3013// g2.draw(new Line2D.Double(pBSR, pBAFSP)); 3014 } else { // continuing path 3015 g2.draw(new Line2D.Double(pBPR, pBFL)); 3016 path = new GeneralPath(); 3017 path.moveTo(pBSL.getX(), pBSL.getY()); 3018 path.quadTo(pBAL.getX(), pBAL.getY(), pBAFS.getX(), pBAFS.getY()); 3019// g2.draw(path); 3020 } 3021 } 3022 if (isMain == mainlineC) { 3023 g2.draw(new Line2D.Double(pDCR, pCR)); 3024 g2.draw(new Line2D.Double(pDFR, pCL)); 3025 } 3026 if (isMain == mainlineD) { 3027 g2.draw(new Line2D.Double(pDR, pDCR)); 3028 GeneralPath path = new GeneralPath(); 3029 path.moveTo(pDL.getX(), pDL.getY()); 3030 path.lineTo(pDPL.getX(), pDPL.getY()); 3031 path.quadTo(pDCL.getX(), pDCL.getY(), pDFL.getX(), pDFL.getY()); 3032 path.lineTo(pBDL.getX(), pBDL.getY()); 3033 g2.draw(path); 3034 g2.draw(new Line2D.Double(pDFR, pBDR)); 3035 if (state != Turnout.CLOSED) { // unknown or diverting path 3036 path = new GeneralPath(); 3037 path.moveTo(pDPR.getX(), pDPR.getY()); 3038 path.quadTo(pDCR.getX(), pDCR.getY(), pDFR.getX(), pDFR.getY()); 3039 g2.draw(path); 3040// g2.draw(new Line2D.Double(pDSL, pDCFSP)); 3041 } else { // continuing path 3042 g2.draw(new Line2D.Double(pDPL, pDFR)); 3043 path = new GeneralPath(); 3044 path.moveTo(pDSR.getX(), pDSR.getY()); 3045 path.quadTo(pDCR.getX(), pDCR.getY(), pDCFS.getX(), pDCFS.getY()); 3046// g2.draw(path); 3047 } 3048 } 3049 break; 3050 } // case LH_XOVER 3051 case SINGLE_SLIP: 3052 case DOUBLE_SLIP: { 3053 log.error("{}.draw2(...); slips should be being drawn by LayoutSlip sub-class", getName()); 3054 break; 3055 } 3056 default: { 3057 // this should never happen... but... 3058 log.error("{}.draw2(...); Unknown turnout type {}", getName(), type); 3059 break; 3060 } 3061 } 3062 } // draw2 3063 3064 /** 3065 * {@inheritDoc} 3066 */ 3067 @Override 3068 protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) { 3069 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_A)) 3070 && (getConnectA() == null)) { 3071 g2.fill(trackControlCircleAt(getCoordsA())); 3072 } 3073 3074 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_B)) 3075 && (getConnectB() == null)) { 3076 g2.fill(trackControlCircleAt(getCoordsB())); 3077 } 3078 3079 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_C)) 3080 && (getConnectC() == null)) { 3081 g2.fill(trackControlCircleAt(getCoordsC())); 3082 } 3083 if (isTurnoutTypeXover()) { 3084 if (((specificType == HitPointType.NONE) || (specificType == HitPointType.TURNOUT_D)) 3085 && (getConnectD() == null)) { 3086 g2.fill(trackControlCircleAt(getCoordsD())); 3087 } 3088 } 3089 } 3090 3091 /** 3092 * {@inheritDoc} 3093 */ 3094 @Override 3095 protected void drawTurnoutControls(Graphics2D g2) { 3096 if (!isDisabled() && !(isDisabledWhenOccupied() && isOccupied())) { 3097 Color foregroundColor = g2.getColor(); 3098 3099 if (getState() != Turnout.CLOSED) { 3100 // then switch to background (thrown) color 3101 g2.setColor(g2.getBackground()); 3102 } 3103 3104 if (layoutEditor.isTurnoutFillControlCircles()) { 3105 g2.fill(trackControlCircleAt(getCoordsCenter())); 3106 // do we need to draw a ? for unknown over the circle? 3107 if (showUnknown && getState() == UNKNOWN) { 3108 drawForShowUnknown(g2, getCoordsCenter(), g2.getBackground(), true); 3109 return; 3110 } 3111 } else { 3112 g2.draw(trackControlCircleAt(getCoordsCenter())); 3113 } 3114 3115 if (getState() != Turnout.CLOSED) { 3116 // then restore foreground color 3117 g2.setColor(foregroundColor); 3118 } 3119 3120 3121 } 3122 } 3123 3124 /** 3125 * {@inheritDoc} 3126 */ 3127 @Override 3128 protected void drawEditControls(Graphics2D g2) { 3129 Point2D pt = getCoordsA(); 3130 if (isTurnoutTypeXover() || isTurnoutTypeSlip()) { 3131 if (getConnectA() == null) { 3132 g2.setColor(Color.magenta); 3133 } else { 3134 g2.setColor(Color.blue); 3135 } 3136 } else { 3137 if (getConnectA() == null) { 3138 g2.setColor(Color.red); 3139 } else { 3140 g2.setColor(Color.green); 3141 } 3142 } 3143 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3144 3145 pt = getCoordsB(); 3146 if (getConnectB() == null) { 3147 g2.setColor(Color.red); 3148 } else { 3149 g2.setColor(Color.green); 3150 } 3151 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3152 3153 pt = getCoordsC(); 3154 if (getConnectC() == null) { 3155 g2.setColor(Color.red); 3156 } else { 3157 g2.setColor(Color.green); 3158 } 3159 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3160 3161 if (isTurnoutTypeXover() || isTurnoutTypeSlip()) { 3162 pt = getCoordsD(); 3163 if (getConnectD() == null) { 3164 g2.setColor(Color.red); 3165 } else { 3166 g2.setColor(Color.green); 3167 } 3168 g2.draw(layoutEditor.layoutEditorControlRectAt(pt)); 3169 } 3170 } 3171 3172 /* 3173 * Used by ConnectivityUtil to determine the turnout state necessary to get 3174 * from prevLayoutBlock ==> currLayoutBlock ==> nextLayoutBlock 3175 */ 3176 protected int getConnectivityStateForLayoutBlocks( 3177 LayoutBlock currLayoutBlock, 3178 LayoutBlock prevLayoutBlock, 3179 LayoutBlock nextLayoutBlock, 3180 boolean suppress) { 3181 3182 return turnout.getConnectivityStateForLayoutBlocks(currLayoutBlock, 3183 prevLayoutBlock, 3184 nextLayoutBlock, 3185 suppress); 3186 } 3187 3188 /** 3189 * {@inheritDoc} 3190 */ 3191 // TODO: on the cross-overs, check the internal boundary details. 3192 @Override 3193 public void reCheckBlockBoundary() { 3194 3195 turnout.reCheckBlockBoundary(); 3196 3197 } 3198 3199 /** 3200 * {@inheritDoc} 3201 */ 3202 @Override 3203 protected List<LayoutConnectivity> getLayoutConnectivity() { 3204 return turnout.getLayoutConnectivity(); 3205 } 3206 3207 /** 3208 * {@inheritDoc} 3209 */ 3210 @Override 3211 public @Nonnull 3212 List<HitPointType> checkForFreeConnections() { 3213 return turnout.checkForFreeConnections(); 3214 } 3215 3216 /** 3217 * {@inheritDoc} 3218 */ 3219 @Override 3220 public boolean checkForUnAssignedBlocks() { 3221 // because getLayoutBlock[BCD] will return block [A] if they're null 3222 // we only need to test block [A] 3223 return turnout.checkForUnAssignedBlocks(); 3224 } 3225 3226 /** 3227 * {@inheritDoc} 3228 */ 3229 @Override 3230 public void checkForNonContiguousBlocks( 3231 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 3232 3233 turnout.checkForNonContiguousBlocks(blockNamesToTrackNameSetsMap); 3234 } 3235 3236 /** 3237 * {@inheritDoc} 3238 */ 3239 @Override 3240 public void collectContiguousTracksNamesInBlockNamed( 3241 @Nonnull String blockName, 3242 @Nonnull Set<String> TrackNameSet) { 3243 3244 turnout.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 3245 } 3246 3247 /** 3248 * {@inheritDoc} 3249 */ 3250 @Override 3251 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 3252 turnout.setAllLayoutBlocks(layoutBlock); 3253 } 3254 3255 private static class AbstractActionImpl extends AbstractAction { 3256 3257 private final String blockName; 3258 private final LayoutBlock layoutBlock; 3259 3260 AbstractActionImpl(String name, String blockName, LayoutBlock layoutBlock) { 3261 super(name); 3262 this.blockName = blockName; 3263 this.layoutBlock = layoutBlock; 3264 } 3265 3266 @Override 3267 public void actionPerformed(ActionEvent e) { 3268 AbstractAction routeTableAction = new LayoutBlockRouteTableAction(blockName, layoutBlock); 3269 routeTableAction.actionPerformed(e); 3270 } 3271 } 3272 3273 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurnoutView.class); 3274}