001package jmri.jmrit.display;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005
006import javax.annotation.Nonnull;
007
008import jmri.InstanceManager;
009import jmri.Light;
010import jmri.NamedBean.DisplayOptions;
011import jmri.jmrit.catalog.NamedIcon;
012import jmri.util.swing.JmriMouseEvent;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * An icon to display a status of a light.
019 * <p>
020 * A click on the icon will command a state change. Specifically, it will set
021 * the state to the opposite (THROWN vs CLOSED) of the current state.
022 * <p>
023 * The default icons show a crossed lamp symbol.
024 * @see Editor#addLightEditor()
025 *
026 * @author Bob Jacobsen Copyright (c) 2002
027 */
028public class LightIcon extends PositionableLabel implements java.beans.PropertyChangeListener {
029
030    public LightIcon(Editor editor) {
031        // super ctor call to make sure this is an icon label
032        super(new NamedIcon("resources/icons/smallschematics/lights/cross-on.png",
033                "resources/icons/smallschematics/lights/cross-off.png"), editor);
034        _control = true;
035        displayState(lightState());
036        setPopupUtility(null);
037    }
038
039    // the associated Light object
040    Light light = null;
041
042    @Override
043    public Positionable deepClone() {
044        LightIcon pos = new LightIcon(_editor);
045        return finishClone(pos);
046    }
047
048    protected Positionable finishClone(LightIcon pos) {
049        pos.setLight(getNameString());
050        pos.setOffIcon(cloneIcon(getOffIcon(), pos));
051        pos.setOnIcon(cloneIcon(getOnIcon(), pos));
052        pos.setInconsistentIcon(cloneIcon(getInconsistentIcon(), pos));
053        pos.setUnknownIcon(cloneIcon(getUnknownIcon(), pos));
054        return super.finishClone(pos);
055    }
056
057    /**
058     * Attached a named light to this display item
059     *
060     * @param pName Used as a system/user name to lookup the light object
061     */
062    public void setLight(String pName) {
063        if (InstanceManager.getNullableDefault(jmri.LightManager.class) != null) {
064            light = InstanceManager.lightManagerInstance().
065                    provideLight(pName);
066            setLight(light);
067        } else {
068            log.error("No LightManager for this protocol, icon won't see changes");
069        }
070    }
071
072    public void setLight(Light to) {
073        if (light != null) {
074            light.removePropertyChangeListener(this);
075        }
076        light = to;
077        if (light != null) {
078            displayState(lightState());
079            light.addPropertyChangeListener(this);
080        }
081    }
082
083    public Light getLight() {
084        return light;
085    }
086
087    // display icons
088    String offLName = "resources/icons/smallschematics/lights/cross-on.png";
089    NamedIcon off = new NamedIcon(offLName, offLName);
090    String onLName = "resources/icons/smallschematics/lights/cross-off.png";
091    NamedIcon on = new NamedIcon(onLName, onLName);
092    String inconsistentLName = "resources/icons/smallschematics/lights/cross-inconsistent.png";
093    NamedIcon inconsistent = new NamedIcon(inconsistentLName, inconsistentLName);
094    String unknownLName = "resources/icons/smallschematics/lights/cross-unknown.png";
095    NamedIcon unknown = new NamedIcon(unknownLName, unknownLName);
096
097    public NamedIcon getOffIcon() {
098        return off;
099    }
100
101    public void setOffIcon(NamedIcon i) {
102        off = i;
103        displayState(lightState());
104    }
105
106    public NamedIcon getOnIcon() {
107        return on;
108    }
109
110    public void setOnIcon(NamedIcon i) {
111        on = i;
112        displayState(lightState());
113    }
114
115    public NamedIcon getInconsistentIcon() {
116        return inconsistent;
117    }
118
119    public void setInconsistentIcon(NamedIcon i) {
120        inconsistent = i;
121        displayState(lightState());
122    }
123
124    public NamedIcon getUnknownIcon() {
125        return unknown;
126    }
127
128    public void setUnknownIcon(NamedIcon i) {
129        unknown = i;
130        displayState(lightState());
131    }
132
133    @Override
134    public int maxHeight() {
135        return Math.max(
136                Math.max((off != null) ? off.getIconHeight() : 0,
137                        (on != null) ? on.getIconHeight() : 0),
138                (inconsistent != null) ? inconsistent.getIconHeight() : 0
139        );
140    }
141
142    @Override
143    public int maxWidth() {
144        return Math.max(
145                Math.max((off != null) ? off.getIconWidth() : 0,
146                        (on != null) ? on.getIconWidth() : 0),
147                (inconsistent != null) ? inconsistent.getIconWidth() : 0
148        );
149    }
150
151    /**
152     * Get current state of attached light
153     *
154     * @return A state variable from a Light, e.g. Turnout.CLOSED
155     */
156    int lightState() {
157        if (light != null) {
158            return light.getState();
159        } // This doesn't seem right. (Light.UNKNOWN = Light.ON = 0X01)
160        //else return Light.UNKNOWN;
161        else {
162            return Light.INCONSISTENT;
163        }
164    }
165
166    // update icon as state of light changes
167    @Override
168    public void propertyChange(java.beans.PropertyChangeEvent e) {
169        if (log.isDebugEnabled()) {
170            log.debug("property change: {} {} is now {}", getNameString(), e.getPropertyName(), e.getNewValue());
171        }
172
173        if (e.getPropertyName().equals("KnownState")) {
174            int now = ((Integer) e.getNewValue());
175            displayState(now);
176        }
177    }
178
179    @Override
180    @Nonnull
181    public String getTypeString() {
182        return Bundle.getMessage("PositionableType_LightIcon");
183    }
184
185    @Override
186    public String getNameString() {
187        String name;
188        if (light == null) {
189            name = Bundle.getMessage("NotConnected");
190        } else {
191            name = light.getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
192        }
193        return name;
194    }
195
196    //
197    // ****** popup AbstractAction.actionPerformed method overrides ********
198    //
199    @Override
200    protected void rotateOrthogonal() {
201        off.setRotation(on.getRotation() + 1, this);
202        on.setRotation(off.getRotation() + 1, this);
203        unknown.setRotation(unknown.getRotation() + 1, this);
204        inconsistent.setRotation(inconsistent.getRotation() + 1, this);
205        displayState(lightState());
206        //bug fix, must repaint icons that have same width and height
207        repaint();
208    }
209
210    @Override
211    public void setScale(double s) {
212        off.scale(s, this);
213        on.scale(s, this);
214        unknown.scale(s, this);
215        inconsistent.scale(s, this);
216        displayState(lightState());
217    }
218
219    @Override
220    public void rotate(int deg) {
221        off.rotate(deg, this);
222        on.rotate(deg, this);
223        unknown.rotate(deg, this);
224        inconsistent.rotate(deg, this);
225        displayState(lightState());
226    }
227
228    @Override
229    protected void edit() {
230        makeIconEditorFrame(this, "Light", true, null);
231        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.lightPickModelInstance());
232        _iconEditor.setIcon(3, "StateOff", off);
233        _iconEditor.setIcon(2, "StateOn", on);
234        _iconEditor.setIcon(0, "BeanStateInconsistent", inconsistent);
235        _iconEditor.setIcon(1, "BeanStateUnknown", unknown);
236        _iconEditor.makeIconPanel(false);
237
238        ActionListener addIconAction = (ActionEvent a) -> updateLight();
239        _iconEditor.complete(addIconAction, true, true, true);
240        _iconEditor.setSelection(light);
241    }
242
243    void updateLight() {
244        setOffIcon(_iconEditor.getIcon("StateOff"));
245        setOnIcon(_iconEditor.getIcon("StateOn"));
246        setUnknownIcon(_iconEditor.getIcon("BeanStateUnknown"));
247        setInconsistentIcon(_iconEditor.getIcon("BeanStateInconsistent"));
248        setLight((Light) _iconEditor.getTableSelection());
249        _iconEditorFrame.dispose();
250        _iconEditorFrame = null;
251        _iconEditor = null;
252        invalidate();
253    }
254
255    //
256    // *********** end popup action methods ***************
257    //
258    /**
259     * Drive the current state of the display from the state of the light.
260     *
261     * @param state the new state
262     */
263    void displayState(int state) {
264        log.debug("{} displayState {}", getNameString(), state);
265        updateSize();
266        switch (state) {
267            case Light.OFF:
268                if (isText()) {
269                    super.setText(InstanceManager.turnoutManagerInstance().getClosedText());
270                }
271                if (isIcon()) {
272                    super.setIcon(off);
273                }
274                break;
275            case Light.ON:
276                if (isText()) {
277                    super.setText(InstanceManager.turnoutManagerInstance().getThrownText());
278                }
279                if (isIcon()) {
280                    super.setIcon(on);
281                }
282                break;
283            default:
284                if (isText()) {
285                    super.setText(Bundle.getMessage("BeanStateInconsistent"));
286                }
287                if (isIcon()) {
288                    super.setIcon(inconsistent);
289                }
290                break;
291        }
292    }
293
294    /**
295     * Change the light when the icon is clicked.
296     *
297     * @param e the mouse click
298     */
299    @Override
300    public void doMouseClicked(JmriMouseEvent e) {
301        if (!_editor.getFlag(Editor.OPTION_CONTROLS, isControlling())) {
302            return;
303        }
304        if (e.isMetaDown() || e.isAltDown()) {
305            return;
306        }
307        if (light == null) {
308            log.error("No light connection, can't process click");
309            return;
310        }
311        if (log.isDebugEnabled()) {
312            log.debug("doMouseClicked state= {}", light.getState());
313        }
314        if (light.getState() == jmri.Light.OFF) {
315            light.setState(jmri.Light.ON);
316        } else {
317            light.setState(jmri.Light.OFF);
318        }
319    }
320
321    @Override
322    public void dispose() {
323        if (light != null) {
324            light.removePropertyChangeListener(this);
325        }
326        light = null;
327
328        off = null;
329        on = null;
330        inconsistent = null;
331        unknown = null;
332
333        super.dispose();
334    }
335
336    private final static Logger log = LoggerFactory.getLogger(LightIcon.class);
337}