001package jmri.jmrit.display;
002
003import java.awt.Dimension;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import java.beans.PropertyChangeListener;
007
008import javax.annotation.Nonnull;
009import javax.swing.AbstractAction;
010import javax.swing.JSpinner;
011import javax.swing.SpinnerNumberModel;
012import javax.swing.event.ChangeEvent;
013import javax.swing.event.ChangeListener;
014
015import jmri.InstanceManager;
016import jmri.NamedBeanHandle;
017import jmri.NamedBean.DisplayOptions;
018import jmri.jmrit.logixng.GlobalVariable;
019import jmri.jmrit.logixng.GlobalVariableManager;
020import jmri.util.swing.*;
021
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025/**
026 * An icon to display a status of a GlobalVariable in a JSpinner.
027 * <p>
028 * Handles the case of either a String or an Integer in the GlobalVariable, preserving
029 * what it finds.
030 *
031 * @author Bob Jacobsen     Copyright (c) 2009
032 * @author Daniel Bergqvist Copyright (C) 2022
033 * @since 2.7.2
034 */
035public class GlobalVariableSpinnerIcon extends PositionableJPanel implements ChangeListener, PropertyChangeListener {
036
037    int _min = 0;
038    int _max = 100;
039    JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, _min, _max, 1));
040    // the associated GlobalVariable object
041    //GlobalVariable globalVariable = null;
042    private NamedBeanHandle<GlobalVariable> namedGlobalVariable;
043
044    private final java.awt.event.MouseListener _mouseListener = JmriMouseListener.adapt(this);
045    private final java.awt.event.MouseMotionListener _mouseMotionListener = JmriMouseMotionListener.adapt(this);
046
047    public GlobalVariableSpinnerIcon(Editor editor) {
048        super(editor);
049        setDisplayLevel(Editor.LABELS);
050
051        setLayout(new java.awt.GridBagLayout());
052        add(spinner, new java.awt.GridBagConstraints());
053        spinner.addChangeListener(this);
054        javax.swing.JTextField textBox = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
055        textBox.addMouseMotionListener(_mouseMotionListener);
056        textBox.addMouseListener(_mouseListener);
057        setPopupUtility(new PositionablePopupUtil(this, textBox));
058    }
059
060    @Override
061    public Positionable deepClone() {
062        GlobalVariableSpinnerIcon pos = new GlobalVariableSpinnerIcon(_editor);
063        return finishClone(pos);
064    }
065
066    protected Positionable finishClone(GlobalVariableSpinnerIcon pos) {
067        pos.setGlobalVariable(namedGlobalVariable.getName());
068        return super.finishClone(pos);
069    }
070
071    @Override
072    public javax.swing.JComponent getTextComponent() {
073        return ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
074    }
075
076    @Override
077    public Dimension getSize() {
078        if (log.isDebugEnabled()) {
079            Dimension d = spinner.getSize();
080            log.debug("spinner width= {}, height= {}", d.width, d.height);
081            java.awt.Rectangle rect = getBounds(null);
082            log.debug("Bounds rect= ({},{}) width= {}, height= {}",
083                    rect.x, rect.y, rect.width, rect.height);
084            d = super.getSize();
085            log.debug("Panel width= {}, height= {}", d.width, d.height);
086        }
087        return super.getSize();
088    }
089
090    /**
091     * Attached a named GlobalVariable to this display item
092     *
093     * @param pName Used as a system/user name to lookup the GlobalVariable object
094     */
095    public void setGlobalVariable(String pName) {
096        log.debug("setGlobalVariable for memory= {}", pName);
097        if (InstanceManager.getNullableDefault(GlobalVariableManager.class) != null) {
098            try {
099                GlobalVariable globalVariable = InstanceManager.getDefault(GlobalVariableManager.class).getGlobalVariable(pName);
100                setGlobalVariable(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, globalVariable));
101            } catch (IllegalArgumentException e) {
102                log.error("GlobalVariable '{}' not available, icon won't see changes", pName);
103            }
104        } else {
105            log.error("No GlobalVariableManager for this protocol, icon won't see changes");
106        }
107        updateSize();
108    }
109
110    /**
111     * Attached a named GlobalVariable to this display item
112     *
113     * @param m The GlobalVariable object
114     */
115    public void setGlobalVariable(NamedBeanHandle<GlobalVariable> m) {
116        if (namedGlobalVariable != null) {
117            getGlobalVariable().removePropertyChangeListener(this);
118        }
119        namedGlobalVariable = m;
120        if (namedGlobalVariable != null) {
121            getGlobalVariable().addPropertyChangeListener(this, namedGlobalVariable.getName(), "GlobalVariable Spinner Icon");
122            displayState();
123            setName(namedGlobalVariable.getName());
124        }
125    }
126
127    public NamedBeanHandle<GlobalVariable> getNamedGlobalVariable() {
128        return namedGlobalVariable;
129    }
130
131    // update icon as state of GlobalVariable changes
132    @Override
133    public void propertyChange(java.beans.PropertyChangeEvent e) {
134        if (e.getPropertyName().equals("value")) {
135            displayState();
136        }
137    }
138
139    public GlobalVariable getGlobalVariable() {
140        if (namedGlobalVariable == null) {
141            return null;
142        }
143        return namedGlobalVariable.getBean();
144    }
145
146    @Override
147    public void stateChanged(ChangeEvent e) {
148        spinnerUpdated();
149    }
150
151    @Override
152    @Nonnull
153    public String getTypeString() {
154        return Bundle.getMessage("PositionableType_GlobalVariableSpinnerIcon");
155    }
156
157    @Override
158    public String getNameString() {
159        String name;
160        if (namedGlobalVariable == null) {
161            name = Bundle.getMessage("NotConnected");
162        } else {
163            name = getGlobalVariable().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
164        }
165        return name;
166    }
167
168    /*
169     public void setSelectable(boolean b) {selectable = b;}
170     public boolean isSelectable() { return selectable;}
171     boolean selectable = false;
172     */
173    @Override
174    public boolean setEditIconMenu(javax.swing.JPopupMenu popup) {
175        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameGlobalVariable"));
176        popup.add(new AbstractAction(txt) {
177            @Override
178            public void actionPerformed(ActionEvent e) {
179                edit();
180            }
181        });
182        return true;
183    }
184
185    @Override
186    protected void edit() {
187        makeIconEditorFrame(this, "GlobalVariable", true, null);
188        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.globalVariablePickModelInstance());
189        ActionListener addIconAction = a -> editGlobalVariable();
190        _iconEditor.complete(addIconAction, false, false, true);
191        _iconEditor.setSelection(getGlobalVariable());
192    }
193
194    void editGlobalVariable() {
195        setGlobalVariable(_iconEditor.getTableSelection().getDisplayName());
196        setSize(getPreferredSize().width, getPreferredSize().height);
197        _iconEditorFrame.dispose();
198        _iconEditorFrame = null;
199        _iconEditor = null;
200        invalidate();
201    }
202
203    /**
204     * Drive the current state of the display from the state of the GlobalVariable.
205     */
206    public void displayState() {
207        log.debug("displayState");
208        if (namedGlobalVariable == null) {  // leave alone if not connected yet
209            return;
210        }
211        if (getGlobalVariable().getValue() == null) {
212            return;
213        }
214        Integer num = null;
215        if (getGlobalVariable().getValue().getClass() == String.class) {
216            try {
217                num = Integer.valueOf((String) getGlobalVariable().getValue());
218            } catch (NumberFormatException e) {
219                return;
220            }
221        } else if (getGlobalVariable().getValue().getClass() == Integer.class) {
222            num = ((Number) getGlobalVariable().getValue()).intValue();
223        } else if (getGlobalVariable().getValue().getClass() == Float.class) {
224            num = Math.round((Float) getGlobalVariable().getValue());
225            log.debug("num= {}", num);
226        } else {
227            //spinner.setValue(getGlobalVariable().getValue());
228            return;
229        }
230        int n = num;
231        if (n > _max) {
232            num = _max;
233        } else if (n < _min) {
234            num = _min;
235        }
236        spinner.setValue(num);
237    }
238
239    @Override
240    public void mouseExited(JmriMouseEvent e) {
241        spinnerUpdated();
242        super.mouseExited(e);
243    }
244
245    protected void spinnerUpdated() {
246        if (namedGlobalVariable == null) {
247            return;
248        }
249        if (getGlobalVariable().getValue() == null) {
250            getGlobalVariable().setValue(spinner.getValue());
251            return;
252        }
253        // Spinner is always an Integer, but memory can contain Integer or String
254        if (getGlobalVariable().getValue().getClass() == String.class) {
255            String newValue = "" + spinner.getValue();
256            if (!getGlobalVariable().getValue().equals(newValue)) {
257                getGlobalVariable().setValue(newValue);
258            }
259        } else {
260            getGlobalVariable().setValue(spinner.getValue());
261        }
262    }
263
264    public String getValue() {
265        return "" + spinner.getValue();
266    }
267
268    @Override
269    void cleanup() {
270        if (namedGlobalVariable != null) {
271            getGlobalVariable().removePropertyChangeListener(this);
272        }
273        if (spinner != null) {
274            spinner.removeChangeListener(this);
275            ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().removeMouseMotionListener(_mouseMotionListener);
276            ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().removeMouseListener(_mouseListener);
277        }
278        namedGlobalVariable = null;
279    }
280
281    private final static Logger log = LoggerFactory.getLogger(GlobalVariableSpinnerIcon.class);
282}