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    static final int LEFT = 0x00;
362    static final int RIGHT = 0x02;
363    static final int CENTRE = 0x04;
364
365    @Override
366    public void updateSize() {
367        if (_popupUtil.getFixedWidth() == 0) {
368            //setSize(maxWidth(), maxHeight());
369            switch (_popupUtil.getJustification()) {
370                case LEFT:
371                    super.setLocation(getOriginalX(), getOriginalY());
372                    break;
373                case RIGHT:
374                    super.setLocation(getOriginalX() - maxWidth(), getOriginalY());
375                    break;
376                case CENTRE:
377                    super.setLocation(getOriginalX() - (maxWidth() / 2), getOriginalY());
378                    break;
379                default:
380                    log.warn("Unhandled justification code: {}", _popupUtil.getJustification());
381                    break;
382            }
383            setSize(maxWidth(), maxHeight());
384        } else {
385            super.updateSize();
386            if (_icon && _namedIcon != null) {
387                _namedIcon.reduceTo(maxWidthTrue(), maxHeightTrue(), 0.2);
388            }
389        }
390    }
391
392    /*Stores the original location of the memory, this is then used to calculate
393     the position of the text dependant upon the justification*/
394    private int originalX = 0;
395    private int originalY = 0;
396
397    public void setOriginalLocation(int x, int y) {
398        originalX = x;
399        originalY = y;
400        updateSize();
401    }
402
403    @Override
404    public int getOriginalX() {
405        return originalX;
406    }
407
408    @Override
409    public int getOriginalY() {
410        return originalY;
411    }
412
413    @Override
414    public void setLocation(int x, int y) {
415        if (_popupUtil.getFixedWidth() == 0) {
416            setOriginalLocation(x, y);
417        } else {
418            super.setLocation(x, y);
419        }
420    }
421
422    @Override
423    public boolean setEditIconMenu(JPopupMenu popup) {
424        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameGlobalVariable"));
425        popup.add(new AbstractAction(txt) {
426            @Override
427            public void actionPerformed(ActionEvent e) {
428                edit();
429            }
430        });
431        return true;
432    }
433
434    @Override
435    protected void edit() {
436        makeIconEditorFrame(this, "GlobalVariable", true, null);
437        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.globalVariablePickModelInstance());
438        ActionListener addIconAction = (ActionEvent a) -> editGlobalVariable();
439        _iconEditor.complete(addIconAction, false, false, true);
440        _iconEditor.setSelection(getGlobalVariable());
441    }
442
443    void editGlobalVariable() {
444        setGlobalVariable(_iconEditor.getTableSelection().getDisplayName());
445        updateSize();
446        _iconEditorFrame.dispose();
447        _iconEditorFrame = null;
448        _iconEditor = null;
449        invalidate();
450    }
451
452    @Override
453    public void dispose() {
454        if (getGlobalVariable() != null) {
455            getGlobalVariable().removePropertyChangeListener(this);
456        }
457        namedGlobalVariable = null;
458        super.dispose();
459    }
460
461    @Override
462    public void doMouseClicked(JmriMouseEvent e) {
463        if (e.getClickCount() == 2) { // double click?
464            editGlobalVariableValue();
465        }
466    }
467
468    protected void editGlobalVariableValue() {
469
470        String reval = (String)JmriJOptionPane.showInputDialog(this,
471                                     Bundle.getMessage("EditCurrentGlobalVariableValue", namedGlobalVariable.getName()),
472                                     getGlobalVariable().getValue());
473
474        setValue(reval);
475        updateSize();
476    }
477
478    protected Object getValue() {
479        if (getGlobalVariable() == null) {
480            return null;
481        }
482        return getGlobalVariable().getValue();
483    }
484
485    protected void setValue(Object val) {
486        getGlobalVariable().setValue(val);
487    }
488
489    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GlobalVariableIcon.class);
490
491}