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