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