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