001package jmri.implementation;
002
003import java.util.Arrays;
004
005/**
006 * Default implementation of the basic logic of the SignalHead interface.
007 *
008 * This class only claims support for the Red, Yellow and Green appearances, and
009 * their corresponding flashing forms. Support for Lunar is deferred to
010 * DefaultLunarSignalHead or an extended class.
011 *
012 * @author Bob Jacobsen Copyright (C) 2001, 2009
013 */
014public abstract class DefaultSignalHead extends AbstractSignalHead {
015
016    public DefaultSignalHead(String systemName, String userName) {
017        super(systemName, userName);
018    }
019
020    public DefaultSignalHead(String systemName) {
021        super(systemName);
022    }
023
024    @Override
025    public void setAppearance(int newAppearance) {
026        int oldAppearance = mAppearance; // store the current appearance
027        mAppearance = newAppearance;
028        appearanceSetsFlashTimer(newAppearance);
029
030        /* there are circumstances (admittedly rare) where signals and turnouts can get out of sync
031         * allow 'newAppearance' to be set to resync these cases - P Cressman
032         * if (oldAppearance != newAppearance) */
033        updateOutput();
034
035        // notify listeners, if any
036        firePropertyChange("Appearance", oldAppearance, newAppearance);
037    }
038
039    /**
040     * Call to set timer when updating the appearance.
041     *
042     * @param newAppearance the new appearance
043     */
044    protected void appearanceSetsFlashTimer(int newAppearance) {
045        if (mLit && ((newAppearance == FLASHGREEN)
046                || (newAppearance == FLASHYELLOW)
047                || (newAppearance == FLASHRED)
048                || (newAppearance == FLASHLUNAR))) {
049            startFlash();
050        }
051        if ((!mLit) || ((newAppearance != FLASHGREEN)
052                && (newAppearance != FLASHYELLOW)
053                && (newAppearance != FLASHRED)
054                && (newAppearance != FLASHLUNAR))) {
055            stopFlash();
056        }
057    }
058
059    @Override
060    public void setLit(boolean newLit) {
061        boolean oldLit = mLit;
062        mLit = newLit;
063        if (oldLit != newLit) {
064            if (mLit && ((mAppearance == FLASHGREEN)
065                    || (mAppearance == FLASHYELLOW)
066                    || (mAppearance == FLASHRED)
067                    || (mAppearance == FLASHLUNAR))) {
068                startFlash();
069            }
070            if (!mLit) {
071                stopFlash();
072            }
073            updateOutput();
074            // notify listeners, if any
075            firePropertyChange("Lit", oldLit, newLit);
076        }
077    }
078
079    /**
080     * Set the held parameter.
081     * <p>
082     * Note that this does not directly effect the output on the layout; the
083     * held parameter is a local variable which effects the aspect only via
084     * higher-level logic.
085     *
086     * @param newHeld new Held state, true if Held, to be compared with current
087     *                Held state
088     */
089    @Override
090    public void setHeld(boolean newHeld) {
091        boolean oldHeld = mHeld;
092        mHeld = newHeld;
093        if (oldHeld != newHeld) {
094            // notify listeners, if any
095            firePropertyChange("Held", oldHeld, newHeld);
096        }
097
098    }
099
100    /**
101     * Type-specific routine to handle output to the layout hardware.
102     * <p>
103     * Does not notify listeners of changes; that's done elsewhere. Should use
104     * the following variables to determine what to send:
105     * <ul>
106     * <li>mAppearance
107     * <li>mLit
108     * <li>mFlashOn
109     * </ul>
110     */
111    abstract protected void updateOutput();
112
113    /**
114     * Should a flashing signal be on (lit) now?
115     */
116    protected boolean mFlashOn = true;
117
118    javax.swing.Timer timer = null;
119    /**
120     * On or off time of flashing signal.
121     * Public so that it can be overridden by 
122     * scripting (before first use)
123     */
124    public int delay = masterDelay;
125
126    public static int masterDelay = 750;
127    
128    /**
129     * Start the timer that controls flashing.
130     */
131    protected void startFlash() {
132        // note that we don't force mFlashOn to be true at the start
133        // of this; that way a flash in process isn't disturbed.
134        if (timer == null) {
135            timer = new javax.swing.Timer(delay, (java.awt.event.ActionEvent e) -> {
136                timeout();
137            });
138            timer.setInitialDelay(delay);
139            timer.setRepeats(true);
140        }
141        timer.start();
142    }
143
144    private void timeout() {
145        mFlashOn = !mFlashOn;
146
147        updateOutput();
148    }
149
150    /*
151     * Stop the timer that controls flashing.
152     * <p>
153     * This is only a resource-saver; the actual use of
154     * flashing happens elsewhere.
155     */
156    protected void stopFlash() {
157        if (timer != null) {
158            timer.stop();
159        }
160        mFlashOn = true;
161    }
162
163    final static private int[] VALID_STATES = new int[]{
164        DARK,
165        RED,
166        YELLOW,
167        GREEN,
168        FLASHRED,
169        FLASHYELLOW,
170        FLASHGREEN,
171    }; // No int for Lunar
172
173    final static private String[] VALID_STATE_KEYS = new String[]{
174        "SignalHeadStateDark",
175        "SignalHeadStateRed",
176        "SignalHeadStateYellow",
177        "SignalHeadStateGreen",
178        "SignalHeadStateFlashingRed",
179        "SignalHeadStateFlashingYellow",
180        "SignalHeadStateFlashingGreen",
181    }; // Lunar not included
182
183    /**
184     * {@inheritDoc}
185     */
186    @Override
187    public int[] getValidStates() {
188        return Arrays.copyOf(VALID_STATES, VALID_STATES.length);
189    }
190
191    /**
192     * {@inheritDoc}
193     */
194    @Override
195    public String[] getValidStateKeys() {
196        return Arrays.copyOf(VALID_STATE_KEYS, VALID_STATE_KEYS.length);
197    }
198
199    /**
200     * {@inheritDoc}
201     */
202    @Override
203    public String[] getValidStateNames() {
204        String[] stateNames = new String[VALID_STATE_KEYS.length];
205        int i = 0;
206        for (String stateKey : VALID_STATE_KEYS) {
207            stateNames[i++] = Bundle.getMessage(stateKey);
208        }
209        return stateNames;
210    }
211
212    @Override
213    boolean isTurnoutUsed(jmri.Turnout t) {
214        return false;
215    }
216
217}