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 jmri.NamedBean getNamedBean() {
146        return getMemory();
147    }
148
149    @Override
150    public void stateChanged(ChangeEvent e) {
151        spinnerUpdated();
152    }
153
154    @Override
155    @Nonnull
156    public String getTypeString() {
157        return Bundle.getMessage("PositionableType_MemorySpinnerIcon");
158    }
159
160    @Override
161    public String getNameString() {
162        String name;
163        if (namedMemory == null) {
164            name = Bundle.getMessage("NotConnected");
165        } else {
166            name = getMemory().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
167        }
168        return name;
169    }
170
171    /*
172     public void setSelectable(boolean b) {selectable = b;}
173     public boolean isSelectable() { return selectable;}
174     boolean selectable = false;
175     */
176    @Override
177    public boolean setEditIconMenu(javax.swing.JPopupMenu popup) {
178        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameMemory"));
179        popup.add(new AbstractAction(txt) {
180            @Override
181            public void actionPerformed(ActionEvent e) {
182                edit();
183            }
184        });
185        return true;
186    }
187
188    @Override
189    protected void edit() {
190        makeIconEditorFrame(this, "Memory", true, null);
191        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.memoryPickModelInstance());
192        ActionListener addIconAction = a -> editMemory();
193        _iconEditor.complete(addIconAction, false, true, true);
194        _iconEditor.setSelection(getMemory());
195    }
196
197    void editMemory() {
198        setMemory(_iconEditor.getTableSelection().getDisplayName());
199        setSize(getPreferredSize().width, getPreferredSize().height);
200        _iconEditorFrame.dispose();
201        _iconEditorFrame = null;
202        _iconEditor = null;
203        invalidate();
204    }
205
206    /**
207     * Drive the current state of the display from the state of the Memory.
208     */
209    public void displayState() {
210        log.debug("displayState");
211        if (namedMemory == null) {  // leave alone if not connected yet
212            return;
213        }
214        if (getMemory().getValue() == null) {
215            return;
216        }
217        Integer num = null;
218        if (getMemory().getValue().getClass() == String.class) {
219            try {
220                num = Integer.valueOf((String) getMemory().getValue());
221            } catch (NumberFormatException e) {
222                return;
223            }
224        } else if (getMemory().getValue().getClass() == Integer.class) {
225            num = ((Number) getMemory().getValue()).intValue();
226        } else if (getMemory().getValue().getClass() == Float.class) {
227            num = Math.round((Float) getMemory().getValue());
228            log.debug("num= {}", num);
229        } else {
230            //spinner.setValue(getMemory().getValue());
231            return;
232        }
233        int n = num;
234        if (n > _max) {
235            num = _max;
236        } else if (n < _min) {
237            num = _min;
238        }
239        spinner.setValue(num);
240    }
241
242    @Override
243    public void mouseExited(JmriMouseEvent e) {
244        spinnerUpdated();
245        super.mouseExited(e);
246    }
247
248    protected void spinnerUpdated() {
249        if (namedMemory == null) {
250            return;
251        }
252        if (getMemory().getValue() == null) {
253            getMemory().setValue(spinner.getValue());
254            return;
255        }
256        // Spinner is always an Integer, but memory can contain Integer or String
257        if (getMemory().getValue().getClass() == String.class) {
258            String newValue = "" + spinner.getValue();
259            if (!getMemory().getValue().equals(newValue)) {
260                getMemory().setValue(newValue);
261            }
262        } else {
263            getMemory().setValue(spinner.getValue());
264        }
265    }
266
267    public String getValue() {
268        return "" + spinner.getValue();
269    }
270
271    @Override
272    void cleanup() {
273        if (namedMemory != null) {
274            getMemory().removePropertyChangeListener(this);
275        }
276        if (spinner != null) {
277            spinner.removeChangeListener(this);
278            ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().removeMouseMotionListener(_mouseMotionListener);
279            ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().removeMouseListener(_mouseListener);
280        }
281        namedMemory = null;
282    }
283
284    private static final Logger log = LoggerFactory.getLogger(MemorySpinnerIcon.class);
285}