001package jmri.jmrit.display;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005
006import javax.annotation.Nonnull;
007import javax.swing.AbstractButton;
008import javax.swing.BoxLayout;
009import javax.swing.DefaultListModel;
010import javax.swing.JButton;
011import javax.swing.JComboBox;
012import javax.swing.JLabel;
013import javax.swing.JList;
014import javax.swing.JPanel;
015import javax.swing.JScrollPane;
016import javax.swing.JTextField;
017import javax.swing.SwingConstants;
018
019import jmri.InstanceManager;
020import jmri.Memory;
021import jmri.NamedBeanHandle;
022import jmri.NamedBean.DisplayOptions;
023import jmri.util.swing.*;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * An icon to display and input a Memory value in a TextField.
030 * <p>
031 * Handles the case of either a String or an Integer in the Memory, preserving
032 * what it finds.
033 *
034 * @author Pete Cressman Copyright (c) 2012
035 * @since 2.7.2
036 */
037public class MemoryComboIcon extends MemoryOrGVComboIcon
038        implements java.beans.PropertyChangeListener, ActionListener {
039
040    private final JComboBox<String> _comboBox;
041    private final ComboModel _model;
042
043    // the associated Memory object
044    private NamedBeanHandle<Memory> namedMemory;
045
046    private final java.awt.event.MouseListener _mouseListener = JmriMouseListener.adapt(this);
047    private final java.awt.event.MouseMotionListener _mouseMotionListener = JmriMouseMotionListener.adapt(this);
048
049    public MemoryComboIcon(Editor editor, String[] list) {
050        super(editor);
051        if (list != null) {
052            _model = new ComboModel(list);
053        } else {
054            _model = new ComboModel();
055        }
056        _comboBox = new JComboBox<>(_model);
057        _comboBox.addActionListener(this);
058        setDisplayLevel(Editor.LABELS);
059
060        setLayout(new java.awt.GridBagLayout());
061        add(_comboBox);
062        _comboBox.addMouseListener(JmriMouseListener.adapt(this));
063
064        for (int i = 0; i < _comboBox.getComponentCount(); i++) {
065            java.awt.Component component = _comboBox.getComponent(i);
066            if (component instanceof AbstractButton) {
067                component.addMouseListener(_mouseListener);
068                component.addMouseMotionListener(_mouseMotionListener);
069            }
070        }
071        setPopupUtility(new PositionablePopupUtil(this, _comboBox));
072    }
073
074    @Override
075    public JComboBox<String> getTextComponent() {
076        return _comboBox;
077    }
078
079    @Override
080    public Positionable deepClone() {
081        String[] list = new String[_model.getSize()];
082        for (int i = 0; i < _model.getSize(); i++) {
083            list[i] = _model.getElementAt(i);
084        }
085        MemoryComboIcon pos = new MemoryComboIcon(_editor, list);
086        return finishClone(pos);
087    }
088
089    protected Positionable finishClone(MemoryComboIcon pos) {
090        pos.setMemory(namedMemory.getName());
091        return super.finishClone(pos);
092    }
093
094    /**
095     * Attach a named Memory to this display item.
096     *
097     * @param pName used as a system/user name to look up the Memory object
098     */
099    public void setMemory(String pName) {
100        log.debug("setMemory for memory= {}", pName);
101        if (InstanceManager.getNullableDefault(jmri.MemoryManager.class) != null) {
102            try {
103                Memory memory = InstanceManager.memoryManagerInstance().provideMemory(pName);
104                setMemory(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, memory));
105            } catch (IllegalArgumentException e) {
106                log.error("No MemoryManager for this protocol, icon won't see changes");
107            }
108        }
109        updateSize();
110    }
111
112    /**
113     * Attach a named Memory to this display item.
114     *
115     * @param m The Memory object
116     */
117    public void setMemory(NamedBeanHandle<Memory> m) {
118        if (namedMemory != null) {
119            getMemory().removePropertyChangeListener(this);
120        }
121        namedMemory = m;
122        if (namedMemory != null) {
123            getMemory().addPropertyChangeListener(this, namedMemory.getName(), "Memory Input Icon");
124            displayState();
125            setName(namedMemory.getName());
126        }
127    }
128
129    public NamedBeanHandle<Memory> getNamedMemory() {
130        return namedMemory;
131    }
132
133    public Memory getMemory() {
134        if (namedMemory == null) {
135            return null;
136        }
137        return namedMemory.getBean();
138    }
139
140    @Override
141    public ComboModel getComboModel() {
142        return _model;
143    }
144
145    /**
146     * Display
147     */
148    @Override
149    public void actionPerformed(ActionEvent e) {
150        update();
151    }
152
153    // update icon as state of Memory changes
154    @Override
155    public void propertyChange(java.beans.PropertyChangeEvent e) {
156        if (e.getPropertyName().equals("value")) {
157            displayState();
158        }
159    }
160
161    @Override
162    @Nonnull
163    public String getTypeString() {
164        return Bundle.getMessage("PositionableType_MemoryComboIcon");
165    }
166
167    @Override
168    public String getNameString() {
169        String name;
170        if (namedMemory == null) {
171            name = Bundle.getMessage("NotConnected");
172        } else {
173            name = getMemory().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
174        }
175        return name;
176    }
177
178    @Override
179    protected void update() {
180        if (namedMemory == null) {
181            return;
182        }
183        getMemory().setValue(_comboBox.getSelectedItem());
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    private DefaultListModel<String> _listModel;
202
203    @Override
204    protected void edit() {
205        _iconEditor = new IconAdder("Memory") {
206            JList<String> list;
207            final JButton bDel = new JButton(Bundle.getMessage("deleteSelection"));
208            final JButton bAdd = new JButton(Bundle.getMessage("addItem"));
209            final JTextField textfield = new JTextField(30);
210            int idx;
211
212            @Override
213            protected void addAdditionalButtons(JPanel p) {
214                _listModel = new DefaultListModel<>();
215                bDel.addActionListener(a -> {
216                    if ( list == null ){ return; }
217                    idx = list.getSelectedIndex();
218                    if (idx >= 0) {
219                        _listModel.removeElementAt(idx);
220                    }
221                });
222                bAdd.addActionListener(a -> {
223                    String text = textfield.getText();
224                    if (text == null || list == null || text.length() == 0 || _listModel.indexOf(text) >= 0) {
225                        return;
226                    }
227                    idx = list.getSelectedIndex();
228                    if (idx < 0) {
229                        idx = _listModel.getSize();
230                    }
231                    _listModel.add(idx, text);
232                });
233                for (int i = 0; i < _model.getSize(); i++) {
234                    _listModel.add(i, _model.getElementAt(i));
235                }
236                list = new JList<>(_listModel);
237                JScrollPane scrollPane = new JScrollPane(list);
238                JPanel p1 = new JPanel();
239                p1.add(new JLabel(Bundle.getMessage("comboList")));
240                p.add(p1);
241                p.add(scrollPane);
242                p1 = new JPanel();
243                JPanel pInner1 = new JPanel();
244                pInner1.setLayout(new BoxLayout(pInner1, BoxLayout.X_AXIS));
245                pInner1.add(new JLabel(Bundle.getMessage("newItem"), SwingConstants.RIGHT));
246                pInner1.add(textfield);
247                p1.add(pInner1);
248                p.add(p1);
249                JPanel p2 = new JPanel();
250                //p2.setLayout(new BoxLayout(p2, BoxLayout.Y_AXIS));
251                //p2.setLayout(new FlowLayout(FlowLayout.TRAILING));
252                p2.add(bDel);
253                p2.add(bAdd);
254                p.add(p2);
255                p.setVisible(true);
256            }
257        };
258
259        makeIconEditorFrame(this, "Memory", true, _iconEditor);
260        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.memoryPickModelInstance());
261        ActionListener addIconAction = a -> editMemory();
262
263        _iconEditor.makeIconPanel(false);
264        _iconEditor.complete(addIconAction, false, true, true);
265        _iconEditor.setSelection(getMemory());
266    }
267
268    void editMemory() {
269        jmri.NamedBean bean = _iconEditor.getTableSelection();
270        setMemory(bean.getDisplayName());
271        _model.removeAllElements();
272        for (int i = 0; i < _listModel.size(); i++) {
273            _model.addElement(_listModel.getElementAt(i));
274        }
275        setSize(getPreferredSize().width + 1, getPreferredSize().height);
276        _iconEditorFrame.dispose();
277        _iconEditorFrame = null;
278        _iconEditor = null;
279        validate();
280    }
281
282    /**
283     * Drive the current state of the display from the state of the Memory.
284     */
285    public void displayState() {
286        log.debug("displayState");
287        if (namedMemory == null) {  // leave alone if not connected yet
288            return;
289        }
290        _model.setSelectedItem(getMemory().getValue());
291    }
292
293    @Override
294    public void mouseExited(JmriMouseEvent e) {
295        _comboBox.setFocusable(false);
296        _comboBox.transferFocus();
297        super.mouseExited(e);
298    }
299
300    @Override
301    void cleanup() {
302        if (namedMemory != null) {
303            getMemory().removePropertyChangeListener(this);
304        }
305        if (_comboBox != null) {
306            for (int i = 0; i < _comboBox.getComponentCount(); i++) {
307                java.awt.Component component = _comboBox.getComponent(i);
308                if (component instanceof AbstractButton) {
309                    component.removeMouseListener(_mouseListener);
310                    component.removeMouseMotionListener(_mouseMotionListener);
311                }
312            }
313            _comboBox.removeMouseListener(_mouseListener);
314        }
315        namedMemory = null;
316    }
317
318    private final static Logger log = LoggerFactory.getLogger(MemoryComboIcon.class);
319
320}