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