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