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}