001package jmri.jmrit.display.layoutEditor; 002 003import java.text.MessageFormat; 004import java.util.*; 005 006import javax.annotation.*; 007 008import jmri.*; 009import jmri.jmrit.signalling.SignallingGuiTools; // temporary 010 011 012/** 013 * PositionablePoint is a Point defining a node in the Track that can be dragged 014 * around the inside of the enclosing LayoutEditor panel using a right-drag 015 * (drag with meta key). 016 * <p> 017 * Three types of Positionable Point are supported: Anchor - point on track - 018 * two track connections End Bumper - end of track point - one track connection 019 * Edge Connector - This is used to link track segments between two different 020 * panels 021 * <p> 022 * Note that a PositionablePoint exists for specifying connectivity and drawing 023 * position only. The Track Segments connected to a PositionablePoint may belong 024 * to the same block or to different blocks. Since each Track Segment may only 025 * belong to one block, a PositionablePoint may function as a Block Boundary. 026 * <p> 027 * As an Edge Connector, this is a semi-transparent connection to a remote 028 * TrackSeqment via another Edge Connector object. 029 * <p> 030 * Signal names are saved here at a Block Boundary anchor point by the tool Set 031 * Signals at Block Boundary. PositionablePoint does nothing with these signal 032 * head names; it only serves as a place to store them. 033 * <p> 034 * Arrows and bumpers are visual presentation aspects handled in the View. 035 * 036 * @author Dave Duchamp Copyright (c) 2004-2007 037 * @author Bob Jacobsen Copyright (2) 2014, 2020 038 * @author George Warner Copyright (c) 2017-2019 039 */ 040public class PositionablePoint extends LayoutTrack { 041 042 // defined constants 043 public enum PointType { 044 NONE, 045 ANCHOR, // 1 046 END_BUMPER, // 2 047 EDGE_CONNECTOR // 3 048 } 049 050 // operational instance variables (not saved between sessions) 051 // persistent instances variables (saved between sessions) 052 private PointType type = PointType.NONE; 053 private TrackSegment connect1 = null; 054 private TrackSegment connect2 = null; 055 056 protected NamedBeanHandle<SignalHead> signalEastHeadNamed = null; // signal head for east (south) bound trains 057 protected NamedBeanHandle<SignalHead> signalWestHeadNamed = null; // signal head for west (north) bound trains 058 059 private NamedBeanHandle<SignalMast> eastBoundSignalMastNamed = null; 060 private NamedBeanHandle<SignalMast> westBoundSignalMastNamed = null; 061 /* We use a namedbeanhandle for the sensors, even though we only store the name here, 062 this is so that we can keep up with moves and changes of userNames */ 063 private NamedBeanHandle<Sensor> eastBoundSensorNamed = null; 064 private NamedBeanHandle<Sensor> westBoundSensorNamed = null; 065 066 public PositionablePoint(String id, PointType t, LayoutEditor models) { 067 super(id, models); 068 069 if ((t == PointType.ANCHOR) || (t == PointType.END_BUMPER) || (t == PointType.EDGE_CONNECTOR)) { 070 type = t; 071 } else { 072 log.error("Illegal type of PositionablePoint - {}", t); 073 type = PointType.ANCHOR; 074 } 075 } 076 077 // this should only be used for debugging... 078 @Override 079 public String toString() { 080 String result = "PositionalablePoint"; 081 switch (type) { 082 case ANCHOR: { 083 result = "Anchor"; 084 break; 085 } 086 case END_BUMPER: { 087 result = "End Bumper"; 088 break; 089 } 090 case EDGE_CONNECTOR: { 091 result = "Edge Connector"; 092 break; 093 } 094 default: { 095 result = "Unknown type (" + type + ")"; 096 break; 097 } 098 } 099 return result + " '" + getName() + "'"; 100 } 101 102 /** 103 * Get the point type. 104 * @return point type, i.e. ANCHOR, END_BUMPER, EDGE_CONNECTOR 105 */ 106 public PointType getType() { 107 return type; 108 } 109 110 public void setType(PointType newType) { 111 if (type != newType) { 112 switch (newType) { 113 default: 114 case ANCHOR: { 115 setTypeAnchor(); 116 break; 117 } 118 case END_BUMPER: { 119 setTypeEndBumper(); 120 break; 121 } 122 case EDGE_CONNECTOR: { 123 setTypeEdgeConnector(); 124 break; 125 } 126 } 127 128 log.debug("temporary - repaint was removed here, needs to be rescoped"); 129 // models.repaint(); 130 131 } 132 } 133 134 void setTypeAnchor() { // temporary: make private once decoupled 135 setIdent(models.getFinder().uniqueName("A", 1)); 136 type = PointType.ANCHOR; 137 if (connect1 != null) { 138 if (connect1.getConnect1() == PositionablePoint.this) { 139 log.debug("Elided handling of connect1 in setTypeAnchor"); 140 //connect1.setArrowEndStart(false); // temporary - is this being done in the view? 141 //connect1.setBumperEndStart(false); // temporary - is this being done in the view? 142 } 143 if (connect1.getConnect2() == PositionablePoint.this) { 144 log.debug("Elided handling of connect1 in setTypeAnchor"); 145 //connect1.setArrowEndStop(false); // temporary - is this being done in the view? 146 //connect1.setBumperEndStop(false); // temporary - is this being done in the view? 147 } 148 } 149 if (connect2 != null) { 150 if (connect2.getConnect1() == PositionablePoint.this) { 151 log.debug("Elided handling of connect2 in setTypeAnchor"); 152 //connect2.setArrowEndStart(false); // temporary - is this being done in the view? 153 //connect2.setBumperEndStart(false); // temporary - is this being done in the view? 154 } 155 if (connect2.getConnect2() == PositionablePoint.this) { 156 log.debug("Elided handling of connect2 in setTypeAnchor"); 157 //connect2.setArrowEndStop(false); // temporary - is this being done in the view? 158 //connect2.setBumperEndStop(false); // temporary - is this being done in the view? 159 } 160 } 161 } 162 163 void setTypeEndBumper() { // temporary: make private once decoupled 164 setIdent(models.getFinder().uniqueName("EB", 1)); 165 type = PointType.END_BUMPER; 166 if (connect1 != null) { 167 if (connect1.getConnect1() == PositionablePoint.this) { 168 log.debug("Elided handling of connect1 in setTypeEndBumper"); 169 //connect1.setArrowEndStart(false); // temporary - is this being done in the view? 170 //connect1.setBumperEndStart(true); // temporary - is this being done in the view? 171 } 172 if (connect1.getConnect2() == PositionablePoint.this) { 173 log.debug("Elided handling of connect2 in setTypeEndBumper"); 174 //connect1.setArrowEndStop(false); // temporary - is this being done in the view? 175 //connect1.setBumperEndStop(true); // temporary - is this being done in the view? 176 } 177 } 178 } 179 180 void setTypeEdgeConnector() { // temporary: make private once decoupled 181 setIdent(models.getFinder().uniqueName("EC", 1)); 182 type = PointType.EDGE_CONNECTOR; 183 if (connect1 != null) { 184 if (connect1.getConnect1() == PositionablePoint.this) { 185 log.debug("Elided handling of connect1 in setTypeEdgeConnector"); 186 //connect1.setBumperEndStart(false); // temporary - is this being done in the view? 187 } 188 if (connect1.getConnect2() == PositionablePoint.this) { 189 log.debug("Elided handling of connect2 in setTypeEdgeConnector"); 190 //connect1.setBumperEndStop(false); // temporary - is this being done in the view? 191 } 192 } 193 } 194 195 /** 196 * Provide the destination TrackSegment of the 1st connection. 197 * @return destination track segment 198 */ 199 public TrackSegment getConnect1() { 200 return connect1; 201 } 202 203 public void setConnect1(TrackSegment trk) { connect1 = trk; } 204 205 /** 206 * Provide the destination TrackSegment of the 2nd connection. 207 * When this is an EDGE CONNECTOR, it looks through the linked point (if any) 208 * to the far-end track connection. 209 * @return destination track segment 210 */ 211 public TrackSegment getConnect2() { 212 if (type == PointType.EDGE_CONNECTOR && getLinkedPoint() != null) { 213 return getLinkedPoint().getConnect1(); 214 } 215 return connect2; 216 } 217 218 /** 219 * Provide the destination TrackSegment of the 2nd connection 220 * without doing the look-through present in {@link #getConnect2()} 221 * @return destination track segment 222 */ 223 public TrackSegment getConnect2Actual() { 224 return connect2; 225 } 226 227 public void setConnect2Actual(TrackSegment trk) { connect2 = trk; } 228 229 230 private PositionablePoint linkedPoint; 231 232 public String getLinkedEditorName() { 233 if (getLinkedEditor() != null) { 234 return getLinkedEditor().getLayoutName(); 235 } 236 return ""; 237 } 238 239 public PositionablePoint getLinkedPoint() { 240 return linkedPoint; 241 } 242 243 public String getLinkedPointId() { 244 if (linkedPoint != null) { 245 return linkedPoint.getId(); 246 } 247 return ""; 248 } 249 250 public void setLinkedPoint(PositionablePoint p) { 251 if (p == linkedPoint) { 252 return; 253 } 254 if (linkedPoint != null) { 255 PositionablePoint oldLinkedPoint = linkedPoint; 256 linkedPoint = null; 257 if (oldLinkedPoint.getLinkedPoint() != null) { 258 oldLinkedPoint.setLinkedPoint(null); 259 } 260 if (oldLinkedPoint.getConnect1() != null) { 261 TrackSegment ts = oldLinkedPoint.getConnect1(); 262 oldLinkedPoint.getLayoutEditor().getLEAuxTools().setBlockConnectivityChanged(); 263 ts.updateBlockInfo(); 264 265 log.debug("temporary - repaint was removed here, needs to be rescoped"); 266 // oldLinkedPoint.getLayoutEditor().repaint(); 267 268 } 269 if (getConnect1() != null) { 270 models.getLEAuxTools().setBlockConnectivityChanged(); 271 getConnect1().updateBlockInfo(); 272 273 log.debug("temporary - repaint was removed here, needs to be rescoped"); 274 // models.repaint(); 275 } 276 } 277 linkedPoint = p; 278 if (p != null) { 279 p.setLinkedPoint(this); 280 if (getConnect1() != null) { 281 models.getLEAuxTools().setBlockConnectivityChanged(); 282 getConnect1().updateBlockInfo(); 283 284 log.debug("temporary - repaint was removed here, needs to be rescoped"); 285 // models.repaint(); 286 } 287 } 288 } 289 290 @CheckReturnValue 291 public LayoutEditor getLinkedEditor() { 292 if (getLinkedPoint() != null) { 293 return getLinkedPoint().getLayoutEditor(); 294 } 295 return null; 296 } 297 298 @CheckReturnValue 299 protected LayoutEditor getLayoutEditor() { 300 return models; 301 } 302 303 @CheckReturnValue 304 @Nonnull 305 public String getEastBoundSignal() { 306 SignalHead h = getEastBoundSignalHead(); 307 if (h != null) { 308 return h.getDisplayName(); 309 } 310 return ""; 311 } 312 313 @CheckForNull 314 @CheckReturnValue 315 public SignalHead getEastBoundSignalHead() { 316 if (getType() == PointType.EDGE_CONNECTOR) { 317 int dir = getConnect1Dir(); 318 if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) { 319 if (signalEastHeadNamed != null) { 320 return signalEastHeadNamed.getBean(); 321 } 322 return null; 323 } else if (getLinkedPoint() != null) { 324 // Do some checks to find where the connection is here. 325 int linkDir = getLinkedPoint().getConnect1Dir(); 326 if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) { 327 return getLinkedPoint().getEastBoundSignalHead(); 328 } 329 } 330 } 331 332 if (signalEastHeadNamed != null) { 333 return signalEastHeadNamed.getBean(); 334 } 335 return null; 336 } 337 338 public void setEastBoundSignal(String signalName) { 339 if (getType() == PointType.EDGE_CONNECTOR) { 340 int dir = getConnect1Dir(); 341 if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) { 342 setEastBoundSignalName(signalName); 343 } else if (getLinkedPoint() != null) { 344 int linkDir = getLinkedPoint().getConnect1Dir(); 345 if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) { 346 getLinkedPoint().setEastBoundSignal(signalName); 347 } else { 348 setEastBoundSignalName(signalName); 349 } 350 } else { 351 setEastBoundSignalName(signalName); 352 } 353 } else { 354 setEastBoundSignalName(signalName); 355 } 356 } 357 358 private void setEastBoundSignalName(@CheckForNull String signalHead) { 359 if (signalHead == null || signalHead.isEmpty()) { 360 signalEastHeadNamed = null; 361 return; 362 } 363 364 SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead); 365 if (head != null) { 366 signalEastHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head); 367 } else { 368 signalEastHeadNamed = null; 369 } 370 } 371 372 @CheckReturnValue 373 @Nonnull 374 public String getWestBoundSignal() { 375 SignalHead h = getWestBoundSignalHead(); 376 if (h != null) { 377 return h.getDisplayName(); 378 } 379 return ""; 380 } 381 382 @CheckForNull 383 @CheckReturnValue 384 public SignalHead getWestBoundSignalHead() { 385 if (getType() == PointType.EDGE_CONNECTOR) { 386 int dir = getConnect1Dir(); 387 if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) { 388 if (signalWestHeadNamed != null) { 389 return signalWestHeadNamed.getBean(); 390 } 391 return null; 392 } else if (getLinkedPoint() != null) { 393 // Do some checks to find where the connection is here. 394 int linkDir = getLinkedPoint().getConnect1Dir(); 395 if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) { 396 return getLinkedPoint().getWestBoundSignalHead(); 397 } 398 } 399 } 400 401 if (signalWestHeadNamed != null) { 402 return signalWestHeadNamed.getBean(); 403 } 404 return null; 405 } 406 407 public void setWestBoundSignal(String signalName) { 408 if (getType() == PointType.EDGE_CONNECTOR) { 409 int dir = getConnect1Dir(); 410 if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) { 411 setWestBoundSignalName(signalName); 412 } else if (getLinkedPoint() != null) { 413 int linkDir = getLinkedPoint().getConnect1Dir(); 414 if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) { 415 getLinkedPoint().setWestBoundSignal(signalName); 416 } else { 417 setWestBoundSignalName(signalName); 418 } 419 } else { 420 setWestBoundSignalName(signalName); 421 } 422 } else { 423 setWestBoundSignalName(signalName); 424 } 425 } 426 427 private void setWestBoundSignalName(@CheckForNull String signalHead) { 428 if (signalHead == null || signalHead.isEmpty()) { 429 signalWestHeadNamed = null; 430 return; 431 } 432 433 SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead); 434 if (head != null) { 435 signalWestHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head); 436 } else { 437 signalWestHeadNamed = null; 438 } 439 } 440 441 @CheckReturnValue 442 @Nonnull 443 public String getEastBoundSensorName() { 444 if (eastBoundSensorNamed != null) { 445 return eastBoundSensorNamed.getName(); 446 } 447 return ""; 448 } 449 450 @CheckReturnValue 451 public Sensor getEastBoundSensor() { 452 if (eastBoundSensorNamed != null) { 453 return eastBoundSensorNamed.getBean(); 454 } 455 return null; 456 } 457 458 public void setEastBoundSensor(String sensorName) { 459 if (sensorName == null || sensorName.isEmpty()) { 460 eastBoundSensorNamed = null; 461 return; 462 } 463 464 try { 465 Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName); 466 eastBoundSensorNamed = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor); 467 } catch (IllegalArgumentException ex) { 468 eastBoundSensorNamed = null; 469 } 470 } 471 472 @CheckReturnValue 473 @Nonnull 474 public String getWestBoundSensorName() { 475 if (westBoundSensorNamed != null) { 476 return westBoundSensorNamed.getName(); 477 } 478 return ""; 479 } 480 481 @CheckReturnValue 482 public Sensor getWestBoundSensor() { 483 if (westBoundSensorNamed != null) { 484 return westBoundSensorNamed.getBean(); 485 } 486 return null; 487 } 488 489 public void setWestBoundSensor(String sensorName) { 490 if (sensorName == null || sensorName.isEmpty()) { 491 westBoundSensorNamed = null; 492 return; 493 } 494 try { 495 Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName); 496 westBoundSensorNamed = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor); 497 } catch (IllegalArgumentException ex) { 498 westBoundSensorNamed = null; 499 } 500 } 501 502 @CheckReturnValue 503 @Nonnull 504 public String getEastBoundSignalMastName() { 505 if (getEastBoundSignalMastNamed() != null) { 506 return getEastBoundSignalMastNamed().getName(); 507 } 508 return ""; 509 } 510 511 @CheckReturnValue 512 public SignalMast getEastBoundSignalMast() { 513 if (getEastBoundSignalMastNamed() != null) { 514 return getEastBoundSignalMastNamed().getBean(); 515 } 516 return null; 517 } 518 519 @CheckReturnValue 520 public NamedBeanHandle<SignalMast> getEastBoundSignalMastNamed() { 521 if (getType() == PointType.EDGE_CONNECTOR) { 522 int dir = getConnect1Dir(); 523 if (dir == Path.SOUTH || dir == Path.EAST || dir == Path.SOUTH_EAST) { 524 return eastBoundSignalMastNamed; 525 } else if (getLinkedPoint() != null) { 526 int linkDir = getLinkedPoint().getConnect1Dir(); 527 if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) { 528 return getLinkedPoint().getEastBoundSignalMastNamed(); 529 } 530 } 531 } 532 return eastBoundSignalMastNamed; 533 } 534 535 public void setEastBoundSignalMast(String signalMast) { 536 SignalMast mast = null; 537 if (signalMast != null && !signalMast.isEmpty()) { 538 mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast); 539 if (mast == null) { 540 log.error("{}.setEastBoundSignalMast({}); Unable to find Signal Mast", 541 getName(), signalMast); 542 return; 543 } 544 } else { 545 eastBoundSignalMastNamed = null; 546 return; 547 } 548 if (getType() == PointType.EDGE_CONNECTOR) { 549 int dir = getConnect1Dir(); 550 if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) { 551 eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 552 } else if (getLinkedPoint() != null) { 553 int linkDir = getLinkedPoint().getConnect1Dir(); 554 if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) { 555 getLinkedPoint().setEastBoundSignalMast(signalMast); 556 } else { 557 eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 558 } 559 } else { 560 eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 561 } 562 } else { 563 eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 564 } 565 } 566 567 @CheckReturnValue 568 @Nonnull 569 public String getWestBoundSignalMastName() { 570 if (getWestBoundSignalMastNamed() != null) { 571 return getWestBoundSignalMastNamed().getName(); 572 } 573 return ""; 574 } 575 576 @CheckReturnValue 577 public SignalMast getWestBoundSignalMast() { 578 if (getWestBoundSignalMastNamed() != null) { 579 return getWestBoundSignalMastNamed().getBean(); 580 } 581 return null; 582 } 583 584 @CheckReturnValue 585 public NamedBeanHandle<SignalMast> getWestBoundSignalMastNamed() { 586 if (getType() == PointType.EDGE_CONNECTOR) { 587 int dir = getConnect1Dir(); 588 if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) { 589 return westBoundSignalMastNamed; 590 } else if (getLinkedPoint() != null) { 591 int linkDir = getLinkedPoint().getConnect1Dir(); 592 if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) { 593 return getLinkedPoint().getWestBoundSignalMastNamed(); 594 } 595 } 596 } 597 return westBoundSignalMastNamed; 598 } 599 600 public void setWestBoundSignalMast(String signalMast) { 601 SignalMast mast = null; 602 if (signalMast != null && !signalMast.isEmpty()) { 603 mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast); 604 if (mast == null) { 605 log.error("{}.setWestBoundSignalMast({}); Unable to find Signal Mast", 606 getName(), signalMast); 607 return; 608 } 609 } else { 610 westBoundSignalMastNamed = null; 611 return; 612 } 613 if (getType() == PointType.EDGE_CONNECTOR) { 614 int dir = getConnect1Dir(); 615 if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) { 616 westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 617 } else if (getLinkedPoint() != null) { 618 int linkDir = getLinkedPoint().getConnect1Dir(); 619 if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) { 620 getLinkedPoint().setWestBoundSignalMast(signalMast); 621 } else { 622 westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 623 } 624 } else { 625 westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 626 } 627 } else { 628 westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast); 629 } 630 } 631 632 public void removeBeanReference(jmri.NamedBean nb) { 633 if (nb == null) { 634 return; 635 } 636 if (nb instanceof SignalMast) { 637 if (nb.equals(getWestBoundSignalMast())) { 638 setWestBoundSignalMast(null); 639 } else if (nb.equals(getEastBoundSignalMast())) { 640 setEastBoundSignalMast(null); 641 } 642 } else if (nb instanceof Sensor) { 643 if (nb.equals(getWestBoundSensor())) { 644 setWestBoundSignalMast(null); 645 } else if (nb.equals(getEastBoundSensor())) { 646 setEastBoundSignalMast(null); 647 } 648 } else if (nb instanceof jmri.SignalHead) { 649 if (nb.equals(getWestBoundSignalHead())) { 650 setWestBoundSignal(null); 651 } 652 if (nb.equals(getEastBoundSignalHead())) { 653 setEastBoundSignal(null); 654 } 655 656 } 657 } 658 659 // initialization instance variables (used when loading a LayoutEditor) 660 public String trackSegment1Name = ""; 661 public String trackSegment2Name = ""; 662 663 /** 664 * Initialization method The above variables are initialized by 665 * PositionablePointXml, then the following method is called after the 666 * entire LayoutEditor is loaded to set the specific TrackSegment objects. 667 */ 668 @Override 669 public void setObjects(LayoutEditor p) { 670 if (type == PointType.EDGE_CONNECTOR) { 671 connect1 = p.getFinder().findTrackSegmentByName(trackSegment1Name); 672 if (getConnect2() != null && getLinkedEditor() != null) { 673 //now that we have a connection we can fire off a change 674 TrackSegment ts = getConnect2(); 675 getLinkedEditor().getLEAuxTools().setBlockConnectivityChanged(); 676 ts.updateBlockInfo(); 677 } 678 } else { 679 connect1 = p.getFinder().findTrackSegmentByName(trackSegment1Name); 680 connect2 = p.getFinder().findTrackSegmentByName(trackSegment2Name); 681 } 682 log.trace("PositionablePoint:setObjects {}: {} and {} {}", trackSegment1Name, connect1, trackSegment1Name, connect1); 683 } 684 685 /** 686 * setup a connection to a track 687 * 688 * @param track the track we want to connect to 689 * @return true if successful 690 */ 691 public boolean setTrackConnection(@Nonnull TrackSegment track) { 692 return replaceTrackConnection(null, track); 693 } 694 695 /** 696 * remove a connection to a track 697 * 698 * @param track the track we want to disconnect from 699 * @return true if successful 700 */ 701 public boolean removeTrackConnection(@Nonnull TrackSegment track) { 702 return replaceTrackConnection(track, null); 703 } 704 705 /** 706 * replace old track connection with new track connection 707 * 708 * @param oldTrack the old track connection 709 * @param newTrack the new track connection 710 * @return true if successful 711 */ 712 public boolean replaceTrackConnection(@CheckForNull TrackSegment oldTrack, @CheckForNull TrackSegment newTrack) { 713 boolean result = false; // assume failure (pessimist!) 714 // trying to replace old track with null? 715 if (newTrack == null) { 716 // (yes) remove old connection 717 if (oldTrack != null) { 718 result = true; // assume success (optimist!) 719 if (connect1 == oldTrack) { 720 connect1 = null; // disconnect connect1 721 reCheckBlockBoundary(); 722 removeLinkedPoint(); 723 connect1 = connect2; // Move connect2 to connect1 724 connect2 = null; // disconnect connect2 725 } else if (connect2 == oldTrack) { 726 connect2 = null; 727 reCheckBlockBoundary(); 728 } else { 729 result = false; // didn't find old connection 730 } 731 } else { 732 result = false; // can't replace null with null 733 } 734 if (!result) { 735 log.error("{}.replaceTrackConnection({}, {}); Attempt to remove non-existant track connection", 736 getName(), (oldTrack == null) ? "null" : oldTrack.getName(), "null"); 737 } 738 } else // already connected to newTrack? 739 if ((connect1 != newTrack) && (connect2 != newTrack)) { 740 // (no) find a connection we can connect to 741 result = true; // assume success (optimist!) 742 if (connect1 == oldTrack) { 743 connect1 = newTrack; 744 } else if ((type == PointType.ANCHOR) && (connect2 == oldTrack)) { 745 connect2 = newTrack; 746 if (connect1.getLayoutBlock() == connect2.getLayoutBlock()) { 747 westBoundSignalMastNamed = null; 748 eastBoundSignalMastNamed = null; 749 setWestBoundSensor(""); 750 setEastBoundSensor(""); 751 } 752 } else { 753 log.error("{}.replaceTrackConnection({}, {}); Attempt to assign more than allowed number of connections", 754 getName(), (oldTrack == null) ? "null" : oldTrack.getName(), newTrack.getName()); 755 result = false; 756 } 757 } else { 758 log.warn("{}.replaceTrackConnection({}, {}); Already connected", 759 getName(), (oldTrack == null) ? "null" : oldTrack.getName(), newTrack.getName()); 760 result = false; 761 } 762 return result; 763 } 764 765 void removeSML(SignalMast signalMast) { 766 if (signalMast == null) { 767 return; 768 } 769 if (jmri.InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled() && InstanceManager.getDefault(jmri.SignalMastLogicManager.class).isSignalMastUsed(signalMast)) { 770 SignallingGuiTools.removeSignalMastLogic(null, signalMast); 771 } 772 } 773 774 /** 775 * {@inheritDoc} 776 */ 777 @Override 778 public boolean canRemove() { 779 List<String> itemList = new ArrayList<>(); 780 // A has two track segments, EB has one, EC has one plus optional link 781 782 TrackSegment ts1 = getConnect1(); 783 TrackSegment ts2 = getConnect2(); 784 785 if (ts1 != null) { 786 itemList.addAll(getSegmentReferences(ts1)); 787 } 788 if (ts2 != null) { 789 for (String item : getSegmentReferences(ts2)) { 790 // Do not add duplicates 791 if (!itemList.contains(item)) { 792 itemList.add(item); 793 } 794 } 795 } 796 797 if (!itemList.isEmpty()) { 798 String typeName = ""; 799 switch (type) { 800 case ANCHOR: 801 typeName = "Anchor"; // NOI18N 802 break; 803 case END_BUMPER: 804 typeName = "EndBumper"; // NOI18N 805 break; 806 case EDGE_CONNECTOR: 807 typeName = "EdgeConnector"; // NOI18N 808 break; 809 default: 810 typeName = "Unknown type (" + type + ")"; // NOI18N 811 break; 812 } 813 models.displayRemoveWarning(this, itemList, typeName); 814 } 815 return itemList.isEmpty(); 816 } 817 818 /** 819 * Build a list of sensors, signal heads, and signal masts attached to a 820 * connection point. 821 * 822 * @param ts The track segment to be checked. 823 * @return a list of bean reference names. 824 */ 825 public ArrayList<String> getSegmentReferences(TrackSegment ts) { 826 ArrayList<String> items = new ArrayList<>(); 827 828 HitPointType type1 = ts.getType1(); 829 LayoutTrack conn1 = ts.getConnect1(); 830 items.addAll(ts.getPointReferences(type1, conn1)); 831 832 HitPointType type2 = ts.getType2(); 833 LayoutTrack conn2 = ts.getConnect2(); 834 items.addAll(ts.getPointReferences(type2, conn2)); 835 836 return items; 837 } 838 839 void removeLinkedPoint() { 840 if (type == PointType.EDGE_CONNECTOR && getLinkedPoint() != null) { 841 if (getConnect2() != null && getLinkedEditor() != null) { 842 //as we have removed the point, need to force the update on the remote end. 843 LayoutEditor oldLinkedEditor = getLinkedEditor(); 844 TrackSegment ts = getConnect2(); 845 getLinkedPoint().setLinkedPoint(null); 846 847 log.debug("temporary - repaint was removed here, needs to be rescoped"); 848 // oldLinkedEditor.repaint(); 849 850 oldLinkedEditor.getLEAuxTools().setBlockConnectivityChanged(); 851 ts.updateBlockInfo(); 852 } 853 linkedPoint = null; 854 } 855 } 856 857 /** 858 * Removes this object from display and persistence 859 */ 860 public void remove() { // temporary public instead of private for migration 861 // remove from persistence by flagging inactive 862 active = false; 863 } 864 865 private boolean active = true; 866 867 /** 868 * "active" means that the object is still displayed, and should be stored. 869 * @return true if active 870 */ 871 protected boolean isActive() { 872 return active; 873 } 874 875 protected int getConnect1Dir() { 876 int result = Path.NONE; 877 878 TrackSegment ts1 = getConnect1(); 879 if (ts1 != null) { 880 if (ts1.getConnect1() == this) { 881 result = models.computeDirectionFromCenter(this, ts1.getConnect2(), ts1.getType2()); 882 } else { 883 result = models.computeDirectionFromCenter(this, ts1.getConnect1(), ts1.getType1()); 884 } 885 } 886 return result; 887 } 888 889 /** 890 * {@inheritDoc} 891 */ 892 @Override 893 public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException { 894 LayoutTrack result = null; 895 if (connectionType == HitPointType.POS_POINT) { 896 result = getConnect1(); 897 if (null == result) { 898 result = getConnect2(); 899 } 900 } else { 901 String errString = MessageFormat.format("{0}.getConnection({1}); Invalid Connection Type", 902 getName(), connectionType); //I18IN 903 log.error("will throw {}", errString); 904 throw new jmri.JmriException(errString); 905 } 906 return result; 907 } 908 909 /** 910 * {@inheritDoc} 911 */ 912 @Override 913 public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException { 914 if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) { 915 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); unexpected type", 916 getName(), connectionType, (o == null) ? "null" : o.getName(), type); //I18IN 917 log.error("will throw {}", errString); //I18IN 918 throw new jmri.JmriException(errString); 919 } 920 if (connectionType != HitPointType.POS_POINT) { 921 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid Connection Type", 922 getName(), connectionType, (o == null) ? "null" : o.getName(), type); //I18IN 923 log.error("will throw {}", errString); //I18IN 924 throw new jmri.JmriException(errString); 925 } 926 } 927 928 /** 929 * return true if this connection type is disconnected 930 * 931 * @param connectionType the connection type to test 932 * @return true if the connection for this connection type is free 933 */ 934 @Override 935 public boolean isDisconnected(HitPointType connectionType) { 936 boolean result = false; 937 if (connectionType == HitPointType.POS_POINT) { 938 result = ((getConnect1() == null) || (getConnect2() == null)); 939 } else { 940 log.error("{}.isDisconnected({}); Invalid Connection Type", 941 getName(), connectionType); //I18IN 942 } 943 return result; 944 } 945 946 @Override 947 public boolean isMainline() { 948 boolean result = false; // assume failure (pessimist!) 949 if (getConnect1() != null) { 950 result = getConnect1().isMainline(); 951 } 952 if (getType() == PointType.ANCHOR) { 953 if (getConnect2() != null) { 954 result |= getConnect2().isMainline(); 955 } 956 } 957 return result; 958 } 959 960 /** 961 * {@inheritDoc} 962 */ 963 @Override 964 public void reCheckBlockBoundary() { 965 if (type == PointType.END_BUMPER) { 966 return; 967 } 968 if (getConnect1() == null && getConnect2() == null) { 969 //This is no longer a block boundary, therefore will remove signal masts and sensors if present 970 if (westBoundSignalMastNamed != null) { 971 removeSML(getWestBoundSignalMast()); 972 } 973 if (eastBoundSignalMastNamed != null) { 974 removeSML(getEastBoundSignalMast()); 975 } 976 westBoundSignalMastNamed = null; 977 eastBoundSignalMastNamed = null; 978 setWestBoundSensor(""); 979 setEastBoundSensor(""); 980 //TODO: May want to look at a method to remove the assigned mast 981 //from the panel and potentially any SignalMast logics generated 982 } else if (getConnect1() == null || getConnect2() == null) { 983 //could still be in the process of rebuilding the point details 984 } else if (getConnect1().getLayoutBlock() == getConnect2().getLayoutBlock()) { 985 //We are no longer a block bounardy 986 if (westBoundSignalMastNamed != null) { 987 removeSML(getWestBoundSignalMast()); 988 } 989 if (eastBoundSignalMastNamed != null) { 990 removeSML(getEastBoundSignalMast()); 991 } 992 westBoundSignalMastNamed = null; 993 eastBoundSignalMastNamed = null; 994 setWestBoundSensor(""); 995 setEastBoundSensor(""); 996 //TODO: May want to look at a method to remove the assigned mast 997 //from the panel and potentially any SignalMast logics generated 998 } 999 } // reCheckBlockBoundary 1000 1001 /** 1002 * {@inheritDoc} 1003 */ 1004 // 1005 // This uses a getCoords() call, and having that at this level 1006 // has to be temporary 1007 // 1008 @Override 1009 protected List<LayoutConnectivity> getLayoutConnectivity() { 1010 List<LayoutConnectivity> results = new ArrayList<>(); 1011 LayoutConnectivity lc = null; 1012 LayoutBlock blk1 = null, blk2 = null; 1013 TrackSegment ts1 = getConnect1(); 1014 1015 if (getType() == PointType.ANCHOR) { 1016 TrackSegment ts2 = getConnect2(); 1017 if ((ts1 != null) && (ts2 != null)) { 1018 blk1 = ts1.getLayoutBlock(); 1019 blk2 = ts2.getLayoutBlock(); 1020 if ((blk1 != null) && (blk2 != null) && (blk1 != blk2)) { 1021 // this is a block boundary, create a LayoutConnectivity 1022 log.debug("Block boundary (''{}''<->''{}'') found at {}", blk1, blk2, this); 1023 lc = new LayoutConnectivity(blk1, blk2); 1024 1025 // determine direction from block 1 to block 2 1026 lc.setDirection( 1027 models.computeDirection( 1028 ts1.getConnect1() == this ? ts1.getConnect2() : ts1.getConnect1(), 1029 ts1.getConnect1() == this ? ts1.getType2() : ts1.getType1(), 1030 1031 ts2.getConnect1() == this ? ts2.getConnect2() : ts2.getConnect1(), 1032 ts2.getConnect1() == this ? ts2.getType2() : ts2.getType1() 1033 ) 1034 ); 1035 1036 // save Connections 1037 lc.setConnections(ts1, ts2, HitPointType.TRACK, this); 1038 results.add(lc); 1039 } 1040 } 1041 } else if (getType() == PointType.EDGE_CONNECTOR) { 1042 TrackSegment ts2 = null; 1043 if (getLinkedPoint() != null) { 1044 ts2 = getLinkedPoint().getConnect1(); 1045 } 1046 if ((ts1 != null) && (ts2 != null)) { 1047 blk1 = ts1.getLayoutBlock(); 1048 blk2 = ts2.getLayoutBlock(); 1049 if ((blk1 != null) && (blk2 != null) && (blk1 != blk2)) { 1050 // this is a block boundary, create a LayoutConnectivity 1051 log.debug("Block boundary (''{}''<->''{}'') found at {}", blk1, blk2, this); 1052 lc = new LayoutConnectivity(blk1, blk2); 1053 1054 // determine direction from block 1 to block 2 1055 int result; 1056 1057 if (ts1.getConnect1() == this) { 1058 result = models.computeDirectionToCenter(ts1.getConnect2(), ts1.getType2(), this); 1059 } else { 1060 result = models.computeDirectionToCenter(ts1.getConnect1(), ts1.getType1(), this); 1061 } 1062 1063 //Need to find a way to compute the direction for this for a split over the panel 1064 //In this instance work out the direction of the first track relative to the positionable poin. 1065 lc.setDirection(result); 1066 // save Connections 1067 lc.setConnections(ts1, ts2, HitPointType.TRACK, this); 1068 results.add(lc); 1069 } 1070 } 1071 } 1072 return results; 1073 } // getLayoutConnectivity() 1074 1075 /** 1076 * {@inheritDoc} 1077 */ 1078 @Override 1079 public List<HitPointType> checkForFreeConnections() { 1080 List<HitPointType> result = new ArrayList<>(); 1081 1082 if ((getConnect1() == null) 1083 || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) { 1084 result.add(HitPointType.POS_POINT); 1085 } 1086 return result; 1087 } 1088 1089 /** 1090 * {@inheritDoc} 1091 */ 1092 @Override 1093 public boolean checkForUnAssignedBlocks() { 1094 // Positionable Points don't have blocks so... 1095 // nothing to see here... move along... 1096 return true; 1097 } 1098 1099 /** 1100 * {@inheritDoc} 1101 */ 1102 @Override 1103 public void checkForNonContiguousBlocks( 1104 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 1105 /* 1106 * For each (non-null) blocks of this track do: 1107 * #1) If it's got an entry in the blockNamesToTrackNameSetMap then 1108 * #2) If this track is not in one of the TrackNameSets for this block 1109 * #3) add a new set (with this block/track) to 1110 * blockNamesToTrackNameSetMap and 1111 * #4) check all the connections in this 1112 * block (by calling the 2nd method below) 1113 * <p> 1114 * Basically, we're maintaining contiguous track sets for each block found 1115 * (in blockNamesToTrackNameSetMap) 1116 */ 1117 //check the 1st connection points block 1118 TrackSegment ts1 = getConnect1(); 1119 String blk1 = null; 1120 List<Set<String>> TrackNameSets = null; 1121 Set<String> TrackNameSet = null; // assume not found (pessimist!) 1122 1123 // this should never be null... but just in case... 1124 if (ts1 != null) { 1125 blk1 = ts1.getBlockName(); 1126 if (!blk1.isEmpty()) { 1127 TrackNameSets = blockNamesToTrackNameSetsMap.get(blk1); 1128 if (TrackNameSets != null) { // (#1) 1129 for (Set<String> checkTrackNameSet : TrackNameSets) { 1130 if (checkTrackNameSet.contains(getName())) { // (#2) 1131 TrackNameSet = checkTrackNameSet; 1132 break; 1133 } 1134 } 1135 } else { // (#3) 1136 log.debug("*New block (''{}'') trackNameSets", blk1); 1137 TrackNameSets = new ArrayList<>(); 1138 blockNamesToTrackNameSetsMap.put(blk1, TrackNameSets); 1139 } 1140 if (TrackNameSet == null) { 1141 TrackNameSet = new LinkedHashSet<>(); 1142 log.debug("* Add track ''{}'' to trackNameSet for block ''{}''", getName(), blk1); 1143 TrackNameSet.add(getName()); 1144 TrackNameSets.add(TrackNameSet); 1145 } 1146 if (connect1 != null) { // (#4) 1147 connect1.collectContiguousTracksNamesInBlockNamed(blk1, TrackNameSet); 1148 } 1149 } 1150 } 1151 1152 if (getType() == PointType.ANCHOR) { 1153 //check the 2nd connection points block 1154 TrackSegment ts2 = getConnect2(); 1155 // this should never be null... but just in case... 1156 if (ts2 != null) { 1157 String blk2 = ts2.getBlockName(); 1158 if (!blk2.isEmpty()) { 1159 TrackNameSet = null; // assume not found (pessimist!) 1160 TrackNameSets = blockNamesToTrackNameSetsMap.get(blk2); 1161 if (TrackNameSets != null) { // (#1) 1162 for (Set<String> checkTrackNameSet : TrackNameSets) { 1163 if (checkTrackNameSet.contains(getName())) { // (#2) 1164 TrackNameSet = checkTrackNameSet; 1165 break; 1166 } 1167 } 1168 } else { // (#3) 1169 log.debug("*New block (''{}'') trackNameSets", blk2); 1170 TrackNameSets = new ArrayList<>(); 1171 blockNamesToTrackNameSetsMap.put(blk2, TrackNameSets); 1172 } 1173 if (TrackNameSet == null) { 1174 TrackNameSet = new LinkedHashSet<>(); 1175 log.debug("* Add track ''{}'' to TrackNameSet for block ''{}''", getName(), blk2); 1176 TrackNameSets.add(TrackNameSet); 1177 TrackNameSet.add(getName()); 1178 } 1179 if (connect2 != null) { // (#4) 1180 connect2.collectContiguousTracksNamesInBlockNamed(blk2, TrackNameSet); 1181 } 1182 } 1183 } 1184 } 1185 } // collectContiguousTracksNamesInBlockNamed 1186 1187 /** 1188 * {@inheritDoc} 1189 */ 1190 @Override 1191 public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName, 1192 @Nonnull Set<String> TrackNameSet) { 1193 if (!TrackNameSet.contains(getName())) { 1194 TrackSegment ts1 = getConnect1(); 1195 // this should never be null... but just in case... 1196 if (ts1 != null) { 1197 String blk1 = ts1.getBlockName(); 1198 // is this the blockName we're looking for? 1199 if (blk1.equals(blockName)) { 1200 // if we are added to the TrackNameSet 1201 if (TrackNameSet.add(getName())) { 1202 log.debug("* Add track ''{}''for block ''{}''", getName(), blockName); 1203 } 1204 // this should never be null... but just in case... 1205 if (connect1 != null) { 1206 connect1.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 1207 } 1208 } 1209 } 1210 if (getType() == PointType.ANCHOR) { 1211 TrackSegment ts2 = getConnect2(); 1212 // this should never be null... but just in case... 1213 if (ts2 != null) { 1214 String blk2 = ts2.getBlockName(); 1215 // is this the blockName we're looking for? 1216 if (blk2.equals(blockName)) { 1217 // if we are added to the TrackNameSet 1218 if (TrackNameSet.add(getName())) { 1219 log.debug("* Add track ''{}''for block ''{}''", getName(), blockName); 1220 } 1221 // this should never be null... but just in case... 1222 if (connect2 != null) { 1223 // it's time to play... flood your neighbour! 1224 connect2.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet); 1225 } 1226 } 1227 } 1228 } 1229 } 1230 } 1231 1232 /** 1233 * {@inheritDoc} 1234 */ 1235 @Override 1236 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 1237 // positionable points don't have blocks... 1238 // nothing to see here, move along... 1239 } 1240 1241 /** 1242 * {@inheritDoc} 1243 */ 1244 @Override 1245 public String getTypeName() { 1246 return Bundle.getMessage("TypeName_PositionablePoint"); 1247 } 1248 1249 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PositionablePoint.class); 1250 1251}