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