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