001package jmri.jmrit.display;
002
003import java.awt.Color;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import java.util.Map;
007
008import javax.annotation.Nonnull;
009import javax.swing.AbstractAction;
010import javax.swing.JPopupMenu;
011import javax.swing.JSeparator;
012
013import jmri.InstanceManager;
014import jmri.NamedBeanHandle;
015import jmri.Reportable;
016import jmri.NamedBean.DisplayOptions;
017import jmri.jmrit.catalog.NamedIcon;
018import jmri.jmrit.logixng.GlobalVariable;
019import jmri.jmrit.logixng.GlobalVariableManager;
020import jmri.util.swing.JmriJOptionPane;
021import jmri.util.swing.JmriMouseEvent;
022
023/**
024 * An icon to display a status of a GlobalVariable.
025 * <p>
026 * The value of the global variable can't be changed with this icon.
027 *
028 * @author Bob Jacobsen     Copyright (c) 2004
029 * @author Daniel Bergqvist Copyright (C) 2022
030 */
031public class GlobalVariableIcon extends MemoryOrGVIcon implements java.beans.PropertyChangeListener/*, DropTargetListener*/ {
032
033    NamedIcon defaultIcon = null;
034    // the map of icons
035    java.util.HashMap<String, NamedIcon> map = null;
036    private NamedBeanHandle<GlobalVariable> namedGlobalVariable;
037
038    public GlobalVariableIcon(String s, Editor editor) {
039        super(s, editor);
040        resetDefaultIcon();
041        _namedIcon = defaultIcon;
042        //By default all content is left justified
043        _popupUtil.setJustification(LEFT);
044    }
045
046    public GlobalVariableIcon(NamedIcon s, Editor editor) {
047        super(s, editor);
048        setDisplayLevel(Editor.LABELS);
049        defaultIcon = s;
050        _popupUtil.setJustification(LEFT);
051        log.debug("GlobalVariableIcon ctor= {}", GlobalVariableIcon.class.getName());
052    }
053
054    @Override
055    public Positionable deepClone() {
056        GlobalVariableIcon pos = new GlobalVariableIcon("", _editor);
057        return finishClone(pos);
058    }
059
060    protected Positionable finishClone(GlobalVariableIcon pos) {
061        pos.setGlobalVariable(namedGlobalVariable.getName());
062        pos.setOriginalLocation(getOriginalX(), getOriginalY());
063        if (map != null) {
064            for (Map.Entry<String, NamedIcon> entry : map.entrySet()) {
065                String url = entry.getValue().getName();
066                pos.addKeyAndIcon(NamedIcon.getIconByName(url), entry.getKey());
067            }
068        }
069        return super.finishClone(pos);
070    }
071
072    public void resetDefaultIcon() {
073        defaultIcon = new NamedIcon("resources/icons/misc/X-red.gif",
074                "resources/icons/misc/X-red.gif");
075    }
076
077    public void setDefaultIcon(NamedIcon n) {
078        defaultIcon = n;
079    }
080
081    public NamedIcon getDefaultIcon() {
082        return defaultIcon;
083    }
084
085    private void setMap() {
086        if (map == null) {
087            map = new java.util.HashMap<>();
088        }
089    }
090
091    /**
092     * Attach a named GlobalVariable to this display item.
093     *
094     * @param pName Used as a system/user name to lookup the GlobalVariable object
095     */
096    public void setGlobalVariable(String pName) {
097        if (InstanceManager.getNullableDefault(GlobalVariableManager.class) != null) {
098            try {
099                GlobalVariable globalVariable = InstanceManager.getDefault(GlobalVariableManager.class).getGlobalVariable(pName);
100                setGlobalVariable(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, globalVariable));
101            } catch (IllegalArgumentException e) {
102                log.error("GlobalVariable '{}' not available, icon won't see changes", pName);
103            }
104        } else {
105            log.error("No GlobalVariableManager for this protocol, icon won't see changes");
106        }
107        updateSize();
108    }
109
110    /**
111     * Attach a named GlobalVariable to this display item.
112     *
113     * @param m The GlobalVariable object
114     */
115    public void setGlobalVariable(NamedBeanHandle<GlobalVariable> m) {
116        if (namedGlobalVariable != null) {
117            getGlobalVariable().removePropertyChangeListener(this);
118        }
119        namedGlobalVariable = m;
120        if (namedGlobalVariable != null) {
121            getGlobalVariable().addPropertyChangeListener(this, namedGlobalVariable.getName(), "GlobalVariable Icon");
122            displayState();
123            setName(namedGlobalVariable.getName());
124        }
125    }
126
127    public NamedBeanHandle<GlobalVariable> getNamedGlobalVariable() {
128        return namedGlobalVariable;
129    }
130
131    public GlobalVariable getGlobalVariable() {
132        if (namedGlobalVariable == null) {
133            return null;
134        }
135        return namedGlobalVariable.getBean();
136    }
137
138    @Override
139    public jmri.NamedBean getNamedBean() {
140        return getGlobalVariable();
141    }
142
143    public java.util.HashMap<String, NamedIcon> getMap() {
144        return map;
145    }
146
147    // display icons
148    public void addKeyAndIcon(NamedIcon icon, String keyValue) {
149        if (map == null) {
150            setMap(); // initialize if needed
151        }
152        map.put(keyValue, icon);
153        // drop size cache
154        //height = -1;
155        //width = -1;
156        displayState(); // in case changed
157    }
158
159    // update icon as state of GlobalVariable changes
160    @Override
161    public void propertyChange(java.beans.PropertyChangeEvent e) {
162        if (log.isDebugEnabled()) {
163            log.debug("property change: {} is now {}",
164                    e.getPropertyName(), e.getNewValue());
165        }
166        if (e.getPropertyName().equals("value")) {
167            displayState();
168        }
169        if (e.getSource() instanceof jmri.Throttle) {
170            if (e.getPropertyName().equals(jmri.Throttle.ISFORWARD)) {
171                Boolean boo = (Boolean) e.getNewValue();
172                if (boo) {
173                    flipIcon(NamedIcon.NOFLIP);
174                } else {
175                    flipIcon(NamedIcon.HORIZONTALFLIP);
176                }
177            }
178        }
179    }
180
181    @Override
182    @Nonnull
183    public String getTypeString() {
184        return Bundle.getMessage("PositionableType_GlobalVariableIcon");
185    }
186
187    @Override
188    public String getNameString() {
189        String name;
190        if (namedGlobalVariable == null) {
191            name = Bundle.getMessage("NotConnected");
192        } else {
193            name = getGlobalVariable().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
194        }
195        return name;
196    }
197
198    public void setSelectable(boolean b) {
199        selectable = b;
200    }
201
202    public boolean isSelectable() {
203        return selectable;
204    }
205    boolean selectable = false;
206
207    @Override
208    public boolean showPopUp(JPopupMenu popup) {
209        if (isEditable() && selectable) {
210            popup.add(new JSeparator());
211
212            for (String key : map.keySet()) {
213                //String value = ((NamedIcon)map.get(key)).getName();
214                popup.add(new AbstractAction(key) {
215
216                    @Override
217                    public void actionPerformed(ActionEvent e) {
218                        String key = e.getActionCommand();
219                        setValue(key);
220                    }
221                });
222            }
223            return true;
224        }  // end of selectable
225        return false;
226    }
227
228    /**
229     * Text edits cannot be done to GlobalVariable text - override
230     */
231    @Override
232    public boolean setTextEditMenu(JPopupMenu popup) {
233        popup.add(new AbstractAction(Bundle.getMessage("EditGlobalVariableValue")) {
234
235            @Override
236            public void actionPerformed(ActionEvent e) {
237                editGlobalVariableValue();
238            }
239        });
240        return true;
241    }
242
243    protected void flipIcon(int flip) {
244        if (_namedIcon != null) {
245            _namedIcon.flip(flip, this);
246        }
247        updateSize();
248        repaint();
249    }
250    Color _saveColor;
251
252    /**
253     * Drive the current state of the display from the state of the GlobalVariable.
254     */
255    @Override
256    public void displayState() {
257        log.debug("displayState()");
258
259        if (namedGlobalVariable == null) {  // use default if not connected yet
260            setIcon(defaultIcon);
261            updateSize();
262            return;
263        }
264        Object key = getGlobalVariable().getValue();
265        displayState(key);
266    }
267
268    /**
269     * Special method to transfer a setAttributes call from the LE version of
270     * GlobalVariableIcon. This eliminates the need to change references to public.
271     *
272     * @since 4.11.6
273     * @param util The LE popup util object.
274     * @param that The current positional object (this).
275     */
276    public void setAttributes(PositionablePopupUtil util, Positionable that) {
277        _editor.setAttributes(util, that);
278    }
279
280    protected void displayState(Object key) {
281        log.debug("displayState({})", key);
282        if (key != null) {
283            if (map == null) {
284                Object val = key;
285                // no map, attempt to show object directly
286                if (val instanceof String) {
287                    String str = (String) val;
288                    _icon = false;
289                    _text = true;
290                    setText(str);
291                    updateIcon(null);
292                    if (log.isDebugEnabled()) {
293                        log.debug("String str= \"{}\" str.trim().length()= {}", str, str.trim().length());
294                        log.debug("  maxWidth()= {}, maxHeight()= {}", maxWidth(), maxHeight());
295                        log.debug("  getBackground(): {}", getBackground());
296                        log.debug("  _editor.getTargetPanel().getBackground(): {}", _editor.getTargetPanel().getBackground());
297                        log.debug("  setAttributes to getPopupUtility({}) with", getPopupUtility());
298                        log.debug("     hasBackground() {}", getPopupUtility().hasBackground());
299                        log.debug("     getBackground() {}", getPopupUtility().getBackground());
300                        log.debug("    on editor {}", _editor);
301                    }
302                    _editor.setAttributes(getPopupUtility(), this);
303                } else if (val instanceof javax.swing.ImageIcon) {
304                    _icon = true;
305                    _text = false;
306                    setIcon((javax.swing.ImageIcon) val);
307                    setText(null);
308                } else if (val instanceof Number) {
309                    _icon = false;
310                    _text = true;
311                    setText(val.toString());
312                    setIcon(null);
313                } else if (val instanceof jmri.IdTag){
314                    // most IdTags are Reportable objects, so
315                    // this needs to be before Reportable
316                    _icon = false;
317                    _text = true;
318                    setIcon(null);
319                    setText(((jmri.IdTag)val).getDisplayName());
320                } else if (val instanceof Reportable) {
321                    _icon = false;
322                    _text = true;
323                    setText(((Reportable)val).toReportString());
324                    setIcon(null);
325                } else {
326                    // don't recognize the type, do our best with toString
327                    log.debug("display current value of {} as String, val= {} of Class {}",
328                            getNameString(), val, val.getClass().getName());
329                    _icon = false;
330                    _text = true;
331                    setText(val.toString());
332                    setIcon(null);
333                }
334            } else {
335                // map exists, use it
336                NamedIcon newicon = map.get(key.toString());
337                if (newicon != null) {
338
339                    setText(null);
340                    super.setIcon(newicon);
341                } else {
342                    // no match, use default
343                    _icon = true;
344                    _text = false;
345                    setIcon(defaultIcon);
346                    setText(null);
347                }
348            }
349        } else {
350            log.debug("object null");
351            _icon = true;
352            _text = false;
353            setIcon(defaultIcon);
354            setText(null);
355        }
356        updateSize();
357    }
358
359    /*As the size of a global variable label can change we want to adjust
360     the position of the x,y if the width is fixed*/
361    @SuppressWarnings("hiding")  // OVerrides a value in SwingConstants
362    static final int LEFT = 0x00;
363    @SuppressWarnings("hiding")  // OVerrides a value in SwingConstants
364    static final int RIGHT = 0x02;
365    static final int CENTRE = 0x04;
366
367    @Override
368    public void updateSize() {
369        if (_popupUtil.getFixedWidth() == 0) {
370            //setSize(maxWidth(), maxHeight());
371            switch (_popupUtil.getJustification()) {
372                case LEFT:
373                    super.setLocation(getOriginalX(), getOriginalY());
374                    break;
375                case RIGHT:
376                    super.setLocation(getOriginalX() - maxWidth(), getOriginalY());
377                    break;
378                case CENTRE:
379                    super.setLocation(getOriginalX() - (maxWidth() / 2), getOriginalY());
380                    break;
381                default:
382                    log.warn("Unhandled justification code: {}", _popupUtil.getJustification());
383                    break;
384            }
385            setSize(maxWidth(), maxHeight());
386        } else {
387            super.updateSize();
388            if (_icon && _namedIcon != null) {
389                _namedIcon.reduceTo(maxWidthTrue(), maxHeightTrue(), 0.2);
390            }
391        }
392    }
393
394    /*Stores the original location of the memory, this is then used to calculate
395     the position of the text dependant upon the justification*/
396    private int originalX = 0;
397    private int originalY = 0;
398
399    public void setOriginalLocation(int x, int y) {
400        originalX = x;
401        originalY = y;
402        updateSize();
403    }
404
405    @Override
406    public int getOriginalX() {
407        return originalX;
408    }
409
410    @Override
411    public int getOriginalY() {
412        return originalY;
413    }
414
415    @Override
416    public void setLocation(int x, int y) {
417        if (_popupUtil.getFixedWidth() == 0) {
418            setOriginalLocation(x, y);
419        } else {
420            super.setLocation(x, y);
421        }
422    }
423
424    @Override
425    public boolean setEditIconMenu(JPopupMenu popup) {
426        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameGlobalVariable"));
427        popup.add(new AbstractAction(txt) {
428            @Override
429            public void actionPerformed(ActionEvent e) {
430                edit();
431            }
432        });
433        return true;
434    }
435
436    @Override
437    protected void edit() {
438        makeIconEditorFrame(this, "GlobalVariable", true, null);
439        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.globalVariablePickModelInstance());
440        ActionListener addIconAction = (ActionEvent a) -> editGlobalVariable();
441        _iconEditor.complete(addIconAction, false, false, true);
442        _iconEditor.setSelection(getGlobalVariable());
443    }
444
445    void editGlobalVariable() {
446        setGlobalVariable(_iconEditor.getTableSelection().getDisplayName());
447        updateSize();
448        _iconEditorFrame.dispose();
449        _iconEditorFrame = null;
450        _iconEditor = null;
451        invalidate();
452    }
453
454    @Override
455    public void dispose() {
456        if (getGlobalVariable() != null) {
457            getGlobalVariable().removePropertyChangeListener(this);
458        }
459        namedGlobalVariable = null;
460        super.dispose();
461    }
462
463    @Override
464    public void doMouseClicked(JmriMouseEvent e) {
465        if (e.getClickCount() == 2) { // double click?
466            if (!getEditor().isEditable() && isValueEditDisabled()) {
467                log.debug("Double click global variable value edit is disabled");
468                return;
469            }
470            editGlobalVariableValue();
471        }
472    }
473
474    protected void editGlobalVariableValue() {
475
476        String reval = (String)JmriJOptionPane.showInputDialog(this,
477                                     Bundle.getMessage("EditCurrentGlobalVariableValue", namedGlobalVariable.getName()),
478                                     getGlobalVariable().getValue());
479
480        setValue(reval);
481        updateSize();
482    }
483
484    protected Object getValue() {
485        if (getGlobalVariable() == null) {
486            return null;
487        }
488        return getGlobalVariable().getValue();
489    }
490
491    protected void setValue(Object val) {
492        getGlobalVariable().setValue(val);
493    }
494
495    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GlobalVariableIcon.class);
496
497}