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