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