001package jmri.jmrit.display; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.util.HashMap; 006 007import javax.annotation.Nonnull; 008import javax.swing.AbstractAction; 009import javax.swing.JMenuItem; 010import javax.swing.JPopupMenu; 011 012import jmri.InstanceManager; 013import jmri.NamedBeanHandle; 014import jmri.Turnout; 015import jmri.jmrit.catalog.NamedIcon; 016import jmri.util.swing.JmriMouseEvent; 017 018import static jmri.NamedBean.INCONSISTENT; 019import static jmri.NamedBean.UNKNOWN; 020import static jmri.Turnout.CLOSED; 021import static jmri.Turnout.THROWN; 022 023/** 024 * An icon to display a status of a Slip, either Single or Double.<p> 025 * This responds to only KnownState, leaving CommandedState to some other 026 * graphic representation later. 027 * <p> 028 * A click on the icon will command a state change. Specifically, it will set 029 * the CommandedState to the opposite (THROWN vs CLOSED) of the current 030 * KnownState. 031 * <p> 032 * Note: lower west to lower east icon is used for storing the slip icon, in a 033 * single slip, even if the slip is set for upper west to upper east. 034 * <p> 035 * With a 3-Way point we use the following translations 036 * <ul> 037 * <li>lower west to upper east - to upper exit 038 * <li>upper west to lower east - to middle exit 039 * <li>lower west to lower east - to lower exit 040 * <li>west Turnout - First Turnout 041 * <li>east Turnout - Second Turnout 042 * <li>singleSlipRoute - translates to which exit the first turnout goes to 043 * <li>true if upper, or false if lower 044 * </ul> 045 * <p> 046 * Based upon the TurnoutIcon by Bob Jacobsen 047 * 048 * @author Kevin Dickerson Copyright (c) 2010 049 */ 050public class SlipTurnoutIcon extends PositionableLabel implements java.beans.PropertyChangeListener { 051 052 public SlipTurnoutIcon(Editor editor) { 053 // super ctor call to make sure this is an icon label 054 super(new NamedIcon("resources/icons/smallschematics/tracksegments/os-slip-lower-west-upper-east.gif", 055 "resources/icons/smallschematics/tracksegments/os-slip-lower-west-upper-east.gif"), editor); 056 _control = true; 057 displayState(turnoutState()); 058 setPopupUtility(null); 059 } 060 061 // the associated Turnout objects 062 private NamedBeanHandle<Turnout> namedTurnoutWest = null; 063 private NamedBeanHandle<Turnout> namedTurnoutWestLower = null; 064 private NamedBeanHandle<Turnout> namedTurnoutEast = null; 065 private NamedBeanHandle<Turnout> namedTurnoutEastLower = null; 066 067 /** 068 * Attach a named turnout to this display item. 069 * 070 * @param pName Used as a system/user name to lookup the turnout object 071 * @param turn is used to determine which turnout position this is for. 072 * 0x01 - West 0x02 - East 0x04 - Lower West 0x06 - Upper East 073 */ 074 public void setTurnout(String pName, int turn) { 075 if (InstanceManager.getNullableDefault(jmri.TurnoutManager.class) != null) { 076 try { 077 Turnout turnout = InstanceManager.turnoutManagerInstance(). 078 provideTurnout(pName); 079 setTurnout(InstanceManager.getDefault(jmri.NamedBeanHandleManager.class) 080 .getNamedBeanHandle(pName, turnout), turn); 081 } catch (IllegalArgumentException e) { 082 log.error("Turnout '{}' not available, icon won't see changes", pName); 083 } 084 } else { 085 log.error("No TurnoutManager for this protocol, icon won't see changes"); 086 } 087 } 088 089 /** 090 * Attach a namedBean Handle turnout to this display item. 091 * 092 * @param to Used as the NamedBeanHandle to lookup the turnout object 093 * @param turn is used to determine which turnout position this is for. 094 * <ul> 095 * <li>0x01 - West 096 * <li>0x02 - East 097 * <li>0x04 - Lower West 098 * <li>0x06 - Upper East 099 * </ul> 100 */ 101 public void setTurnout(NamedBeanHandle<Turnout> to, int turn) { 102 switch (turn) { 103 case WEST: 104 if (namedTurnoutWest != null) { 105 getTurnout(WEST).removePropertyChangeListener(this); 106 } 107 namedTurnoutWest = to; 108 if (namedTurnoutWest != null) { 109 displayState(turnoutState()); 110 getTurnout(WEST).addPropertyChangeListener(this, namedTurnoutWest.getName(), "Panel Editor Turnout"); 111 } 112 break; 113 case EAST: 114 if (namedTurnoutEast != null) { 115 getTurnout(EAST).removePropertyChangeListener(this); 116 } 117 namedTurnoutEast = to; 118 if (namedTurnoutEast != null) { 119 displayState(turnoutState()); 120 getTurnout(EAST).addPropertyChangeListener(this, namedTurnoutEast.getName(), "Panel Editor Turnout"); 121 } 122 break; 123 case LOWERWEST: 124 if (namedTurnoutWestLower != null) { 125 getTurnout(LOWERWEST).removePropertyChangeListener(this); 126 } 127 namedTurnoutWestLower = to; 128 if (namedTurnoutWestLower != null) { 129 displayState(turnoutState()); 130 getTurnout(LOWERWEST).addPropertyChangeListener(this, namedTurnoutWestLower.getName(), "Panel Editor Turnout"); 131 } 132 break; 133 case LOWEREAST: 134 if (namedTurnoutEastLower != null) { 135 getTurnout(LOWEREAST).removePropertyChangeListener(this); 136 } 137 namedTurnoutEastLower = to; 138 if (namedTurnoutEastLower != null) { 139 displayState(turnoutState()); 140 getTurnout(LOWEREAST).addPropertyChangeListener(this, namedTurnoutEastLower.getName(), "Panel Editor Turnout"); 141 } 142 break; 143 default: 144 log.error("turn value {} should not have appeared", turn); 145 } 146 } 147 148 /** 149 * Constant used to refer to the Turnout address configured to operate 150 * the west (or first for a three way) of the Turnout. 151 */ 152 @SuppressWarnings("hiding") // Hides a value from Swing Constants 153 public static final int WEST = 0x01; 154 155 /** 156 * Constant used to refer to the Turnout address configured to operate 157 * the east (or second for a three way) of the Turnout. 158 */ 159 @SuppressWarnings("hiding") // Hides a value from Swing Constants 160 public static final int EAST = 0x02; 161 162 /** 163 * Constant used for a scissor crossing using 4 turnout address, and refers 164 * to the turnout located at the lower west. 165 */ 166 public static final int LOWERWEST = 0x04; 167 168 /** 169 * Constant used for a scissor crossing using 4 turnout address, and refers 170 * to the turnout located at the lower east. 171 */ 172 public static final int LOWEREAST = 0x06; 173 174 /** 175 * Constant used to refer to a Double Slip Configuration. 176 */ 177 public static final int DOUBLESLIP = 0x00; 178 179 /** 180 * Constant used to refer to a Single Slip Configuration. 181 */ 182 public static final int SINGLESLIP = 0x02; 183 184 /** 185 * Constant used to refer to a Three Way Turnout Configuration. 186 */ 187 public static final int THREEWAY = 0x04; 188 189 /** 190 * Constant used to refer to a Scissor (Double Crossover) Configuration. 191 */ 192 public static final int SCISSOR = 0x08; 193 194 //true for double slip, false for single. 195 private int turnoutType = DOUBLESLIP; 196 197 /** 198 * Sets the type of turnout configuration which is being used 199 * 200 * @param slip valid values are 201 * <ul> 202 * <li>0x00 - Double Slip 203 * <li>0x02 - Single Slip 204 * <li>0x04 - Three Way Turnout 205 * <li>0x08 - Scissor Crossing 206 * </ul> 207 */ 208 public void setTurnoutType(int slip) { 209 turnoutType = slip; 210 } 211 212 public int getTurnoutType() { 213 return turnoutType; 214 } 215 216 private boolean singleSlipRoute = false; 217 //static boolean LOWERWESTtoLOWEREAST = false; 218 //static boolean UPPERWESTtoUPPEREAST = true; 219 220 /** 221 * Single Slip Route, determines if the slip route is from upper west to 222 * upper east (true) or lower west to lower east (false) This also doubles 223 * up for the three way and determines if the first turnout routes to the 224 * upper (true) or lower (false) exit point. 225 * <p> 226 * In a Scissor crossing this returns true if only two turnout address are 227 * required to set the crossing or false if four turnout address are 228 * required. 229 * 230 * @return true if route is through the turnout on a slip; false otherwise 231 */ 232 public boolean getSingleSlipRoute() { 233 return singleSlipRoute; 234 } 235 236 public void setSingleSlipRoute(boolean route) { 237 singleSlipRoute = route; 238 } 239 240 /** 241 * Returns the turnout located at the position specified. 242 * 243 * @param turn One of {@link #EAST}, {@link #WEST}, {@link #LOWEREAST}, or 244 * {@link #LOWERWEST} 245 * @return the turnout at turn or null if turn is not a known constant or no 246 * turnout is at the position turn 247 */ 248 public Turnout getTurnout(int turn) { 249 switch (turn) { 250 case EAST: 251 return namedTurnoutEast.getBean(); 252 case WEST: 253 return namedTurnoutWest.getBean(); 254 case LOWEREAST: 255 return namedTurnoutEastLower.getBean(); 256 case LOWERWEST: 257 return namedTurnoutWestLower.getBean(); 258 default: 259 return null; 260 } 261 } 262 263 /** 264 * Returns the turnout located at the position specified. 265 * 266 * @param turn One of {@link #EAST}, {@link #WEST}, {@link #LOWEREAST}, or 267 * {@link #LOWERWEST} 268 * @return the handle for the turnout at turn or null if turn is not a known 269 * constant or no turnout is at the position turn 270 */ 271 public NamedBeanHandle<Turnout> getNamedTurnout(int turn) { 272 switch (turn) { 273 case EAST: 274 return namedTurnoutEast; 275 case WEST: 276 return namedTurnoutWest; 277 case LOWEREAST: 278 return namedTurnoutEastLower; 279 case LOWERWEST: 280 return namedTurnoutWestLower; 281 default: 282 return null; 283 } 284 } 285 286 /* 287 Note: lower west to lower east icon is used for storing the slip icon, in a single slip, 288 even if the slip is set for upper west to upper east. 289 290 With a 3-Way point we use the following translations 291 292 lower west to upper east - to upper exit 293 upper west to lower east - to middle exit 294 lower west to lower east - to lower exit 295 296 With a Scissor Crossing we use the following to represent straight 297 lower west to lower east 298 upper west to upper east 299 */ 300 // display icons 301 String lowerWestToUpperEastLName = "resources/icons/smallschematics/tracksegments/os-slip-lower-west-upper-east.gif"; 302 NamedIcon lowerWestToUpperEast = new NamedIcon(lowerWestToUpperEastLName, lowerWestToUpperEastLName); 303 String upperWestToLowerEastLName = "resources/icons/smallschematics/tracksegments/os-slip-upper-west-lower-east.gif"; 304 NamedIcon upperWestToLowerEast = new NamedIcon(upperWestToLowerEastLName, upperWestToLowerEastLName); 305 String lowerWestToLowerEastLName = "resources/icons/smallschematics/tracksegments/os-slip-lower-west-lower-east.gif"; 306 NamedIcon lowerWestToLowerEast = new NamedIcon(lowerWestToLowerEastLName, lowerWestToLowerEastLName); 307 String upperWestToUpperEastLName = "resources/icons/smallschematics/tracksegments/os-slip-upper-west-upper-east.gif"; 308 NamedIcon upperWestToUpperEast = new NamedIcon(upperWestToUpperEastLName, upperWestToUpperEastLName); 309 String inconsistentLName = "resources/icons/smallschematics/tracksegments/os-slip-error-full.gif"; 310 NamedIcon inconsistent = new NamedIcon(inconsistentLName, inconsistentLName); 311 String unknownLName = "resources/icons/smallschematics/tracksegments/os-slip-unknown-full.gif"; 312 NamedIcon unknown = new NamedIcon(unknownLName, unknownLName); 313 314 public NamedIcon getLowerWestToUpperEastIcon() { 315 return lowerWestToUpperEast; 316 } 317 318 public void setLowerWestToUpperEastIcon(NamedIcon i) { 319 lowerWestToUpperEast = i; 320 displayState(turnoutState()); 321 } 322 323 public NamedIcon getUpperWestToLowerEastIcon() { 324 return upperWestToLowerEast; 325 } 326 327 public void setUpperWestToLowerEastIcon(NamedIcon i) { 328 upperWestToLowerEast = i; 329 displayState(turnoutState()); 330 } 331 332 public NamedIcon getLowerWestToLowerEastIcon() { 333 return lowerWestToLowerEast; 334 } 335 336 public void setLowerWestToLowerEastIcon(NamedIcon i) { 337 lowerWestToLowerEast = i; 338 displayState(turnoutState()); 339 /*Only a double slip needs the fourth icon, we therefore set the upper west to upper east icon 340 to be the same as the lower west to upper wast icon*/ 341 if (turnoutType != DOUBLESLIP) { 342 setUpperWestToUpperEastIcon(i); 343 } 344 } 345 346 public NamedIcon getUpperWestToUpperEastIcon() { 347 return upperWestToUpperEast; 348 } 349 350 public void setUpperWestToUpperEastIcon(NamedIcon i) { 351 upperWestToUpperEast = i; 352 displayState(turnoutState()); 353 } 354 355 public NamedIcon getInconsistentIcon() { 356 return inconsistent; 357 } 358 359 public void setInconsistentIcon(NamedIcon i) { 360 inconsistent = i; 361 displayState(turnoutState()); 362 } 363 364 public NamedIcon getUnknownIcon() { 365 return unknown; 366 } 367 368 public void setUnknownIcon(NamedIcon i) { 369 unknown = i; 370 displayState(turnoutState()); 371 } 372 373 @Override 374 public int maxHeight() { 375 return Math.max( 376 Math.max((lowerWestToUpperEast != null) ? lowerWestToUpperEast.getIconHeight() : 0, 377 (upperWestToLowerEast != null) ? upperWestToLowerEast.getIconHeight() : 0), 378 Math.max( 379 Math.max((upperWestToUpperEast != null) ? upperWestToUpperEast.getIconHeight() : 0, 380 (lowerWestToLowerEast != null) ? lowerWestToLowerEast.getIconHeight() : 0), 381 Math.max((unknown != null) ? unknown.getIconHeight() : 0, 382 (inconsistent != null) ? inconsistent.getIconHeight() : 0)) 383 ); 384 } 385 386 @Override 387 public int maxWidth() { 388 return Math.max( 389 Math.max((lowerWestToUpperEast != null) ? lowerWestToUpperEast.getIconWidth() : 0, 390 (upperWestToLowerEast != null) ? upperWestToLowerEast.getIconWidth() : 0), 391 Math.max( 392 Math.max((upperWestToUpperEast != null) ? upperWestToUpperEast.getIconWidth() : 0, 393 (lowerWestToLowerEast != null) ? lowerWestToLowerEast.getIconWidth() : 0), 394 Math.max((unknown != null) ? unknown.getIconWidth() : 0, 395 (inconsistent != null) ? inconsistent.getIconWidth() : 0)) 396 ); 397 } 398 399 /** 400 * Get current state of attached turnouts. This adds the two turnout states 401 * together, however for the second turnout configured it will add 1 to the 402 * Closed state and 3 to the Thrown state. This helps to identify which 403 * turnout is thrown and/or closed. 404 * <p> 405 * For a Scissor crossing that uses four turnouts, the code simply checks to 406 * ensure that diagonally opposite turnouts are set the same. If not, it will 407 * return Inconsistent state. 408 * <p> 409 * If any turnout that has either not been configured or is in an Unknown or 410 * Inconsistent state, the code will return the state UNKNOWN or 411 * INCONSISTENT. 412 * 413 * @return A state variable from a Turnout, e.g. Turnout.CLOSED 414 */ 415 private int turnoutState() { 416 //Need to rework this! 417 //might be as simple as adding the two states together. 418 //if either turnout is not entered then the state to report 419 //back will be unknown 420 int state; 421 if (namedTurnoutWest != null) { 422 state = getTurnout(WEST).getKnownState(); // part 1 of the slip(turnouticon)state 423 } else { 424 state = UNKNOWN; 425 } 426 if ((state == UNKNOWN) || (state == INCONSISTENT)) { 427 return state; 428 } 429 //We add 1 (or 3) to the value of the west turnout to help identify the states for both turnouts 430 if (namedTurnoutEast != null) { 431 int eState = getTurnout(EAST).getKnownState(); 432 if (eState == CLOSED) { 433 eState = state + (getTurnout(EAST).getKnownState() + 1); // part 2 of the slip(turnouticon)state 434 } else if (eState == THROWN) { 435 eState = state + (getTurnout(EAST).getKnownState() + 3); // part 2 of the slip(turnouticon)state 436 } 437 state = eState; 438 } else { 439 state = UNKNOWN; 440 } 441 442 if ((turnoutType == SCISSOR) && (!singleSlipRoute)) { 443 // We simply check that the opposite turnout is set the same state 444 if (namedTurnoutEastLower != null) { 445 int leState = getTurnout(LOWEREAST).getKnownState(); 446 if (( leState== UNKNOWN) || (leState == INCONSISTENT)) { 447 state = leState; 448 } else if (leState != getTurnout(WEST).getKnownState()) { 449 state = INCONSISTENT; 450 } 451 } else { 452 state = UNKNOWN; 453 } 454 if (namedTurnoutWestLower != null) { 455 int lwState = getTurnout(LOWERWEST).getKnownState(); 456 if ((lwState == UNKNOWN) || (lwState == INCONSISTENT)) { 457 state = lwState; 458 } else if (lwState != getTurnout(EAST).getKnownState()) { 459 state = INCONSISTENT; 460 } 461 } else { 462 state = UNKNOWN; 463 } 464 } 465 return state; 466 } 467 468 /** 469 * Update icon as state of turnout changes. 470 */ 471 @Override 472 public void propertyChange(java.beans.PropertyChangeEvent e) { 473 log.debug("property change: {} {} is now {}", getNameString(), e.getPropertyName(), e.getNewValue()); 474 475 // when there's feedback, transition through inconsistent icon for better 476 // animation 477 if (getTristate() 478 && (getTurnout(WEST).getFeedbackMode() != Turnout.DIRECT) 479 && (Turnout.PROPERTY_COMMANDED_STATE.equals(e.getPropertyName()))) { 480 if ((getTurnout(WEST).getCommandedState() != getTurnout(WEST).getKnownState()) 481 || (getTurnout(EAST).getCommandedState() != getTurnout(EAST).getKnownState())) { 482 displayState(INCONSISTENT); 483 } 484 // this takes care of the quick double click 485 if ((getTurnout(WEST).getCommandedState() == getTurnout(WEST).getKnownState()) 486 || (getTurnout(EAST).getCommandedState() == getTurnout(EAST).getKnownState())) { 487 displayState(turnoutState()); 488 } 489 } 490 491 if (Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) { 492 displayState(turnoutState()); 493 } 494 } 495 496 @Override 497 @Nonnull 498 public String getTypeString() { 499 return Bundle.getMessage("PositionableType_SlipTurnoutIcon"); 500 } 501 502 @Override 503 @Nonnull 504 public String getNameString() { 505 String name; 506 if (namedTurnoutWest == null) { 507 name = Bundle.getMessage("NotConnected"); 508 } else { 509 name = namedTurnoutWest.getName(); 510 } 511 if (namedTurnoutEast != null) { 512 name = name + " " + namedTurnoutEast.getName(); 513 } 514 if ((getTurnoutType() == SCISSOR) && (!getSingleSlipRoute())) { 515 if (namedTurnoutWestLower != null) { 516 name = name + " " + namedTurnoutWestLower.getName(); 517 } 518 if (namedTurnoutEastLower != null) { 519 name = name + " " + namedTurnoutEastLower.getName(); 520 } 521 } 522 return name; 523 } 524 525 public void setTristate(boolean set) { 526 tristate = set; 527 } 528 529 public boolean getTristate() { 530 return tristate; 531 } 532 private boolean tristate = false; 533 534 javax.swing.JCheckBoxMenuItem tristateItem = null; 535 536 void addTristateEntry(JPopupMenu popup) { 537 tristateItem = new javax.swing.JCheckBoxMenuItem(Bundle.getMessage("Tristate")); 538 tristateItem.setSelected(getTristate()); 539 popup.add(tristateItem); 540 tristateItem.addActionListener( e -> setTristate(tristateItem.isSelected())); 541 } 542 543 /** 544 * ****** popup AbstractAction.actionPerformed method overrides ******** 545 */ 546 @Override 547 protected void rotateOrthogonal() { 548 lowerWestToUpperEast.setRotation(lowerWestToUpperEast.getRotation() + 1, this); 549 upperWestToLowerEast.setRotation(upperWestToLowerEast.getRotation() + 1, this); 550 lowerWestToLowerEast.setRotation(lowerWestToLowerEast.getRotation() + 1, this); 551 upperWestToUpperEast.setRotation(upperWestToUpperEast.getRotation() + 1, this); 552 unknown.setRotation(unknown.getRotation() + 1, this); 553 inconsistent.setRotation(inconsistent.getRotation() + 1, this); 554 displayState(turnoutState()); 555 // bug fix, must repaint icons that have same width and height 556 repaint(); 557 } 558 559 @Override 560 public void setScale(double s) { 561 lowerWestToUpperEast.scale(s, this); 562 upperWestToLowerEast.scale(s, this); 563 lowerWestToLowerEast.scale(s, this); 564 upperWestToUpperEast.scale(s, this); 565 unknown.scale(s, this); 566 inconsistent.scale(s, this); 567 displayState(turnoutState()); 568 } 569 570 @Override 571 public void rotate(int deg) { 572 lowerWestToUpperEast.rotate(deg, this); 573 upperWestToLowerEast.rotate(deg, this); 574 lowerWestToLowerEast.rotate(deg, this); 575 upperWestToUpperEast.rotate(deg, this); 576 unknown.rotate(deg, this); 577 inconsistent.rotate(deg, this); 578 displayState(turnoutState()); 579 } 580 581 /** 582 * Drive the current state of the display from the state of the turnout. 583 * Here we have to alter the passed state to match the type of turnout we 584 * are dealing with. 585 * 586 * @param state An integer value of the turnout states. 587 */ 588 private void displayState(int state) { 589 // TODO This needs to be worked on. 590 // When changes are made, update the slipturnouticon code in web/js/panel.js accordingly 591 log.debug("{} displayState {}", getNameString(), state); 592 updateSize(); 593 // we have to make some adjustments if we are using a single slip, three way point 594 // or scissor arrangement to make sure that we get the correct representation. 595 switch (getTurnoutType()) { 596 case SINGLESLIP: 597 if (singleSlipRoute && state == 9) { 598 state = 0; 599 } else if ((!singleSlipRoute) && state == 7) { 600 state = 0; 601 } 602 break; 603 case THREEWAY: 604 if ((state == 7) || (state == 11)) { 605 if (singleSlipRoute) { 606 state = 11; 607 } else { 608 state = 9; 609 } 610 } else if (state == 9) { 611 if (!singleSlipRoute) { 612 state = 11; 613 } 614 } 615 break; 616 case SCISSOR: 617 //State 11 should not be allowed for a scissor. 618 switch (state) { 619 case 5: 620 state = 9; 621 break; 622 case 7: 623 state = 5; 624 break; 625 case 9: 626 state = 11; 627 break; 628 case 11: 629 state = 0; 630 break; 631 default: 632 log.warn("Unhandled scissors state: {}", state); 633 state = 8; 634 break; 635 } 636 break; 637 case DOUBLESLIP: 638 // DOUBLESLIP is an allowed type, so it shouldn't 639 // cause a warning, even if we don't need special handling. 640 break; 641 default: 642 log.warn("Unhandled turnout type: {}", getTurnoutType()); 643 break; 644 } 645 switch (state) { 646 case UNKNOWN: 647 if (isText()) { 648 super.setText(Bundle.getMessage("BeanStateUnknown")); 649 } 650 if (isIcon()) { 651 super.setIcon(unknown); 652 } 653 break; 654 case 5: //first Closed, second Closed 655 if (isText()) { 656 super.setText(upperWestToLowerEastText); 657 } 658 if (isIcon()) { 659 super.setIcon(upperWestToLowerEast); 660 } 661 break; 662 case 9: // first Closed, second Thrown 663 if (isText()) { 664 super.setText(lowerWestToLowerEastText); 665 } 666 if (isIcon()) { 667 super.setIcon(lowerWestToLowerEast); 668 } 669 break; 670 case 7: //first Thrown, second Closed 671 if (isText()) { 672 super.setText(upperWestToUpperEastText); 673 } 674 if (isIcon()) { 675 super.setIcon(upperWestToUpperEast); 676 } 677 break; 678 case 11: //first Thrown, second Thrown 679 if (isText()) { 680 super.setText(lowerWestToUpperEastText); 681 } 682 if (isIcon()) { 683 super.setIcon(lowerWestToUpperEast); 684 } 685 break; 686 default: 687 if (isText()) { 688 super.setText(Bundle.getMessage("BeanStateInconsistent")); 689 } 690 if (isIcon()) { 691 super.setIcon(inconsistent); 692 } 693 break; 694 } 695 } 696 697 String lowerWestToUpperEastText = Bundle.getMessage("LowerWestToUpperEast"); 698 String upperWestToLowerEastText = Bundle.getMessage("UpperWestToLowerEast"); 699 String lowerWestToLowerEastText = Bundle.getMessage("LowerWestToLowerEast"); 700 String upperWestToUpperEastText = Bundle.getMessage("UpperWestToUpperEast"); 701 702 /** 703 * Get the text used in the pop-up for setting the route from Lower West to 704 * Upper East. 705 * For a scissor crossing this is the Left-hand crossing. 706 * For a 3 Way turnout this is the Upper Exit. 707 * 708 * @return localized description of route 709 */ 710 public String getLWUEText() { 711 return lowerWestToUpperEastText; 712 } 713 714 /** 715 * Get the text used in the pop-up for setting the route from Upper West to 716 * Lower East. 717 * For a scissor crossing this is the Right-hand crossing. 718 * For a 3 Way turnout this is the Middle Exit. 719 * 720 * @return localized description of route 721 */ 722 public String getUWLEText() { 723 return upperWestToLowerEastText; 724 } 725 726 /** 727 * Get the text used in the pop-up for setting the route from Lower West to 728 * Lower East. 729 * For a scissor crossing this is the Straight (Normal) Route. 730 * For a 3 Way turnout this is the Lower Exit. 731 * 732 * @return localized description of route 733 */ 734 public String getLWLEText() { 735 return lowerWestToLowerEastText; 736 } 737 738 /** 739 * Get the text used in the pop-up for setting the route from Upper West to 740 * Upper East. 741 * For a scissor crossing this is not used. 742 * For a 3 Way turnout this is not used. 743 * 744 * @return localized description of route 745 */ 746 public String getUWUEText() { 747 return upperWestToUpperEastText; 748 } 749 750 public void setLWUEText(String txt) { 751 lowerWestToUpperEastText = txt; 752 } 753 754 public void setUWLEText(String txt) { 755 upperWestToLowerEastText = txt; 756 } 757 758 public void setLWLEText(String txt) { 759 lowerWestToLowerEastText = txt; 760 } 761 762 public void setUWUEText(String txt) { 763 upperWestToUpperEastText = txt; 764 } 765 766 SlipIconAdder _iconEditor; 767 768 @Override 769 protected void edit() { 770 if (_iconEditor == null) { 771 _iconEditor = new SlipIconAdder(); 772 } 773 makeIconEditorFrame(this, "SlipTOEditor", true, _iconEditor); 774 _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.turnoutPickModelInstance()); 775 _iconEditor.setTurnoutType(getTurnoutType()); 776 switch (getTurnoutType()) { 777 case DOUBLESLIP: 778 _iconEditor.setIcon(3, "LowerWestToUpperEast", getLowerWestToUpperEastIcon()); 779 _iconEditor.setIcon(2, "UpperWestToLowerEast", getUpperWestToLowerEastIcon()); 780 _iconEditor.setIcon(4, "LowerWestToLowerEast", getLowerWestToLowerEastIcon()); 781 _iconEditor.setIcon(5, "UpperWestToUpperEast", getUpperWestToUpperEastIcon()); 782 break; 783 case SINGLESLIP: 784 _iconEditor.setSingleSlipRoute(getSingleSlipRoute()); 785 _iconEditor.setIcon(3, "LowerWestToUpperEast", getLowerWestToUpperEastIcon()); 786 _iconEditor.setIcon(2, "UpperWestToLowerEast", getUpperWestToLowerEastIcon()); 787 _iconEditor.setIcon(4, "Slip", getLowerWestToLowerEastIcon()); 788 789 break; 790 case THREEWAY: 791 _iconEditor.setSingleSlipRoute(getSingleSlipRoute()); 792 _iconEditor.setIcon(3, "Upper", getLowerWestToUpperEastIcon()); 793 _iconEditor.setIcon(2, "Middle", getUpperWestToLowerEastIcon()); 794 _iconEditor.setIcon(4, "Lower", getLowerWestToLowerEastIcon()); 795 break; 796 case SCISSOR: 797 _iconEditor.setSingleSlipRoute(getSingleSlipRoute()); 798 _iconEditor.setIcon(3, "LowerWestToUpperEast", getLowerWestToUpperEastIcon()); 799 _iconEditor.setIcon(2, "UpperWestToLowerEast", getUpperWestToLowerEastIcon()); 800 _iconEditor.setIcon(4, "LowerWestToLowerEast", getLowerWestToLowerEastIcon()); 801 if (!getSingleSlipRoute()) { 802 _iconEditor.setTurnout("lowerwest", namedTurnoutWestLower); 803 _iconEditor.setTurnout("lowereast", namedTurnoutEastLower); 804 } 805 break; 806 default: 807 log.error("getTurnoutType() value {} should not have appeared", getTurnoutType()); 808 } 809 _iconEditor.setIcon(0, "BeanStateInconsistent", getInconsistentIcon()); 810 _iconEditor.setIcon(1, "BeanStateUnknown", getUnknownIcon()); 811 _iconEditor.setTurnout("west", namedTurnoutWest); 812 _iconEditor.setTurnout("east", namedTurnoutEast); 813 814 _iconEditor.makeIconPanel(true); 815 816 ActionListener addIconAction = (ActionEvent a) -> updateTurnout(); 817 _iconEditor.complete(addIconAction, true, true, true); 818 } 819 820 void updateTurnout() { 821 setTurnoutType(_iconEditor.getTurnoutType()); 822 switch (_iconEditor.getTurnoutType()) { 823 case DOUBLESLIP: 824 setLowerWestToUpperEastIcon(_iconEditor.getIcon("LowerWestToUpperEast")); 825 setUpperWestToLowerEastIcon(_iconEditor.getIcon("UpperWestToLowerEast")); 826 setLowerWestToLowerEastIcon(_iconEditor.getIcon("LowerWestToLowerEast")); 827 setUpperWestToUpperEastIcon(_iconEditor.getIcon("UpperWestToUpperEast")); 828 break; 829 case SINGLESLIP: 830 setLowerWestToUpperEastIcon(_iconEditor.getIcon("LowerWestToUpperEast")); 831 setUpperWestToLowerEastIcon(_iconEditor.getIcon("UpperWestToLowerEast")); 832 setSingleSlipRoute(_iconEditor.getSingleSlipRoute()); 833 setLowerWestToLowerEastIcon(_iconEditor.getIcon("Slip")); 834 break; 835 case THREEWAY: 836 setSingleSlipRoute(_iconEditor.getSingleSlipRoute()); 837 setLowerWestToUpperEastIcon(_iconEditor.getIcon("Upper")); 838 setUpperWestToLowerEastIcon(_iconEditor.getIcon("Middle")); 839 setLowerWestToLowerEastIcon(_iconEditor.getIcon("Lower")); 840 break; 841 case SCISSOR: 842 setLowerWestToUpperEastIcon(_iconEditor.getIcon("LowerWestToUpperEast")); 843 setUpperWestToLowerEastIcon(_iconEditor.getIcon("UpperWestToLowerEast")); 844 setLowerWestToLowerEastIcon(_iconEditor.getIcon("LowerWestToLowerEast")); 845 setSingleSlipRoute(_iconEditor.getSingleSlipRoute()); 846 if (!getSingleSlipRoute()) { 847 setTurnout(_iconEditor.getTurnout("lowerwest"), LOWERWEST); 848 setTurnout(_iconEditor.getTurnout("lowereast"), LOWEREAST); 849 } 850 break; 851 default: 852 log.error("_iconEditor.getTurnoutType() value {} should not have appeared", _iconEditor.getTurnoutType()); 853 } 854 setInconsistentIcon(_iconEditor.getIcon("BeanStateInconsistent")); 855 setUnknownIcon(_iconEditor.getIcon("BeanStateUnknown")); 856 setTurnout(_iconEditor.getTurnout("west"), WEST); 857 setTurnout(_iconEditor.getTurnout("east"), EAST); 858 _iconEditorFrame.dispose(); 859 _iconEditorFrame = null; 860 _iconEditor = null; 861 invalidate(); 862 } 863 864 /** 865 * Throw the turnout when the icon is clicked. 866 * 867 * @param e the click event 868 */ 869 @Override 870 public void doMouseClicked(JmriMouseEvent e) { 871 if (!_editor.getFlag(Editor.OPTION_CONTROLS, isControlling())) { 872 return; 873 } 874 if (e.isMetaDown() || e.isAltDown()) { 875 return; 876 } 877 if ((namedTurnoutWest == null) || (namedTurnoutEast == null)) { 878 log.error("No turnout connection, can't process click"); 879 return; 880 } 881 switch (turnoutType) { 882 case DOUBLESLIP: 883 doDoubleSlipMouseClick(); 884 break; 885 case SINGLESLIP: 886 doSingleSlipMouseClick(); 887 break; 888 case THREEWAY: 889 do3WayMouseClick(); 890 break; 891 case SCISSOR: 892 doScissorMouseClick(); 893 break; 894 default: 895 log.error("turnoutType value {} should not have appeared", turnoutType); 896 } 897 } 898 899 /** 900 * Throw the turnouts for a double slip when the icon is clicked. 901 */ 902 private void doDoubleSlipMouseClick() { 903 switch (turnoutState()) { 904 case 5: 905 setUpperWestToUpperEast(); 906 break; 907 case 7: 908 setLowerWestToUpperEast(); 909 break; 910 case 11: 911 setLowerWestToLowerEast(); 912 break; 913 case 9: 914 default: 915 setUpperWestToLowerEast(); 916 } 917 } 918 919 /** 920 * Throw the turnouts for a single slip when the icon is clicked. 921 */ 922 private void doSingleSlipMouseClick() { 923 switch (turnoutState()) { 924 case 5: 925 if (singleSlipRoute) { 926 setLowerWestToUpperEast(); 927 } else { 928 setLowerWestToLowerEast(); 929 } 930 break; 931 case 7: 932 case 9: 933 if (singleSlipRoute) { 934 setUpperWestToLowerEast(); 935 } else { 936 setLowerWestToUpperEast(); 937 } 938 break; 939 case 11: 940 if (singleSlipRoute) { 941 setUpperWestToUpperEast(); 942 } else { 943 setUpperWestToLowerEast(); 944 } 945 break; 946 default: 947 setUpperWestToLowerEast(); 948 } 949 } 950 951 /** 952 * Throw the turnouts for a 3 way Turnout when the icon is clicked. 953 */ 954 private void do3WayMouseClick() { 955 switch (turnoutState()) { 956 case 5: 957 if (singleSlipRoute) { 958 setLowerWestToLowerEast(); 959 } else { 960 setUpperWestToUpperEast(); 961 } 962 break; 963 case 7: 964 if (singleSlipRoute) { 965 setLowerWestToUpperEast(); 966 } else { 967 setLowerWestToLowerEast(); 968 } 969 break; 970 case 9: 971 if (singleSlipRoute) { 972 setLowerWestToUpperEast(); 973 } else { 974 setUpperWestToLowerEast(); 975 } 976 break; 977 case 11: 978 if (singleSlipRoute) { 979 setUpperWestToLowerEast(); 980 } else { 981 setLowerWestToLowerEast(); 982 } 983 break; 984 default: 985 setLowerWestToUpperEast(); 986 } 987 } 988 989 /** 990 * Throw the turnouts for a scissor crossing when the icon is clicked. 991 */ 992 private boolean firstStraight = false; 993 994 private void doScissorMouseClick() { 995 if (turnoutState() == 5) { 996 if (firstStraight) { 997 setUpperWestToLowerEast(); 998 firstStraight = false; 999 } else { 1000 setLowerWestToUpperEast(); 1001 firstStraight = true; 1002 } 1003 } else { 1004 setLowerWestToLowerEast(); 1005 } 1006 } 1007 1008 private HashMap<Turnout, Integer> _turnoutSetting = new HashMap<>(); 1009 1010 protected HashMap<Turnout, Integer> getTurnoutSettings() { 1011 return _turnoutSetting; 1012 } 1013 1014 protected void reset() { 1015 _turnoutSetting = new HashMap<>(); 1016 } 1017 1018 /** 1019 * Set the turnouts appropriate for Upper West to Lower East line in a Slip, 1020 * which is the equivalent of the right hand crossing in a scissors. With a 1021 * three way turnout, this is also the middle route. 1022 */ 1023 private void setUpperWestToLowerEast() { 1024 reset(); 1025 if (getTurnoutType() == SCISSOR) { 1026 _turnoutSetting.put(getTurnout(WEST), THROWN); 1027 _turnoutSetting.put(getTurnout(EAST), CLOSED); 1028 if (!singleSlipRoute) { 1029 _turnoutSetting.put(namedTurnoutWestLower.getBean(), CLOSED); 1030 _turnoutSetting.put(namedTurnoutEastLower.getBean(), THROWN); 1031 } 1032 } else { 1033 _turnoutSetting.put(getTurnout(WEST), CLOSED); 1034 _turnoutSetting.put(getTurnout(EAST), CLOSED); 1035 } 1036 setSlip(); 1037 } 1038 1039 /** 1040 * Set the turns appropriate for Lower West to Upper East line in a Slip 1041 * which is the equivalent of the left hand crossing in a scissors. With a 1042 * three way turnout, this is also the upper route. 1043 */ 1044 private void setLowerWestToUpperEast() { 1045 reset(); 1046 if (getTurnoutType() == SCISSOR) { 1047 _turnoutSetting.put(getTurnout(EAST), THROWN); 1048 _turnoutSetting.put(getTurnout(WEST), CLOSED); 1049 if (!singleSlipRoute) { 1050 _turnoutSetting.put(namedTurnoutWestLower.getBean(), THROWN); 1051 _turnoutSetting.put(namedTurnoutEastLower.getBean(), CLOSED); 1052 } 1053 } else { 1054 _turnoutSetting.put(getTurnout(EAST), THROWN); 1055 _turnoutSetting.put(getTurnout(WEST), THROWN); 1056 } 1057 setSlip(); 1058 } 1059 1060 /** 1061 * Set the turnouts appropriate for Upper West to Upper East line in a Slip 1062 * which is the equivalent of the straight (normal route) in a scissors. 1063 * With a three way turnout, this is not used. 1064 */ 1065 private void setUpperWestToUpperEast() { 1066 reset(); 1067 if (getTurnoutType() == SCISSOR) { 1068 _turnoutSetting.put(getTurnout(WEST), CLOSED); 1069 _turnoutSetting.put(getTurnout(EAST), CLOSED); 1070 if (!singleSlipRoute) { 1071 _turnoutSetting.put(namedTurnoutWestLower.getBean(), CLOSED); 1072 _turnoutSetting.put(namedTurnoutEastLower.getBean(), CLOSED); 1073 } 1074 } else { 1075 _turnoutSetting.put(getTurnout(WEST), THROWN); 1076 _turnoutSetting.put(getTurnout(EAST), CLOSED); 1077 } 1078 setSlip(); 1079 } 1080 1081 /** 1082 * Set the turnouts appropriate for Lower West to Lower East line in a Slip 1083 * which is the equivalent of the straight (normal route) in a scissors. 1084 * With a three way turnout, this is the lower route. 1085 */ 1086 private void setLowerWestToLowerEast() { 1087 reset(); 1088 if (getTurnoutType() == SCISSOR) { 1089 _turnoutSetting.put(getTurnout(WEST), CLOSED); 1090 _turnoutSetting.put(getTurnout(EAST), CLOSED); 1091 if (!singleSlipRoute) { 1092 _turnoutSetting.put(namedTurnoutWestLower.getBean(), CLOSED); 1093 _turnoutSetting.put(namedTurnoutEastLower.getBean(), CLOSED); 1094 } 1095 } else { 1096 _turnoutSetting.put(getTurnout(WEST), CLOSED); 1097 _turnoutSetting.put(getTurnout(EAST), THROWN); 1098 } 1099 setSlip(); 1100 } 1101 1102 /** 1103 * Display a popup menu to select a given state, rather than cycling 1104 * through each state. 1105 * 1106 * @param popup the menu to add the state menu to 1107 * @return true if anything added to menu 1108 */ 1109 @Override 1110 public boolean showPopUp(JPopupMenu popup) { 1111 if (isEditable()) { 1112 // add tristate option if turnout has feedback 1113 boolean returnstate = false; 1114 if (namedTurnoutWest != null && getTurnout(WEST).getFeedbackMode() != Turnout.DIRECT) { 1115 addTristateEntry(popup); 1116 returnstate = true; 1117 } 1118 if (namedTurnoutEast != null && getTurnout(EAST).getFeedbackMode() != Turnout.DIRECT) { 1119 addTristateEntry(popup); 1120 returnstate = true; 1121 } 1122 return returnstate; 1123 } else { 1124 JMenuItem LWUE = new JMenuItem(lowerWestToUpperEastText); 1125 if ((turnoutType == THREEWAY) && (!singleSlipRoute)) { 1126 LWUE.addActionListener((ActionEvent e) -> setLowerWestToLowerEast()); 1127 1128 } else { 1129 LWUE.addActionListener((ActionEvent e) -> setLowerWestToUpperEast()); 1130 } 1131 popup.add(LWUE); 1132 JMenuItem UWLE = new JMenuItem(upperWestToLowerEastText); 1133 UWLE.addActionListener((ActionEvent e) -> setUpperWestToLowerEast()); 1134 popup.add(UWLE); 1135 if ((turnoutType == DOUBLESLIP) || ((turnoutType == SINGLESLIP) && (!singleSlipRoute))) { 1136 JMenuItem LWLE = new JMenuItem(lowerWestToLowerEastText); 1137 LWLE.addActionListener((ActionEvent e) -> setLowerWestToLowerEast()); 1138 popup.add(LWLE); 1139 } 1140 if ((turnoutType == DOUBLESLIP) || ((turnoutType == SINGLESLIP) && (singleSlipRoute))) { 1141 JMenuItem UWUE = new JMenuItem(upperWestToUpperEastText); 1142 UWUE.addActionListener((ActionEvent e) -> setUpperWestToUpperEast()); 1143 popup.add(UWUE); 1144 } 1145 if (turnoutType == THREEWAY) { 1146 JMenuItem LWLE = new JMenuItem(lowerWestToLowerEastText); 1147 if (!singleSlipRoute) { 1148 LWLE.addActionListener((ActionEvent e) -> setLowerWestToUpperEast()); 1149 } else { 1150 LWLE.addActionListener((ActionEvent e) -> setLowerWestToLowerEast()); 1151 } 1152 popup.add(LWLE); 1153 } 1154 if (turnoutType == SCISSOR) { 1155 JMenuItem LWLE = new JMenuItem(lowerWestToLowerEastText); 1156 LWLE.addActionListener((ActionEvent e) -> setLowerWestToLowerEast()); 1157 popup.add(LWLE); 1158 } 1159 } 1160 return true; 1161 } 1162 1163 @Override 1164 public boolean setTextEditMenu(@Nonnull JPopupMenu popup) { 1165 String popuptext = Bundle.getMessage("SetSlipText"); 1166 if (turnoutType == THREEWAY) { 1167 popuptext = Bundle.getMessage("Set3WayText"); 1168 } else if (turnoutType == SCISSOR) { 1169 popuptext = Bundle.getMessage("SetScissorText"); 1170 } 1171 popup.add(new AbstractAction(popuptext) { 1172 @Override 1173 public void actionPerformed(ActionEvent e) { 1174 String name = getNameString(); 1175 slipTurnoutTextEdit(name); 1176 } 1177 }); 1178 return true; 1179 } 1180 1181 public void slipTurnoutTextEdit(String name) { 1182 log.debug("make text edit menu"); 1183 1184 SlipTurnoutTextEdit f = new SlipTurnoutTextEdit(); 1185 f.addHelpMenu("package.jmri.jmrit.display.SlipTurnoutTextEdit", true); 1186 try { 1187 f.initComponents(this, name); 1188 } catch (Exception ex) { 1189 log.error("Exception: {}", ex.toString()); 1190 } 1191 f.setVisible(true); 1192 } 1193 1194 @Override 1195 public void dispose() { 1196 if (namedTurnoutWest != null) { 1197 getTurnout(WEST).removePropertyChangeListener(this); 1198 } 1199 namedTurnoutWest = null; 1200 if (namedTurnoutEast != null) { 1201 getTurnout(EAST).removePropertyChangeListener(this); 1202 } 1203 namedTurnoutEast = null; 1204 if (namedTurnoutWestLower != null) { 1205 getTurnout(WEST).removePropertyChangeListener(this); 1206 } 1207 namedTurnoutWestLower = null; 1208 if (namedTurnoutEastLower != null) { 1209 getTurnout(EAST).removePropertyChangeListener(this); 1210 } 1211 namedTurnoutEastLower = null; 1212 lowerWestToUpperEast = null; 1213 upperWestToLowerEast = null; 1214 lowerWestToLowerEast = null; 1215 upperWestToUpperEast = null; 1216 inconsistent = null; 1217 unknown = null; 1218 1219 super.dispose(); 1220 } 1221 1222 boolean busy = false; 1223 1224 /** 1225 * Set Slip busy when commands are being issued to Slip turnouts. 1226 */ 1227 protected void setSlipBusy() { 1228 busy = true; 1229 } 1230 1231 /** 1232 * Set Slip not busy when all commands have been issued to Slip turnouts. 1233 */ 1234 protected void setSlipNotBusy() { 1235 busy = false; 1236 } 1237 1238 /** 1239 * Check if Slip is busy. 1240 * 1241 * @return true if commands are being issued to Slip turnouts. 1242 */ 1243 protected boolean isSlipBusy() { 1244 return (busy); 1245 } 1246 1247 /** 1248 * Set the Slip. Sets the slips Turnouts to the state required. This call is 1249 * ignored if the slip is 'busy', i.e., if there is a thread currently 1250 * sending commands to this Slips's turnouts. 1251 */ 1252 private void setSlip() { 1253 if (!busy) { 1254 setSlipBusy(); 1255 SetSlipThread thread = new SetSlipThread(this); 1256 thread.start(); 1257 } 1258 } 1259 1260 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SlipTurnoutIcon.class); 1261 1262 private static class SetSlipThread extends Thread { 1263 1264 /** 1265 * Construct the thread. 1266 * 1267 * @param aSlip the slip icon to manipulate in the thread 1268 */ 1269 SetSlipThread(SlipTurnoutIcon aSlip) { 1270 s = aSlip; 1271 } 1272 1273 //This is used to set the two turnouts, with a delay of 250ms between each one. 1274 @Override 1275 public void run() { 1276 1277 HashMap<Turnout, Integer> _turnoutSetting = s.getTurnoutSettings(); 1278 1279 _turnoutSetting.forEach((turnout, state) -> { 1280 jmri.util.ThreadingUtil.runOnLayout(() -> { // run on layout thread 1281 turnout.setCommandedState(state); 1282 }); 1283 try { 1284 Thread.sleep(250); 1285 } catch (InterruptedException e) { 1286 Thread.currentThread().interrupt(); // retain if needed later 1287 } 1288 }); 1289 1290 //set Slip not busy 1291 s.setSlipNotBusy(); 1292 } 1293 1294 private final SlipTurnoutIcon s; 1295 1296 } 1297 1298}