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