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 getTypeString() {
309        return Bundle.getMessage("PositionableType_SensorIcon");
310    }
311
312    @Override
313    @Nonnull
314    public String getNameString() {
315        String name;
316        if (namedSensor == null) {
317            name = Bundle.getMessage("NotConnected");
318        } else {
319            name = getSensor().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
320        }
321        return name;
322    }
323
324    JCheckBoxMenuItem momentaryItem = new JCheckBoxMenuItem(Bundle.getMessage("Momentary"));
325
326    /**
327     * Pop-up just displays the sensor name.
328     *
329     * @param popup the menu to display
330     * @return always true
331     */
332    @Override
333    public boolean showPopUp(JPopupMenu popup) {
334        if (isEditable()) {
335            if (isIcon()) {
336                popup.add(new AbstractAction(Bundle.getMessage("ChangeToText")) {
337                    @Override
338                    public void actionPerformed(ActionEvent e) {
339                        changeLayoutSensorType();
340                    }
341                });
342            } else {
343                popup.add(new AbstractAction(Bundle.getMessage("ChangeToIcon")) {
344                    @Override
345                    public void actionPerformed(ActionEvent e) {
346                        changeLayoutSensorType();
347                    }
348                });
349            }
350
351            popup.add(momentaryItem);
352            momentaryItem.setSelected(getMomentary());
353            momentaryItem.addActionListener((java.awt.event.ActionEvent e) -> setMomentary(momentaryItem.isSelected()));
354        } else if (getPopupUtility() != null) {
355            getPopupUtility().setAdditionalViewPopUpMenu(popup);
356        }
357        return true;
358    }
359
360    //////// popup AbstractAction.actionPerformed method overrides ////////
361
362    @Override
363    public boolean setTextEditMenu(JPopupMenu popup) {
364        log.debug("setTextEditMenu isIcon={}, isText={}", isIcon(), isText());
365        if (isIcon()) {
366            popup.add(CoordinateEdit.getTextEditAction(this, "OverlayText"));
367        } else {
368            popup.add(new AbstractAction(Bundle.getMessage("SetSensorText")) {
369                @Override
370                public void actionPerformed(ActionEvent e) {
371                    String name = getNameString();
372                    sensorTextEdit(name);
373                }
374            });
375            if (isText() && !isIcon()) {
376                JMenu stateColor = new JMenu(Bundle.getMessage("StateColors"));
377                stateColor.add(stateMenu(Bundle.getMessage("BeanStateUnknown"), UNKOWN_FONT_COLOR)); //Unknown
378                stateColor.add(stateMenu(Bundle.getMessage("SensorStateActive"), ACTIVE_FONT_COLOR)); //Active
379                stateColor.add(stateMenu(Bundle.getMessage("SensorStateInactive"), INACTIVE_FONT_COLOR)); //Inactive
380                stateColor.add(stateMenu(Bundle.getMessage("BeanStateInconsistent"), INCONSISTENT_FONT_COLOR)); //Inconsistent
381                popup.add(stateColor);
382            }
383        }
384        return true;
385    }
386
387    public void sensorTextEdit(String name) {
388        log.debug("make text edit menu");
389
390        SensorTextEdit f = new SensorTextEdit();
391        f.addHelpMenu("package.jmri.jmrit.display.SensorTextEdit", true);
392        try {
393            f.initComponents(this, name);
394        } catch (Exception ex) {
395            log.error("Exception: {}", ex.toString());
396        }
397        f.setVisible(true);
398    }
399
400    /**
401     * Drive the current state of the display from the state of the sensor.
402     *
403     * @param state the sensor state
404     */
405    @Override
406    public void displayState(int state) {
407        if (getNamedSensor() == null) {
408            log.debug("Display state {}, disconnected", state);
409        } else if (isIcon()) {
410            NamedIcon icon = getIcon(state);
411            if (icon != null) {
412                super.setIcon(icon);
413            }
414        } else if (isText()) {
415            switch (state) {
416                case Sensor.UNKNOWN:
417                    super.setText(unknownText);
418                    getPopupUtility().setBackgroundColor(backgroundColorUnknown);
419                    getPopupUtility().setForeground(textColorUnknown);
420                    break;
421                case Sensor.ACTIVE:
422                    super.setText(activeText);
423                    getPopupUtility().setBackgroundColor(backgroundColorActive);
424                    getPopupUtility().setForeground(textColorActive);
425                    break;
426                case Sensor.INACTIVE:
427                    super.setText(inactiveText);
428                    getPopupUtility().setBackgroundColor(backgroundColorInActive);
429                    getPopupUtility().setForeground(textColorInActive);
430                    break;
431                default:
432                    super.setText(inconsistentText);
433                    getPopupUtility().setBackgroundColor(backgroundColorInconsistent);
434                    getPopupUtility().setForeground(textColorInconsistent);
435                    break;
436            }
437        }
438        int deg = getDegrees();
439        rotate(deg);
440        if (deg == 0) {
441            setOpaque(getPopupUtility().hasBackground());
442        }
443
444        updateSize();
445    }
446
447    TableItemPanel<Sensor> _itemPanel;
448
449    @Override
450    public boolean setEditItemMenu(JPopupMenu popup) {
451        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSensor"));
452        popup.add(new AbstractAction(txt) {
453            @Override
454            public void actionPerformed(ActionEvent e) {
455                editItem();
456            }
457        });
458        return true;
459    }
460
461    protected void editItem() {
462        _paletteFrame = makePaletteFrame(java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSensor")));
463        _itemPanel = new TableItemPanel<>(_paletteFrame, "Sensor", _iconFamily,
464                PickListModel.sensorPickModelInstance()); // NOI18N
465        ActionListener updateAction = (ActionEvent a) -> updateItem();
466        // duplicate _iconMap map with unscaled and unrotated icons
467        HashMap<String, NamedIcon> map = new HashMap<>();
468        for (Entry<String, NamedIcon> entry : _iconMap.entrySet()) {
469            NamedIcon oldIcon = entry.getValue();
470            NamedIcon newIcon = cloneIcon(oldIcon, this);
471            newIcon.rotate(0, this);
472            newIcon.scale(1.0, this);
473            newIcon.setRotation(4, this);
474            map.put(entry.getKey(), newIcon);
475        }
476        _itemPanel.init(updateAction, map);
477        _itemPanel.setSelection(getSensor());
478        initPaletteFrame(_paletteFrame, _itemPanel);
479    }
480
481    void updateItem() {
482        HashMap<String, NamedIcon> oldMap = cloneMap(_iconMap, this);
483        setSensor(_itemPanel.getTableSelection().getSystemName());
484        _iconFamily = _itemPanel.getFamilyName();
485        HashMap<String, NamedIcon> iconMap = _itemPanel.getIconMap();
486        if (iconMap != null) {
487            for (Entry<String, NamedIcon> entry : iconMap.entrySet()) {
488                if (log.isDebugEnabled()) {
489                    log.debug("key= {}", entry.getKey());
490                }
491                NamedIcon newIcon = entry.getValue();
492                NamedIcon oldIcon = oldMap.get(entry.getKey());
493                newIcon.setLoad(oldIcon.getDegrees(), oldIcon.getScale(), this);
494                newIcon.setRotation(oldIcon.getRotation(), this);
495                setIcon(entry.getKey(), newIcon);
496            }
497        }   // otherwise retain current map
498        finishItemUpdate(_paletteFrame, _itemPanel);
499    }
500
501    @Override
502    public boolean setEditIconMenu(JPopupMenu popup) {
503        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSensor"));
504        popup.add(new AbstractAction(txt) {
505            @Override
506            public void actionPerformed(ActionEvent e) {
507                edit();
508            }
509        });
510        return true;
511    }
512
513    @Override
514    protected void edit() {
515        makeIconEditorFrame(this, "Sensor", true, null);
516        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.sensorPickModelInstance());
517        int i = 0;
518        for (Entry<String, NamedIcon> entry : _iconMap.entrySet()) {
519            _iconEditor.setIcon(i++, entry.getKey(), entry.getValue());
520        }
521        _iconEditor.makeIconPanel(false);
522
523        // set default icons, then override with this turnout's icons
524        ActionListener addIconAction = (ActionEvent a) -> updateSensor();
525        _iconEditor.complete(addIconAction, true, true, true);
526        _iconEditor.setSelection(getSensor());
527    }
528
529    void updateSensor() {
530        HashMap<String, NamedIcon> oldMap = cloneMap(_iconMap, this);
531        setSensor(_iconEditor.getTableSelection().getDisplayName());
532        Hashtable<String, NamedIcon> iconMap = _iconEditor.getIconMap();
533
534        for (Entry<String, NamedIcon> entry : iconMap.entrySet()) {
535            log.debug("key= {}", entry.getKey());
536            NamedIcon newIcon = entry.getValue();
537            NamedIcon oldIcon = oldMap.get(entry.getKey());
538            newIcon.setLoad(oldIcon.getDegrees(), oldIcon.getScale(), this);
539            newIcon.setRotation(oldIcon.getRotation(), this);
540            setIcon(entry.getKey(), newIcon);
541        }
542        _iconEditorFrame.dispose();
543        _iconEditorFrame = null;
544        _iconEditor = null;
545        invalidate();
546    }
547
548    // Original text is used when changing between icon and text, this allows for a undo when reverting back.
549    String originalText;
550
551    public void setOriginalText(String s) {
552        originalText = s;
553        displayState(sensorState());
554    }
555
556    public String getOriginalText() {
557        return originalText;
558    }
559
560    @Override
561    public void setText(String s) {
562        setOpaque(false);
563        if (super._rotateText && !_icon) {
564            return;
565        }
566        _text = (s != null && s.length() > 0);
567        super.setText(s);
568        updateSize();
569    }
570
571    boolean momentary = false;
572
573    public boolean getMomentary() {
574        return momentary;
575    }
576
577    public void setMomentary(boolean m) {
578        momentary = m;
579    }
580
581    public boolean buttonLive() {
582        if (namedSensor == null) {  // no sensor connected for this protocol
583            log.error("No sensor connection, can't process click");
584            return false;
585        }
586        return _editor.getFlag(Editor.OPTION_CONTROLS, isControlling());
587    }
588
589    @Override
590    public void doMousePressed(JmriMouseEvent e) {
591        log.debug("doMousePressed buttonLive={}, getMomentary={}", buttonLive(), getMomentary());
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        if (getMomentary() && buttonLive() && !e.isMetaDown() && !e.isAltDown()) {
606            // this is a momentary button release
607            try {
608                getSensor().setKnownState(jmri.Sensor.INACTIVE);
609            } catch (jmri.JmriException reason) {
610                log.warn("Exception setting momentary sensor", reason);
611            }
612        }
613        super.doMouseReleased(e);
614    }
615
616    @Override
617    public void doMouseClicked(JmriMouseEvent e) {
618        if (buttonLive() && !getMomentary()) {
619            // this button responds to clicks
620            if (!e.isMetaDown() && !e.isAltDown()) {
621                try {
622                    if (getSensor().getKnownState() == jmri.Sensor.INACTIVE) {
623                        getSensor().setKnownState(jmri.Sensor.ACTIVE);
624                    } else {
625                        getSensor().setKnownState(jmri.Sensor.INACTIVE);
626                    }
627                } catch (jmri.JmriException reason) {
628                    log.warn("Exception flipping sensor", reason);
629                }
630            }
631        }
632        super.doMouseClicked(e);
633    }
634
635    @Override
636    public void dispose() {
637        if (namedSensor != null) {
638            getSensor().removePropertyChangeListener(this);
639        }
640        namedSensor = null;
641        _iconMap = null;
642        _name2stateMap = null;
643        _state2nameMap = null;
644
645        super.dispose();
646    }
647
648    protected HashMap<Integer, NamedIcon> cloneMap(HashMap<Integer, NamedIcon> map,
649            SensorIcon pos) {
650        HashMap<Integer, NamedIcon> clone = new HashMap<>();
651        if (map != null) {
652            for (Entry<Integer, NamedIcon> entry : map.entrySet()) {
653                clone.put(entry.getKey(), cloneIcon(entry.getValue(), pos));
654                if (pos != null) {
655                    pos.setIcon(pos._state2nameMap.get(entry.getKey()), _iconMap.get(entry.getKey().toString()));
656                }
657            }
658        }
659        return clone;
660    }
661    // The code below here is from the layoutsensoricon.
662
663    Color textColorActive = Color.red;
664
665    public void setTextActive(Color color) {
666        textColorActive = color;
667        displayState(sensorState());
668        JmriColorChooser.addRecentColor(color);
669    }
670
671    public Color getTextActive() {
672        return textColorActive;
673    }
674
675    Color textColorInActive = Color.yellow;
676
677    public void setTextInActive(Color color) {
678        textColorInActive = color;
679        displayState(sensorState());
680        JmriColorChooser.addRecentColor(color);
681    }
682
683    public Color getTextInActive() {
684        return textColorInActive;
685    }
686
687    Color textColorUnknown = Color.blue;
688
689    public void setTextUnknown(Color color) {
690        textColorUnknown = color;
691        displayState(sensorState());
692        JmriColorChooser.addRecentColor(color);
693    }
694
695    public Color getTextUnknown() {
696        return textColorUnknown;
697    }
698
699    Color textColorInconsistent = Color.black;
700
701    public void setTextInconsistent(Color color) {
702        textColorInconsistent = color;
703        displayState(sensorState());
704        JmriColorChooser.addRecentColor(color);
705    }
706
707    public Color getTextInconsistent() {
708        return textColorInconsistent;
709    }
710
711    Color backgroundColorActive = null;
712
713    public void setBackgroundActive(Color color) {
714        backgroundColorActive = color;
715        displayState(sensorState());
716        JmriColorChooser.addRecentColor(color);
717    }
718
719    public Color getBackgroundActive() {
720        return backgroundColorActive;
721    }
722
723    Color backgroundColorInActive = null;
724
725    public void setBackgroundInActive(Color color) {
726        backgroundColorInActive = color;
727        displayState(sensorState());
728        JmriColorChooser.addRecentColor(color);
729    }
730
731    public Color getBackgroundInActive() {
732        return backgroundColorInActive;
733    }
734
735    Color backgroundColorUnknown = null;
736
737    public void setBackgroundUnknown(Color color) {
738        backgroundColorUnknown = color;
739        displayState(sensorState());
740        JmriColorChooser.addRecentColor(color);
741    }
742
743    public Color getBackgroundUnknown() {
744        return backgroundColorUnknown;
745    }
746
747    Color backgroundColorInconsistent = null;
748
749    public void setBackgroundInconsistent(Color color) {
750        backgroundColorInconsistent = color;
751        displayState(sensorState());
752        JmriColorChooser.addRecentColor(color);
753    }
754
755    public Color getBackgroundInconsistent() {
756        return backgroundColorInconsistent;
757    }
758
759    private String activeText;
760    private String inactiveText;
761    private String inconsistentText;
762    private String unknownText;
763
764    public String getActiveText() {
765        return activeText;
766    }
767
768    public void setActiveText(String i) {
769        activeText = i;
770        displayState(sensorState());
771    }
772
773    public String getInactiveText() {
774        return inactiveText;
775    }
776
777    public void setInactiveText(String i) {
778        inactiveText = i;
779        displayState(sensorState());
780    }
781
782    public String getInconsistentText() {
783        return inconsistentText;
784    }
785
786    public void setInconsistentText(String i) {
787        inconsistentText = i;
788        displayState(sensorState());
789    }
790
791    public String getUnknownText() {
792        return unknownText;
793    }
794
795    public void setUnknownText(String i) {
796        unknownText = i;
797        displayState(sensorState());
798    }
799
800    JMenu stateMenu(final String name, int state) {
801        JMenu menu = new JMenu(name);
802        JMenuItem colorMenu = new JMenuItem(Bundle.getMessage("FontColor"));
803        colorMenu.addActionListener((ActionEvent event) -> {
804            Color desiredColor = JmriColorChooser.showDialog(this,
805                                 Bundle.getMessage("FontColor"),
806                                 getColor(state));
807            if (desiredColor!=null ) {
808                 setColor(desiredColor,state);
809            }
810        });
811        menu.add(colorMenu);
812        colorMenu = new JMenuItem(Bundle.getMessage("FontBackgroundColor"));
813        colorMenu.addActionListener((ActionEvent event) -> {
814            Color desiredColor = JmriColorChooser.showDialog(this,
815                                 Bundle.getMessage("FontBackgroundColor"),
816                                 getColor(state+1));
817            if (desiredColor!=null ) {
818                 setColor(desiredColor,state+1);
819            }
820        });
821        menu.add(colorMenu);
822        return menu;
823    }
824
825    private void setColor(Color desiredColor, int state) {
826        PositionablePopupUtil pop = getPopupUtility();
827        if (pop instanceof SensorPopupUtil) {
828            SensorPopupUtil util = (SensorPopupUtil) pop;
829            switch (state) {
830                case PositionablePopupUtil.FONT_COLOR:
831                    util.setForeground(desiredColor);
832                    break;
833                case PositionablePopupUtil.BACKGROUND_COLOR:
834                    util.setBackgroundColor(desiredColor);
835                    break;
836                case PositionablePopupUtil.BORDER_COLOR:
837                    util.setBorderColor(desiredColor);
838                    break;
839                case UNKOWN_FONT_COLOR:
840                    setTextUnknown(desiredColor);
841                    break;
842                case UNKOWN_BACKGROUND_COLOR:
843                    util.setHasBackground(desiredColor != null);
844                    setBackgroundUnknown(desiredColor);
845                    break;
846                case ACTIVE_FONT_COLOR:
847                    setTextActive(desiredColor);
848                    break;
849                case ACTIVE_BACKGROUND_COLOR:
850                    util.setHasBackground(desiredColor != null);
851                    setBackgroundActive(desiredColor);
852                    break;
853                case INACTIVE_FONT_COLOR:
854                    setTextInActive(desiredColor);
855                    break;
856                case INACTIVE_BACKGROUND_COLOR:
857                    util.setHasBackground(desiredColor != null);
858                    setBackgroundInActive(desiredColor);
859                    break;
860                case INCONSISTENT_FONT_COLOR:
861                    setTextInconsistent(desiredColor);
862                    break;
863                case INCONSISTENT_BACKGROUND_COLOR:
864                    util.setHasBackground(desiredColor != null);
865                    setBackgroundInconsistent(desiredColor);
866                    break;
867                default:
868                    break;
869            }
870        }
871    }
872
873    private Color getColor(int state){
874        PositionablePopupUtil pop = getPopupUtility();
875        if (pop instanceof SensorPopupUtil) {
876            SensorPopupUtil util = (SensorPopupUtil) pop;
877            switch (state) {
878                case PositionablePopupUtil.FONT_COLOR:
879                    return util.getForeground();
880                case PositionablePopupUtil.BACKGROUND_COLOR:
881                    return util.getBackground();
882                case PositionablePopupUtil.BORDER_COLOR:
883                    return util.getBorderColor();
884                case UNKOWN_FONT_COLOR:
885                    return getTextUnknown();
886                case UNKOWN_BACKGROUND_COLOR:
887                    return getBackgroundUnknown();
888                case ACTIVE_FONT_COLOR:
889                    return getTextActive();
890                case ACTIVE_BACKGROUND_COLOR:
891                    return getBackgroundActive();
892                case INACTIVE_FONT_COLOR:
893                    return getTextInActive();
894                case INACTIVE_BACKGROUND_COLOR:
895                    return getBackgroundInActive();
896                case INCONSISTENT_FONT_COLOR:
897                    return getTextInconsistent();
898                case INCONSISTENT_BACKGROUND_COLOR:
899                    return getBackgroundInconsistent();
900                default:
901                    return null;
902            }
903        }
904        return null;
905    }
906
907    void changeLayoutSensorType() {
908//        NamedBeanHandle <Sensor> handle = getNamedSensor();
909        if (isIcon()) {
910            _icon = false;
911            _text = true;
912            setIcon(null);
913//            setOriginalText(getUnRotatedText());
914            setSuperText(null);
915            setOpaque(true);
916        } else if (isText()) {
917            _icon = true;
918            _text = (originalText != null && originalText.length() > 0);
919            setUnRotatedText(getOriginalText());
920            setOpaque(false);
921        }
922        _namedIcon = null;
923        setAttributes();
924        displayState(sensorState());
925//        setSensor(handle);
926        int deg = getDegrees();
927        rotate(deg);
928        if (deg != 0 && _text && !_icon) {
929            setSuperText(null);
930        }
931    }
932
933    private int flashStateOn = -1;
934    private int flashStateOff = -1;
935    private boolean flashon = false;
936    private ActionListener taskPerformer;
937    private Timer flashTimer;
938
939    synchronized public void flashSensor(int tps, int state1, int state2) {
940        if ((flashTimer != null) && flashTimer.isRunning()) {
941            return;
942        }
943        //Set the maximum number of state changes to 10 per second
944        if (tps > 10) {
945            tps = 10;
946        } else if (tps <= 0) {
947            return;
948        }
949        if ((_state2nameMap.get(state1) == null) || _state2nameMap.get(state2) == null) {
950            log.error("one or other of the states passed for flash is null");
951            return;
952        } else if (state1 == state2) {
953            log.debug("Both states to flash between are the same, therefore no flashing will occur");
954            return;
955        }
956        int interval = (1000 / tps) / 2;
957        flashStateOn = state1;
958        flashStateOff = state2;
959        if (taskPerformer == null) {
960            taskPerformer = (ActionEvent evt) -> {
961                if (flashon) {
962                    flashon = false;
963                    displayState(flashStateOn);
964                } else {
965                    flashon = true;
966                    displayState(flashStateOff);
967                }
968            };
969        }
970        flashTimer = new Timer(interval, taskPerformer);
971        flashTimer.start();
972    }
973
974    synchronized public void stopFlash() {
975        if (flashTimer != null) {
976            flashTimer.stop();
977        }
978        displayState(sensorState());
979    }
980
981    class SensorPopupUtil extends PositionablePopupUtil {
982
983        SensorPopupUtil(Positionable parent, javax.swing.JComponent textComp) {
984            super(parent, textComp);
985        }
986
987        @Override
988        public SensorPopupUtil clone(Positionable parent, JComponent textComp) {
989            SensorPopupUtil util = new SensorPopupUtil(parent, textComp);
990            util.setJustification(getJustification());
991            util.setHorizontalAlignment(getJustification());
992            util.setFixedWidth(getFixedWidth());
993            util.setFixedHeight(getFixedHeight());
994            util.setMargin(getMargin());
995            util.setBorderSize(getBorderSize());
996            util.setBorderColor(getBorderColor());
997            util.setFont(util.getFont().deriveFont(getFontStyle()));
998            util.setFontSize(getFontSize());
999            util.setOrientation(getOrientation());
1000            util.setBackgroundColor(getBackground());
1001            util.setForeground(getForeground());
1002            util.setHasBackground(hasBackground());     // must do this AFTER setBackgroundColor
1003            return util;
1004        }
1005
1006        @Override
1007        public void setTextJustificationMenu(JPopupMenu popup) {
1008            if (isText()) {
1009                super.setTextJustificationMenu(popup);
1010            }
1011        }
1012
1013        @Override
1014        public void setTextOrientationMenu(JPopupMenu popup) {
1015            if (isText()) {
1016                super.setTextOrientationMenu(popup);
1017            }
1018        }
1019
1020        @Override
1021        public void setFixedTextMenu(JPopupMenu popup) {
1022            if (isText()) {
1023                super.setFixedTextMenu(popup);
1024            }
1025        }
1026
1027        @Override
1028        public void setTextMarginMenu(JPopupMenu popup) {
1029            if (isText()) {
1030                super.setTextMarginMenu(popup);
1031            }
1032        }
1033
1034        @Override
1035        public void setTextBorderMenu(JPopupMenu popup) {
1036            if (isText()) {
1037                super.setTextBorderMenu(popup);
1038            }
1039        }
1040
1041        @Override
1042        public void setTextFontMenu(JPopupMenu popup) {
1043            if (isText()) {
1044                super.setTextFontMenu(popup);
1045            }
1046        }
1047
1048        @Override
1049        public void setBackgroundMenu(JPopupMenu popup) {
1050            if (isIcon()) {
1051                super.setBackgroundMenu(popup);
1052            }
1053        }
1054    }
1055
1056    private final static Logger log = LoggerFactory.getLogger(SensorIcon.class);
1057
1058}