001package jmri.jmrit.display;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.awt.event.FocusAdapter;
006import java.awt.event.FocusEvent;
007import java.awt.event.KeyAdapter;
008import java.awt.event.KeyEvent;
009
010import javax.annotation.Nonnull;
011import javax.swing.JComponent;
012import javax.swing.JLabel;
013import javax.swing.JPanel;
014import javax.swing.JSpinner;
015import javax.swing.SpinnerNumberModel;
016
017import jmri.InstanceManager;
018import jmri.Memory;
019import jmri.NamedBeanHandle;
020import jmri.NamedBean.DisplayOptions;
021import jmri.util.swing.*;
022
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * An icon to display and input a Memory value in a TextField.
028 * <p>
029 * Handles the case of either a String or an Integer in the Memory, preserving
030 * what it finds.
031 *
032 * @author Pete Cressman Copyright (c) 2009
033 * @since 2.7.2
034 */
035public class MemoryInputIcon extends PositionableJPanelWithColumns implements java.beans.PropertyChangeListener {
036
037    // the associated Memory object
038    private NamedBeanHandle<Memory> namedMemory;
039
040    private final java.awt.event.MouseListener _mouseListener = JmriMouseListener.adapt(this);
041    private final java.awt.event.MouseMotionListener _mouseMotionListener = JmriMouseMotionListener.adapt(this);
042
043    public MemoryInputIcon(int nCols, Editor editor) {
044        super(editor);
045        _nCols = nCols;
046        setDisplayLevel(Editor.LABELS);
047
048        setLayout(new java.awt.GridBagLayout());
049        add(_textBox, new java.awt.GridBagConstraints());
050        _textBox.addKeyListener(new KeyAdapter() {
051            @Override
052            public void keyReleased(KeyEvent e) {
053                int key = e.getKeyCode();
054                if (key == KeyEvent.VK_ENTER) {
055                    updateMemory();
056                    transferFocus();  // this field should no longer be in edit mode
057                }
058            }
059        });
060        _textBox.setColumns(_nCols);
061        _textBox.setBorder(null);  // drop default border so user can configure entire border appearance
062        _textBox.addFocusListener(new FocusAdapter() {
063            @Override
064            public void focusLost(FocusEvent e) {
065                updateMemory();
066            }
067        });
068        _textBox.addMouseMotionListener(_mouseMotionListener);
069        _textBox.addMouseListener(_mouseListener);
070        setPopupUtility(new PositionablePopupUtil(this, _textBox));
071    }
072
073    @Override
074    public Positionable deepClone() {
075        MemoryInputIcon pos = new MemoryInputIcon(_nCols, _editor);
076        return finishClone(pos);
077    }
078
079    protected Positionable finishClone(MemoryInputIcon pos) {
080        pos.setMemory(namedMemory.getName());
081        return super.finishClone(pos);
082    }
083
084    @Override
085    public JComponent getTextComponent() {
086        return _textBox;
087    }
088
089    @Override
090    public void mouseExited(JmriMouseEvent e) {
091        updateMemory();
092        super.mouseExited(e);
093        transferFocus();  // this field should no longer be in edit mode
094    }
095
096    /**
097     * Attached a named Memory to this display item
098     *
099     * @param pName Used as a system/user name to lookup the Memory object
100     */
101    public void setMemory(String pName) {
102        log.debug("setMemory for memory= {}", pName);
103        if (InstanceManager.getNullableDefault(jmri.MemoryManager.class) != null) {
104            try {
105                Memory memory = InstanceManager.memoryManagerInstance().
106                        provideMemory(pName);
107                setMemory(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, memory));
108            } catch (IllegalArgumentException e) {
109                log.error("Memory '{}' not available, icon won't see changes", pName);
110            }
111        } else {
112            log.error("No MemoryManager for this protocol, icon won't see changes");
113        }
114        updateSize();
115    }
116
117    /**
118     * Attached a named Memory to this display item
119     *
120     * @param m The Memory object
121     */
122    public void setMemory(NamedBeanHandle<Memory> m) {
123        if (namedMemory != null) {
124            getMemory().removePropertyChangeListener(this);
125        }
126        namedMemory = m;
127        if (namedMemory != null) {
128            getMemory().addPropertyChangeListener(this, namedMemory.getName(), "Memory Input Icon");
129            displayState();
130            setName(namedMemory.getName());
131        }
132    }
133
134    public NamedBeanHandle<Memory> getNamedMemory() {
135        return namedMemory;
136    }
137
138    public Memory getMemory() {
139        if (namedMemory == null) {
140            return null;
141        }
142        return namedMemory.getBean();
143    }
144
145    @Override
146    public jmri.NamedBean getNamedBean() {
147        return getMemory();
148    }
149
150    // update icon as state of Memory changes
151    @Override
152    public void propertyChange(java.beans.PropertyChangeEvent e) {
153        if (e.getPropertyName().equals("value")) {
154            displayState();
155        }
156    }
157
158    @Override
159    @Nonnull
160    public String getTypeString() {
161        return Bundle.getMessage("PositionableType_MemoryInputIcon");
162    }
163
164    @Override
165    public String getNameString() {
166        String name;
167        if (namedMemory == null) {
168            name = Bundle.getMessage("NotConnected");
169        } else {
170            name = getMemory().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
171        }
172        return name;
173    }
174
175    @Override
176    public void mouseMoved(JmriMouseEvent e) {
177        super.mouseMoved(e);
178        updateMemory();
179    }
180
181    private void updateMemory() {
182        if (namedMemory == null) {
183            return;
184        }
185        String str = _textBox.getText();
186        getMemory().setValue(str);
187    }
188
189    @Override
190    public boolean setEditIconMenu(javax.swing.JPopupMenu popup) {
191        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameMemory"));
192        popup.add(new javax.swing.AbstractAction(txt) {
193            @Override
194            public void actionPerformed(ActionEvent e) {
195                edit();
196            }
197        });
198        return true;
199    }
200
201    /**
202     * Popup menu iconEditor's ActionListener
203     */
204    SpinnerNumberModel _spinModel = new SpinnerNumberModel(3, 1, 100, 1);
205
206    @Override
207    protected void edit() {
208        _iconEditor = new IconAdder("Memory") {
209            final JSpinner spinner = new JSpinner(_spinModel);
210
211            @Override
212            protected void addAdditionalButtons(JPanel p) {
213                ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().setColumns(2);
214                spinner.setMaximumSize(spinner.getPreferredSize());
215                spinner.setValue(_textBox.getColumns());
216                JPanel p2 = new JPanel();
217                //p2.setLayout(new BoxLayout(p2, BoxLayout.X_AXIS));
218                //p2.setLayout(new FlowLayout(FlowLayout.TRAILING));
219                p2.add(new JLabel(Bundle.getMessage("NumColsLabel")));
220                p2.add(spinner);
221                p.add(p2);
222                p.setVisible(true);
223            }
224        };
225
226        makeIconEditorFrame(this, "Memory", true, _iconEditor);
227        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.memoryPickModelInstance());
228        ActionListener addIconAction = a -> editMemory();
229        _iconEditor.makeIconPanel(false);
230        _iconEditor.complete(addIconAction, false, true, true);
231        _iconEditor.setSelection(getMemory());
232    }
233
234    void editMemory() {
235        setMemory(_iconEditor.getTableSelection().getDisplayName());
236        _nCols = _spinModel.getNumber().intValue();
237        _textBox.setColumns(_nCols);
238        setSize(getPreferredSize().width + 1, getPreferredSize().height);
239        _iconEditorFrame.dispose();
240        _iconEditorFrame = null;
241        _iconEditor = null;
242        validate();
243    }
244
245    /**
246     * Drive the current state of the display from the state of the Memory.
247     */
248    public void displayState() {
249        log.debug("displayState");
250        if (namedMemory == null) {  // leave alone if not connected yet
251            return;
252        }
253        Object show = getMemory().getValue();
254        if (show != null) {
255            _textBox.setText(show.toString());
256        } else {
257            _textBox.setText("");
258        }
259    }
260
261    @Override
262    void cleanup() {
263        if (namedMemory != null) {
264            getMemory().removePropertyChangeListener(this);
265        }
266        if (_textBox != null) {
267            _textBox.removeMouseMotionListener(_mouseMotionListener);
268            _textBox.removeMouseListener(_mouseListener);
269        }
270        namedMemory = null;
271    }
272
273    private static final Logger log = LoggerFactory.getLogger(MemoryInputIcon.class);
274}