001package jmri.jmrit.display;
002
003import java.awt.Color;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import java.util.Collection;
007import java.util.HashMap;
008import java.util.Hashtable;
009import java.util.Map.Entry;
010
011import javax.annotation.Nonnull;
012import javax.swing.AbstractAction;
013import javax.swing.JCheckBoxMenuItem;
014import javax.swing.JComponent;
015import javax.swing.JMenu;
016import javax.swing.JMenuItem;
017import javax.swing.JPopupMenu;
018import javax.swing.Timer;
019
020import jmri.InstanceManager;
021import jmri.NamedBeanHandle;
022import jmri.Sensor;
023import jmri.NamedBean.DisplayOptions;
024import jmri.jmrit.catalog.NamedIcon;
025import jmri.jmrit.display.palette.TableItemPanel;
026import jmri.jmrit.picker.PickListModel;
027import jmri.util.swing.JmriColorChooser;
028import jmri.util.swing.JmriMouseEvent;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * An icon to display a status of a Sensor.
035 *
036 * @author Bob Jacobsen Copyright (C) 2001
037 * @author Pete Cressman Copyright (C) 2010, 2011
038 */
039public class SensorIcon extends PositionableIcon implements java.beans.PropertyChangeListener {
040
041    static final public int UNKOWN_FONT_COLOR = 0x03;
042    static final public int UNKOWN_BACKGROUND_COLOR = 0x04;
043    static final public int ACTIVE_FONT_COLOR = 0x05;
044    static final public int ACTIVE_BACKGROUND_COLOR = 0x06;
045    static final public int INACTIVE_FONT_COLOR = 0x07;
046    static final public int INACTIVE_BACKGROUND_COLOR = 0x08;
047    static final public int INCONSISTENT_FONT_COLOR = 0x0A;
048    static final public int INCONSISTENT_BACKGROUND_COLOR = 0x0B;
049
050    protected HashMap<String, Integer> _name2stateMap;       // name to state
051    protected HashMap<Integer, String> _state2nameMap;       // state to name
052
053    public SensorIcon(Editor editor) {
054        // super ctor call to make sure this is an icon label
055        this(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
056                "resources/icons/smallschematics/tracksegments/circuit-error.gif"), editor);
057    }
058
059    public SensorIcon(NamedIcon s, Editor editor) {
060        // super ctor call to make sure this is an icon label
061        super(s, editor);
062        setOpaque(false);
063        _control = true;
064        setPopupUtility(new SensorPopupUtil(this, this));
065    }
066
067    public SensorIcon(String s, Editor editor) {
068        super(s, editor);
069        _control = true;
070        originalText = s;
071        setPopupUtility(new SensorPopupUtil(this, this));
072        displayState(sensorState());
073    }
074
075    @Override
076    public Positionable deepClone() {
077        SensorIcon pos = new SensorIcon(_editor);
078        return finishClone(pos);
079    }
080
081    protected Positionable finishClone(SensorIcon pos) {
082        pos.setSensor(getNamedSensor().getName());
083        pos.makeIconMap();
084        pos._iconMap = cloneMap(_iconMap, pos);
085        pos.setMomentary(getMomentary());
086        pos.originalText = originalText;
087        pos.setText(getText());
088        pos.setIcon(null);
089        pos._namedIcon = null;
090        pos.activeText = activeText;
091        pos.inactiveText = inactiveText;
092        pos.inconsistentText = inconsistentText;
093        pos.unknownText = unknownText;
094        pos.textColorInconsistent = textColorInconsistent;
095        pos.textColorUnknown = textColorUnknown;
096        pos.textColorInActive = textColorInActive;
097        pos.textColorActive = textColorActive;
098        pos.backgroundColorInActive = backgroundColorInActive;
099        pos.backgroundColorActive = backgroundColorActive;
100        pos.backgroundColorUnknown = backgroundColorUnknown;
101        pos.backgroundColorInconsistent = backgroundColorInconsistent;
102        return super.finishClone(pos);
103    }
104
105    // the associated Sensor object
106    private NamedBeanHandle<Sensor> namedSensor;
107
108    /**
109     * Attached a named sensor to this display item
110     *
111     * @param pName System/user name to lookup the sensor object
112     */
113    public void setSensor(String pName) {
114        if (InstanceManager.getNullableDefault(jmri.SensorManager.class) != null) {
115            try {
116                Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(pName);
117                setSensor(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, sensor));
118            } catch (IllegalArgumentException ex) {
119                log.error("Sensor '{}' not available, icon won't see changes", pName);
120            }
121        } else {
122            log.error("No SensorManager for this protocol, icon won't see changes");
123        }
124    }
125
126    /**
127     * Attached a named sensor to this display item
128     *
129     * @param s the Sensor
130     */
131    public void setSensor(NamedBeanHandle<Sensor> s) {
132        if (namedSensor != null) {
133            getSensor().removePropertyChangeListener(this);
134        }
135
136        namedSensor = s;
137        if (namedSensor != null) {
138            if (_iconMap == null) {
139                makeIconMap();
140            }
141            getSensor().addPropertyChangeListener(this, s.getName(), "SensorIcon on Panel " + _editor.getName());
142            setName(namedSensor.getName());  // Swing name for e.g. tests
143        }
144        setAttributes();
145    }
146
147    private void setAttributes() {
148        if (isText()) {
149            if (namedSensor != null) {
150                if (getSensor().getUserName() != null) {
151                    String userName = getSensor().getUserName();
152                    if (activeText == null) {
153                        activeText = userName;
154                        //textColorActive=Color.red;
155                    }
156                    if (inactiveText == null) {
157                        inactiveText = userName;
158                        //textColorInActive=Color.yellow;
159                    }
160                    if (inconsistentText == null) {
161                        inconsistentText = userName;
162                        //textColorUnknown=Color.black;
163                    }
164                    if (unknownText == null) {
165                        unknownText = userName;
166                        //textColorInconsistent=Color.blue;
167                    }
168                }
169            }
170            if (activeText == null) {
171                activeText = "<" + Bundle.getMessage("SensorStateActive").toLowerCase() + ">"; // initiate state label string
172                // created from Bundle as lower case, enclosed in < >
173            }
174            if (inactiveText == null) {
175                inactiveText = "<" + Bundle.getMessage("SensorStateInactive").toLowerCase() + ">";
176            }
177            if (inconsistentText == null) {
178                inconsistentText = "<" + Bundle.getMessage("BeanStateInconsistent").toLowerCase() + ">";
179            }
180            if (unknownText == null) {
181                unknownText = "<" + Bundle.getMessage("BeanStateUnknown").toLowerCase() + ">";
182            }
183            if (textColorActive == null) {
184                textColorActive = Color.red;
185            }
186            if (textColorInActive == null) {
187                textColorInActive = Color.yellow;
188            }
189            if (textColorUnknown == null) {
190                textColorUnknown = Color.blue;
191            }
192            if (textColorInconsistent == null) {
193                textColorInconsistent = Color.black;
194            }
195        } else {
196            setOpaque(false);
197        }
198        displayState(sensorState());
199        if (log.isDebugEnabled()) { // avoid String building unless needed
200            log.debug("setSensor: namedSensor= {} isIcon= {}, isText= {}, activeText= {}",
201                    ((namedSensor == null) ? "null" : getNameString()), isIcon(), isText(), activeText);
202        }
203        repaint();
204    }
205
206    public Sensor getSensor() {
207        if (namedSensor == null) {
208            return null;
209        }
210        return namedSensor.getBean();
211    }
212
213    @Override
214    public jmri.NamedBean getNamedBean() {
215        return getSensor();
216    }
217
218    public NamedBeanHandle<Sensor> getNamedSensor() {
219        return namedSensor;
220    }
221
222    void makeIconMap() {
223        _iconMap = new HashMap<>();
224        _name2stateMap = new HashMap<>();
225        _name2stateMap.put("BeanStateUnknown", Sensor.UNKNOWN);
226        _name2stateMap.put("BeanStateInconsistent", Sensor.INCONSISTENT);
227        _name2stateMap.put("SensorStateActive", Sensor.ACTIVE);
228        _name2stateMap.put("SensorStateInactive", Sensor.INACTIVE);
229        _state2nameMap = new HashMap<>();
230        _state2nameMap.put(Sensor.UNKNOWN, "BeanStateUnknown");
231        _state2nameMap.put(Sensor.INCONSISTENT, "BeanStateInconsistent");
232        _state2nameMap.put(Sensor.ACTIVE, "SensorStateActive");
233        _state2nameMap.put(Sensor.INACTIVE, "SensorStateInactive");
234    }
235
236    @Override
237    public Collection<String> getStateNameCollection() {
238        return _state2nameMap.values();
239    }
240
241    /**
242     * Place icon by its bean state name key found in the properties file
243     * jmri.NamedBeanBundle.properties by its localized bean state name.
244     *
245     * @param name the icon state name
246     * @param icon the icon to place
247     */
248    public void setIcon(String name, NamedIcon icon) {
249        log.debug("setIcon for name \"{}\"", name);
250        if (_iconMap == null) {
251            makeIconMap();
252        }
253        _iconMap.put(name, icon);
254        displayState(sensorState());
255    }
256
257    /**
258     * Get icon by its localized bean state name.
259     *
260     * @param state the state to get the icon for
261     * @return the icon or null if state not found
262     */
263    @Override
264    public NamedIcon getIcon(String state) {
265        return _iconMap.get(state);
266    }
267
268    public NamedIcon getIcon(int state) {
269        return _iconMap.get(_state2nameMap.get(state));
270    }
271
272    @Override
273    public String getFamily() {
274        return _iconFamily;
275    }
276
277    @Override
278    public void setFamily(String family) {
279        _iconFamily = family;
280    }
281
282    /**
283     * Get current state of attached sensor
284     *
285     * @return A state variable from a Sensor, e.g. Sensor.ACTIVE
286     */
287    int sensorState() {
288        if (namedSensor != null) {
289            return getSensor().getKnownState();
290        } else {
291            return Sensor.UNKNOWN;
292        }
293    }
294
295    // update icon as state of turnout changes
296    @Override
297    public void propertyChange(java.beans.PropertyChangeEvent e) {
298        log.debug("property change: {}", e);
299        if (e.getPropertyName().equals("KnownState")) {
300            int now = ((Integer) e.getNewValue());
301            displayState(now);
302            _editor.repaint();
303        }
304    }
305
306    @Override
307    @Nonnull
308    public String getNameString() {
309        String name;
310        if (namedSensor == null) {
311            name = Bundle.getMessage("NotConnected");
312        } else {
313            name = getSensor().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
314        }
315        return name;
316    }
317
318    JCheckBoxMenuItem momentaryItem = new JCheckBoxMenuItem(Bundle.getMessage("Momentary"));
319
320    /**
321     * Pop-up just displays the sensor name.
322     *
323     * @param popup the menu to display
324     * @return always true
325     */
326    @Override
327    public boolean showPopUp(JPopupMenu popup) {
328        if (isEditable()) {
329            if (isIcon()) {
330                popup.add(new AbstractAction(Bundle.getMessage("ChangeToText")) {
331                    @Override
332                    public void actionPerformed(ActionEvent e) {
333                        changeLayoutSensorType();
334                    }
335                });
336            } else {
337                popup.add(new AbstractAction(Bundle.getMessage("ChangeToIcon")) {
338                    @Override
339                    public void actionPerformed(ActionEvent e) {
340                        changeLayoutSensorType();
341                    }
342                });
343            }
344
345            popup.add(momentaryItem);
346            momentaryItem.setSelected(getMomentary());
347            momentaryItem.addActionListener((java.awt.event.ActionEvent e) -> setMomentary(momentaryItem.isSelected()));
348        } else if (getPopupUtility() != null) {
349            getPopupUtility().setAdditionalViewPopUpMenu(popup);
350        }
351        return true;
352    }
353
354    //////// popup AbstractAction.actionPerformed method overrides ////////
355
356    @Override
357    public boolean setTextEditMenu(JPopupMenu popup) {
358        log.debug("setTextEditMenu isIcon={}, isText={}", isIcon(), isText());
359        if (isIcon()) {
360            popup.add(CoordinateEdit.getTextEditAction(this, "OverlayText"));
361        } else {
362            popup.add(new AbstractAction(Bundle.getMessage("SetSensorText")) {
363                @Override
364                public void actionPerformed(ActionEvent e) {
365                    String name = getNameString();
366                    sensorTextEdit(name);
367                }
368            });
369            if (isText() && !isIcon()) {
370                JMenu stateColor = new JMenu(Bundle.getMessage("StateColors"));
371                stateColor.add(stateMenu(Bundle.getMessage("BeanStateUnknown"), UNKOWN_FONT_COLOR)); //Unknown
372                stateColor.add(stateMenu(Bundle.getMessage("SensorStateActive"), ACTIVE_FONT_COLOR)); //Active
373                stateColor.add(stateMenu(Bundle.getMessage("SensorStateInactive"), INACTIVE_FONT_COLOR)); //Inactive
374                stateColor.add(stateMenu(Bundle.getMessage("BeanStateInconsistent"), INCONSISTENT_FONT_COLOR)); //Inconsistent
375                popup.add(stateColor);
376            }
377        }
378        return true;
379    }
380
381    public void sensorTextEdit(String name) {
382        log.debug("make text edit menu");
383
384        SensorTextEdit f = new SensorTextEdit();
385        f.addHelpMenu("package.jmri.jmrit.display.SensorTextEdit", true);
386        try {
387            f.initComponents(this, name);
388        } catch (Exception ex) {
389            log.error("Exception: {}", ex.toString());
390        }
391        f.setVisible(true);
392    }
393
394    /**
395     * Drive the current state of the display from the state of the sensor.
396     *
397     * @param state the sensor state
398     */
399    @Override
400    public void displayState(int state) {
401        if (getNamedSensor() == null) {
402            log.debug("Display state {}, disconnected", state);
403        } else if (isIcon()) {
404            NamedIcon icon = getIcon(state);
405            if (icon != null) {
406                super.setIcon(icon);
407            }
408        } else if (isText()) {
409            switch (state) {
410                case Sensor.UNKNOWN:
411                    super.setText(unknownText);
412                    getPopupUtility().setBackgroundColor(backgroundColorUnknown);
413                    getPopupUtility().setForeground(textColorUnknown);
414                    break;
415                case Sensor.ACTIVE:
416                    super.setText(activeText);
417                    getPopupUtility().setBackgroundColor(backgroundColorActive);
418                    getPopupUtility().setForeground(textColorActive);
419                    break;
420                case Sensor.INACTIVE:
421                    super.setText(inactiveText);
422                    getPopupUtility().setBackgroundColor(backgroundColorInActive);
423                    getPopupUtility().setForeground(textColorInActive);
424                    break;
425                default:
426                    super.setText(inconsistentText);
427                    getPopupUtility().setBackgroundColor(backgroundColorInconsistent);
428                    getPopupUtility().setForeground(textColorInconsistent);
429                    break;
430            }
431        }
432        int deg = getDegrees();
433        rotate(deg);
434        if (deg == 0) {
435            setOpaque(getPopupUtility().hasBackground());
436        }
437
438        updateSize();
439    }
440
441    TableItemPanel<Sensor> _itemPanel;
442
443    @Override
444    public boolean setEditItemMenu(JPopupMenu popup) {
445        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSensor"));
446        popup.add(new AbstractAction(txt) {
447            @Override
448            public void actionPerformed(ActionEvent e) {
449                editItem();
450            }
451        });
452        return true;
453    }
454
455    protected void editItem() {
456        _paletteFrame = makePaletteFrame(java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSensor")));
457        _itemPanel = new TableItemPanel<>(_paletteFrame, "Sensor", _iconFamily,
458                PickListModel.sensorPickModelInstance()); // NOI18N
459        ActionListener updateAction = (ActionEvent a) -> updateItem();
460        // duplicate _iconMap map with unscaled and unrotated icons
461        HashMap<String, NamedIcon> map = new HashMap<>();
462        for (Entry<String, NamedIcon> entry : _iconMap.entrySet()) {
463            NamedIcon oldIcon = entry.getValue();
464            NamedIcon newIcon = cloneIcon(oldIcon, this);
465            newIcon.rotate(0, this);
466            newIcon.scale(1.0, this);
467            newIcon.setRotation(4, this);
468            map.put(entry.getKey(), newIcon);
469        }
470        _itemPanel.init(updateAction, map);
471        _itemPanel.setSelection(getSensor());
472        initPaletteFrame(_paletteFrame, _itemPanel);
473    }
474
475    void updateItem() {
476        HashMap<String, NamedIcon> oldMap = cloneMap(_iconMap, this);
477        setSensor(_itemPanel.getTableSelection().getSystemName());
478        _iconFamily = _itemPanel.getFamilyName();
479        HashMap<String, NamedIcon> iconMap = _itemPanel.getIconMap();
480        if (iconMap != null) {
481            for (Entry<String, NamedIcon> entry : iconMap.entrySet()) {
482                if (log.isDebugEnabled()) {
483                    log.debug("key= {}", entry.getKey());
484                }
485                NamedIcon newIcon = entry.getValue();
486                NamedIcon oldIcon = oldMap.get(entry.getKey());
487                newIcon.setLoad(oldIcon.getDegrees(), oldIcon.getScale(), this);
488                newIcon.setRotation(oldIcon.getRotation(), this);
489                setIcon(entry.getKey(), newIcon);
490            }
491        }   // otherwise retain current map
492        finishItemUpdate(_paletteFrame, _itemPanel);
493    }
494
495    @Override
496    public boolean setEditIconMenu(JPopupMenu popup) {
497        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSensor"));
498        popup.add(new AbstractAction(txt) {
499            @Override
500            public void actionPerformed(ActionEvent e) {
501                edit();
502            }
503        });
504        return true;
505    }
506
507    @Override
508    protected void edit() {
509        makeIconEditorFrame(this, "Sensor", true, null);
510        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.sensorPickModelInstance());
511        int i = 0;
512        for (Entry<String, NamedIcon> entry : _iconMap.entrySet()) {
513            _iconEditor.setIcon(i++, entry.getKey(), entry.getValue());
514        }
515        _iconEditor.makeIconPanel(false);
516
517        // set default icons, then override with this turnout's icons
518        ActionListener addIconAction = (ActionEvent a) -> updateSensor();
519        _iconEditor.complete(addIconAction, true, true, true);
520        _iconEditor.setSelection(getSensor());
521    }
522
523    void updateSensor() {
524        HashMap<String, NamedIcon> oldMap = cloneMap(_iconMap, this);
525        setSensor(_iconEditor.getTableSelection().getDisplayName());
526        Hashtable<String, NamedIcon> iconMap = _iconEditor.getIconMap();
527
528        for (Entry<String, NamedIcon> entry : iconMap.entrySet()) {
529            log.debug("key= {}", entry.getKey());
530            NamedIcon newIcon = entry.getValue();
531            NamedIcon oldIcon = oldMap.get(entry.getKey());
532            newIcon.setLoad(oldIcon.getDegrees(), oldIcon.getScale(), this);
533            newIcon.setRotation(oldIcon.getRotation(), this);
534            setIcon(entry.getKey(), newIcon);
535        }
536        _iconEditorFrame.dispose();
537        _iconEditorFrame = null;
538        _iconEditor = null;
539        invalidate();
540    }
541
542    // Original text is used when changing between icon and text, this allows for a undo when reverting back.
543    String originalText;
544
545    public void setOriginalText(String s) {
546        originalText = s;
547        displayState(sensorState());
548    }
549
550    public String getOriginalText() {
551        return originalText;
552    }
553
554    @Override
555    public void setText(String s) {
556        setOpaque(false);
557        if (super._rotateText && !_icon) {
558            return;
559        }
560        _text = (s != null && s.length() > 0);
561        super.setText(s);
562        updateSize();
563    }
564
565    boolean momentary = false;
566
567    public boolean getMomentary() {
568        return momentary;
569    }
570
571    public void setMomentary(boolean m) {
572        momentary = m;
573    }
574
575    public boolean buttonLive() {
576        if (namedSensor == null) {  // no sensor connected for this protocol
577            log.error("No sensor connection, can't process click");
578            return false;
579        }
580        return _editor.getFlag(Editor.OPTION_CONTROLS, isControlling());
581    }
582
583    @Override
584    public void doMousePressed(JmriMouseEvent e) {
585        log.debug("doMousePressed buttonLive={}, getMomentary={}", buttonLive(), getMomentary());
586        if (getMomentary() && buttonLive() && !e.isMetaDown() && !e.isAltDown()) {
587            // this is a momentary button press
588            try {
589                getSensor().setKnownState(jmri.Sensor.ACTIVE);
590            } catch (jmri.JmriException reason) {
591                log.warn("Exception setting momentary sensor", reason);
592            }
593        }
594        super.doMousePressed(e);
595    }
596
597    @Override
598    public void doMouseReleased(JmriMouseEvent e) {
599        if (getMomentary() && buttonLive() && !e.isMetaDown() && !e.isAltDown()) {
600            // this is a momentary button release
601            try {
602                getSensor().setKnownState(jmri.Sensor.INACTIVE);
603            } catch (jmri.JmriException reason) {
604                log.warn("Exception setting momentary sensor", reason);
605            }
606        }
607        super.doMouseReleased(e);
608    }
609
610    @Override
611    public void doMouseClicked(JmriMouseEvent e) {
612        if (buttonLive() && !getMomentary()) {
613            // this button responds to clicks
614            if (!e.isMetaDown() && !e.isAltDown()) {
615                try {
616                    if (getSensor().getKnownState() == jmri.Sensor.INACTIVE) {
617                        getSensor().setKnownState(jmri.Sensor.ACTIVE);
618                    } else {
619                        getSensor().setKnownState(jmri.Sensor.INACTIVE);
620                    }
621                } catch (jmri.JmriException reason) {
622                    log.warn("Exception flipping sensor", reason);
623                }
624            }
625        }
626        super.doMouseClicked(e);
627    }
628
629    @Override
630    public void dispose() {
631        if (namedSensor != null) {
632            getSensor().removePropertyChangeListener(this);
633        }
634        namedSensor = null;
635        _iconMap = null;
636        _name2stateMap = null;
637        _state2nameMap = null;
638
639        super.dispose();
640    }
641
642    protected HashMap<Integer, NamedIcon> cloneMap(HashMap<Integer, NamedIcon> map,
643            SensorIcon pos) {
644        HashMap<Integer, NamedIcon> clone = new HashMap<>();
645        if (map != null) {
646            for (Entry<Integer, NamedIcon> entry : map.entrySet()) {
647                clone.put(entry.getKey(), cloneIcon(entry.getValue(), pos));
648                if (pos != null) {
649                    pos.setIcon(pos._state2nameMap.get(entry.getKey()), _iconMap.get(entry.getKey().toString()));
650                }
651            }
652        }
653        return clone;
654    }
655    // The code below here is from the layoutsensoricon.
656
657    Color textColorActive = Color.red;
658
659    public void setTextActive(Color color) {
660        textColorActive = color;
661        displayState(sensorState());
662        JmriColorChooser.addRecentColor(color);
663    }
664
665    public Color getTextActive() {
666        return textColorActive;
667    }
668
669    Color textColorInActive = Color.yellow;
670
671    public void setTextInActive(Color color) {
672        textColorInActive = color;
673        displayState(sensorState());
674        JmriColorChooser.addRecentColor(color);
675    }
676
677    public Color getTextInActive() {
678        return textColorInActive;
679    }
680
681    Color textColorUnknown = Color.blue;
682
683    public void setTextUnknown(Color color) {
684        textColorUnknown = color;
685        displayState(sensorState());
686        JmriColorChooser.addRecentColor(color);
687    }
688
689    public Color getTextUnknown() {
690        return textColorUnknown;
691    }
692
693    Color textColorInconsistent = Color.black;
694
695    public void setTextInconsistent(Color color) {
696        textColorInconsistent = color;
697        displayState(sensorState());
698        JmriColorChooser.addRecentColor(color);
699    }
700
701    public Color getTextInconsistent() {
702        return textColorInconsistent;
703    }
704
705    Color backgroundColorActive = null;
706
707    public void setBackgroundActive(Color color) {
708        backgroundColorActive = color;
709        displayState(sensorState());
710        JmriColorChooser.addRecentColor(color);
711    }
712
713    public Color getBackgroundActive() {
714        return backgroundColorActive;
715    }
716
717    Color backgroundColorInActive = null;
718
719    public void setBackgroundInActive(Color color) {
720        backgroundColorInActive = color;
721        displayState(sensorState());
722        JmriColorChooser.addRecentColor(color);
723    }
724
725    public Color getBackgroundInActive() {
726        return backgroundColorInActive;
727    }
728
729    Color backgroundColorUnknown = null;
730
731    public void setBackgroundUnknown(Color color) {
732        backgroundColorUnknown = color;
733        displayState(sensorState());
734        JmriColorChooser.addRecentColor(color);
735    }
736
737    public Color getBackgroundUnknown() {
738        return backgroundColorUnknown;
739    }
740
741    Color backgroundColorInconsistent = null;
742
743    public void setBackgroundInconsistent(Color color) {
744        backgroundColorInconsistent = color;
745        displayState(sensorState());
746        JmriColorChooser.addRecentColor(color);
747    }
748
749    public Color getBackgroundInconsistent() {
750        return backgroundColorInconsistent;
751    }
752
753    private String activeText;
754    private String inactiveText;
755    private String inconsistentText;
756    private String unknownText;
757
758    public String getActiveText() {
759        return activeText;
760    }
761
762    public void setActiveText(String i) {
763        activeText = i;
764        displayState(sensorState());
765    }
766
767    public String getInactiveText() {
768        return inactiveText;
769    }
770
771    public void setInactiveText(String i) {
772        inactiveText = i;
773        displayState(sensorState());
774    }
775
776    public String getInconsistentText() {
777        return inconsistentText;
778    }
779
780    public void setInconsistentText(String i) {
781        inconsistentText = i;
782        displayState(sensorState());
783    }
784
785    public String getUnknownText() {
786        return unknownText;
787    }
788
789    public void setUnknownText(String i) {
790        unknownText = i;
791        displayState(sensorState());
792    }
793
794    JMenu stateMenu(final String name, int state) {
795        JMenu menu = new JMenu(name);
796        JMenuItem colorMenu = new JMenuItem(Bundle.getMessage("FontColor"));
797        colorMenu.addActionListener((ActionEvent event) -> {
798            Color desiredColor = JmriColorChooser.showDialog(this,
799                                 Bundle.getMessage("FontColor"),
800                                 getColor(state));
801            if (desiredColor!=null ) {
802                 setColor(desiredColor,state);
803            }
804        });
805        menu.add(colorMenu);
806        colorMenu = new JMenuItem(Bundle.getMessage("FontBackgroundColor"));
807        colorMenu.addActionListener((ActionEvent event) -> {
808            Color desiredColor = JmriColorChooser.showDialog(this,
809                                 Bundle.getMessage("FontBackgroundColor"),
810                                 getColor(state+1));
811            if (desiredColor!=null ) {
812                 setColor(desiredColor,state+1);
813            }
814        });
815        menu.add(colorMenu);
816        return menu;
817    }
818
819    private void setColor(Color desiredColor, int state) {
820        PositionablePopupUtil pop = getPopupUtility();
821        if (pop instanceof SensorPopupUtil) {
822            SensorPopupUtil util = (SensorPopupUtil) pop;
823            switch (state) {
824                case PositionablePopupUtil.FONT_COLOR:
825                    util.setForeground(desiredColor);
826                    break;
827                case PositionablePopupUtil.BACKGROUND_COLOR:
828                    util.setBackgroundColor(desiredColor);
829                    break;
830                case PositionablePopupUtil.BORDER_COLOR:
831                    util.setBorderColor(desiredColor);
832                    break;
833                case UNKOWN_FONT_COLOR:
834                    setTextUnknown(desiredColor);
835                    break;
836                case UNKOWN_BACKGROUND_COLOR:
837                    util.setHasBackground(desiredColor != null);
838                    setBackgroundUnknown(desiredColor);
839                    break;
840                case ACTIVE_FONT_COLOR:
841                    setTextActive(desiredColor);
842                    break;
843                case ACTIVE_BACKGROUND_COLOR:
844                    util.setHasBackground(desiredColor != null);
845                    setBackgroundActive(desiredColor);
846                    break;
847                case INACTIVE_FONT_COLOR:
848                    setTextInActive(desiredColor);
849                    break;
850                case INACTIVE_BACKGROUND_COLOR:
851                    util.setHasBackground(desiredColor != null);
852                    setBackgroundInActive(desiredColor);
853                    break;
854                case INCONSISTENT_FONT_COLOR:
855                    setTextInconsistent(desiredColor);
856                    break;
857                case INCONSISTENT_BACKGROUND_COLOR:
858                    util.setHasBackground(desiredColor != null);
859                    setBackgroundInconsistent(desiredColor);
860                    break;
861                default:
862                    break;
863            }
864        }
865    }
866
867    private Color getColor(int state){
868        PositionablePopupUtil pop = getPopupUtility();
869        if (pop instanceof SensorPopupUtil) {
870            SensorPopupUtil util = (SensorPopupUtil) pop;
871            switch (state) {
872                case PositionablePopupUtil.FONT_COLOR:
873                    return util.getForeground();
874                case PositionablePopupUtil.BACKGROUND_COLOR:
875                    return util.getBackground();
876                case PositionablePopupUtil.BORDER_COLOR:
877                    return util.getBorderColor();
878                case UNKOWN_FONT_COLOR:
879                    return getTextUnknown();
880                case UNKOWN_BACKGROUND_COLOR:
881                    return getBackgroundUnknown();
882                case ACTIVE_FONT_COLOR:
883                    return getTextActive();
884                case ACTIVE_BACKGROUND_COLOR:
885                    return getBackgroundActive();
886                case INACTIVE_FONT_COLOR:
887                    return getTextInActive();
888                case INACTIVE_BACKGROUND_COLOR:
889                    return getBackgroundInActive();
890                case INCONSISTENT_FONT_COLOR:
891                    return getTextInconsistent();
892                case INCONSISTENT_BACKGROUND_COLOR:
893                    return getBackgroundInconsistent();
894                default:
895                    return null;
896            }
897        }
898        return null;
899    }
900
901    void changeLayoutSensorType() {
902//        NamedBeanHandle <Sensor> handle = getNamedSensor();
903        if (isIcon()) {
904            _icon = false;
905            _text = true;
906            setIcon(null);
907//            setOriginalText(getUnRotatedText());
908            setSuperText(null);
909            setOpaque(true);
910        } else if (isText()) {
911            _icon = true;
912            _text = (originalText != null && originalText.length() > 0);
913            setUnRotatedText(getOriginalText());
914            setOpaque(false);
915        }
916        _namedIcon = null;
917        setAttributes();
918        displayState(sensorState());
919//        setSensor(handle);
920        int deg = getDegrees();
921        rotate(deg);
922        if (deg != 0 && _text && !_icon) {
923            setSuperText(null);
924        }
925    }
926
927    private int flashStateOn = -1;
928    private int flashStateOff = -1;
929    private boolean flashon = false;
930    private ActionListener taskPerformer;
931    private Timer flashTimer;
932
933    synchronized public void flashSensor(int tps, int state1, int state2) {
934        if ((flashTimer != null) && flashTimer.isRunning()) {
935            return;
936        }
937        //Set the maximum number of state changes to 10 per second
938        if (tps > 10) {
939            tps = 10;
940        } else if (tps <= 0) {
941            return;
942        }
943        if ((_state2nameMap.get(state1) == null) || _state2nameMap.get(state2) == null) {
944            log.error("one or other of the states passed for flash is null");
945            return;
946        } else if (state1 == state2) {
947            log.debug("Both states to flash between are the same, therefore no flashing will occur");
948            return;
949        }
950        int interval = (1000 / tps) / 2;
951        flashStateOn = state1;
952        flashStateOff = state2;
953        if (taskPerformer == null) {
954            taskPerformer = (ActionEvent evt) -> {
955                if (flashon) {
956                    flashon = false;
957                    displayState(flashStateOn);
958                } else {
959                    flashon = true;
960                    displayState(flashStateOff);
961                }
962            };
963        }
964        flashTimer = new Timer(interval, taskPerformer);
965        flashTimer.start();
966    }
967
968    synchronized public void stopFlash() {
969        if (flashTimer != null) {
970            flashTimer.stop();
971        }
972        displayState(sensorState());
973    }
974
975    class SensorPopupUtil extends PositionablePopupUtil {
976
977        SensorPopupUtil(Positionable parent, javax.swing.JComponent textComp) {
978            super(parent, textComp);
979        }
980
981        @Override
982        public SensorPopupUtil clone(Positionable parent, JComponent textComp) {
983            SensorPopupUtil util = new SensorPopupUtil(parent, textComp);
984            util.setJustification(getJustification());
985            util.setHorizontalAlignment(getJustification());
986            util.setFixedWidth(getFixedWidth());
987            util.setFixedHeight(getFixedHeight());
988            util.setMargin(getMargin());
989            util.setBorderSize(getBorderSize());
990            util.setBorderColor(getBorderColor());
991            util.setFont(util.getFont().deriveFont(getFontStyle()));
992            util.setFontSize(getFontSize());
993            util.setOrientation(getOrientation());
994            util.setBackgroundColor(getBackground());
995            util.setForeground(getForeground());
996            util.setHasBackground(hasBackground());     // must do this AFTER setBackgroundColor
997            return util;
998        }
999
1000        @Override
1001        public void setTextJustificationMenu(JPopupMenu popup) {
1002            if (isText()) {
1003                super.setTextJustificationMenu(popup);
1004            }
1005        }
1006
1007        @Override
1008        public void setTextOrientationMenu(JPopupMenu popup) {
1009            if (isText()) {
1010                super.setTextOrientationMenu(popup);
1011            }
1012        }
1013
1014        @Override
1015        public void setFixedTextMenu(JPopupMenu popup) {
1016            if (isText()) {
1017                super.setFixedTextMenu(popup);
1018            }
1019        }
1020
1021        @Override
1022        public void setTextMarginMenu(JPopupMenu popup) {
1023            if (isText()) {
1024                super.setTextMarginMenu(popup);
1025            }
1026        }
1027
1028        @Override
1029        public void setTextBorderMenu(JPopupMenu popup) {
1030            if (isText()) {
1031                super.setTextBorderMenu(popup);
1032            }
1033        }
1034
1035        @Override
1036        public void setTextFontMenu(JPopupMenu popup) {
1037            if (isText()) {
1038                super.setTextFontMenu(popup);
1039            }
1040        }
1041
1042        @Override
1043        public void setBackgroundMenu(JPopupMenu popup) {
1044            if (isIcon()) {
1045                super.setBackgroundMenu(popup);
1046            }
1047        }
1048    }
1049
1050    private final static Logger log = LoggerFactory.getLogger(SensorIcon.class);
1051
1052}