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    @Override
088    public jmri.NamedBean getNamedBean() {
089        return getLight();
090    }
091
092    // display icons
093    String offLName = "resources/icons/smallschematics/lights/cross-on.png";
094    NamedIcon off = new NamedIcon(offLName, offLName);
095    String onLName = "resources/icons/smallschematics/lights/cross-off.png";
096    NamedIcon on = new NamedIcon(onLName, onLName);
097    String inconsistentLName = "resources/icons/smallschematics/lights/cross-inconsistent.png";
098    NamedIcon inconsistent = new NamedIcon(inconsistentLName, inconsistentLName);
099    String unknownLName = "resources/icons/smallschematics/lights/cross-unknown.png";
100    NamedIcon unknown = new NamedIcon(unknownLName, unknownLName);
101
102    public NamedIcon getOffIcon() {
103        return off;
104    }
105
106    public void setOffIcon(NamedIcon i) {
107        off = i;
108        displayState(lightState());
109    }
110
111    public NamedIcon getOnIcon() {
112        return on;
113    }
114
115    public void setOnIcon(NamedIcon i) {
116        on = i;
117        displayState(lightState());
118    }
119
120    public NamedIcon getInconsistentIcon() {
121        return inconsistent;
122    }
123
124    public void setInconsistentIcon(NamedIcon i) {
125        inconsistent = i;
126        displayState(lightState());
127    }
128
129    public NamedIcon getUnknownIcon() {
130        return unknown;
131    }
132
133    public void setUnknownIcon(NamedIcon i) {
134        unknown = i;
135        displayState(lightState());
136    }
137
138    @Override
139    public int maxHeight() {
140        return Math.max(
141                Math.max((off != null) ? off.getIconHeight() : 0,
142                        (on != null) ? on.getIconHeight() : 0),
143                (inconsistent != null) ? inconsistent.getIconHeight() : 0
144        );
145    }
146
147    @Override
148    public int maxWidth() {
149        return Math.max(
150                Math.max((off != null) ? off.getIconWidth() : 0,
151                        (on != null) ? on.getIconWidth() : 0),
152                (inconsistent != null) ? inconsistent.getIconWidth() : 0
153        );
154    }
155
156    /**
157     * Get current state of attached light
158     *
159     * @return A state variable from a Light, e.g. Turnout.CLOSED
160     */
161    int lightState() {
162        if (light != null) {
163            return light.getState();
164        } // This doesn't seem right. (Light.UNKNOWN = Light.ON = 0X01)
165        //else return Light.UNKNOWN;
166        else {
167            return Light.INCONSISTENT;
168        }
169    }
170
171    // update icon as state of light changes
172    @Override
173    public void propertyChange(java.beans.PropertyChangeEvent e) {
174        if (log.isDebugEnabled()) {
175            log.debug("property change: {} {} is now {}", getNameString(), e.getPropertyName(), e.getNewValue());
176        }
177
178        if (e.getPropertyName().equals("KnownState")) {
179            int now = ((Integer) e.getNewValue());
180            displayState(now);
181        }
182    }
183
184    @Override
185    @Nonnull
186    public String getTypeString() {
187        return Bundle.getMessage("PositionableType_LightIcon");
188    }
189
190    @Override
191    public String getNameString() {
192        String name;
193        if (light == null) {
194            name = Bundle.getMessage("NotConnected");
195        } else {
196            name = light.getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
197        }
198        return name;
199    }
200
201    //
202    // ****** popup AbstractAction.actionPerformed method overrides ********
203    //
204    @Override
205    protected void rotateOrthogonal() {
206        off.setRotation(on.getRotation() + 1, this);
207        on.setRotation(off.getRotation() + 1, this);
208        unknown.setRotation(unknown.getRotation() + 1, this);
209        inconsistent.setRotation(inconsistent.getRotation() + 1, this);
210        displayState(lightState());
211        //bug fix, must repaint icons that have same width and height
212        repaint();
213    }
214
215    @Override
216    public void setScale(double s) {
217        off.scale(s, this);
218        on.scale(s, this);
219        unknown.scale(s, this);
220        inconsistent.scale(s, this);
221        displayState(lightState());
222    }
223
224    @Override
225    public void rotate(int deg) {
226        off.rotate(deg, this);
227        on.rotate(deg, this);
228        unknown.rotate(deg, this);
229        inconsistent.rotate(deg, this);
230        displayState(lightState());
231    }
232
233    @Override
234    protected void edit() {
235        makeIconEditorFrame(this, "Light", true, null);
236        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.lightPickModelInstance());
237        _iconEditor.setIcon(3, "StateOff", off);
238        _iconEditor.setIcon(2, "StateOn", on);
239        _iconEditor.setIcon(0, "BeanStateInconsistent", inconsistent);
240        _iconEditor.setIcon(1, "BeanStateUnknown", unknown);
241        _iconEditor.makeIconPanel(false);
242
243        ActionListener addIconAction = (ActionEvent a) -> updateLight();
244        _iconEditor.complete(addIconAction, true, true, true);
245        _iconEditor.setSelection(light);
246    }
247
248    void updateLight() {
249        setOffIcon(_iconEditor.getIcon("StateOff"));
250        setOnIcon(_iconEditor.getIcon("StateOn"));
251        setUnknownIcon(_iconEditor.getIcon("BeanStateUnknown"));
252        setInconsistentIcon(_iconEditor.getIcon("BeanStateInconsistent"));
253        setLight((Light) _iconEditor.getTableSelection());
254        _iconEditorFrame.dispose();
255        _iconEditorFrame = null;
256        _iconEditor = null;
257        invalidate();
258    }
259
260    //
261    // *********** end popup action methods ***************
262    //
263    /**
264     * Drive the current state of the display from the state of the light.
265     *
266     * @param state the new state
267     */
268    void displayState(int state) {
269        log.debug("{} displayState {}", getNameString(), state);
270        updateSize();
271        switch (state) {
272            case Light.OFF:
273                if (isText()) {
274                    super.setText(InstanceManager.turnoutManagerInstance().getClosedText());
275                }
276                if (isIcon()) {
277                    super.setIcon(off);
278                }
279                break;
280            case Light.ON:
281                if (isText()) {
282                    super.setText(InstanceManager.turnoutManagerInstance().getThrownText());
283                }
284                if (isIcon()) {
285                    super.setIcon(on);
286                }
287                break;
288            default:
289                if (isText()) {
290                    super.setText(Bundle.getMessage("BeanStateInconsistent"));
291                }
292                if (isIcon()) {
293                    super.setIcon(inconsistent);
294                }
295                break;
296        }
297    }
298
299    /**
300     * Change the light when the icon is clicked.
301     *
302     * @param e the mouse click
303     */
304    @Override
305    public void doMouseClicked(JmriMouseEvent e) {
306        if (!_editor.getFlag(Editor.OPTION_CONTROLS, isControlling())) {
307            return;
308        }
309        if (e.isMetaDown() || e.isAltDown()) {
310            return;
311        }
312        if (light == null) {
313            log.error("No light connection, can't process click");
314            return;
315        }
316        if (log.isDebugEnabled()) {
317            log.debug("doMouseClicked state= {}", light.getState());
318        }
319        if (light.getState() == jmri.Light.OFF) {
320            light.setState(jmri.Light.ON);
321        } else {
322            light.setState(jmri.Light.OFF);
323        }
324    }
325
326    @Override
327    public void dispose() {
328        if (light != null) {
329            light.removePropertyChangeListener(this);
330        }
331        light = null;
332
333        off = null;
334        on = null;
335        inconsistent = null;
336        unknown = null;
337
338        super.dispose();
339    }
340
341    private static final Logger log = LoggerFactory.getLogger(LightIcon.class);
342}