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