Source code for taurus.qt.qtgui.display.taurusled

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""This module provides a set of basic Taurus widgets based on QLed"""

from __future__ import annotations

import weakref
from collections.abc import Sequence

from taurus.core import AttrQuality, DataFormat, DataType
from taurus.core.taurusbasetypes import TaurusEventType
from taurus.core.util.colors import (
    ATTRIBUTE_QUALITY_PALETTE,
    DEVICE_STATE_PALETTE,
)
from taurus.external.qt import Qt
from taurus.qt.qtgui.base import TaurusBaseWidget
from taurus.qt.qtgui.display.qled import QLed, QLedPainted, strToQColor

__docformat__ = "restructuredtext"

_QT_PLUGIN_INFO = {
    "module": "taurus.qt.qtgui.display",
    "group": "Taurus Display",
    "icon": "designer:ledgreen.png",
}

_PIXMAP_FALLBACKS = {
    "ON": "green",
    "OFF": "black",
    "CLOSE": "white",
    "OPEN": "green",
    "INSERT": "white",
    "EXTRACT": "green",
    "MOVING": "blue",
    "STANDBY": "yellow",
    "FAULT": "red",
    "INIT": "yellow",
    "RUNNING": "blue",
    "ALARM": "orange",
    "DISABLE": "magenta",
    "UNKNOWN": "black",
    "ATTR_VALID": "green",
    "ATTR_INVALID": "red",
    "ATTR_ALARM": "orange",
    "ATTR_WARNING": "orange",
    "ATTR_CHANGING": "blue",
}


def _to_bool(value):
    if isinstance(value, str):
        return value.strip().lower() in ("1", "true", "yes", "on")
    return bool(value)


class _TaurusLedController(object):
    #            key     status,     color, inTrouble
    LedMap = {
        True: (True, "green", False),
        False: (False, "black", False),
        None: (False, "black", True),
    }

    LedQualityMap = {
        AttrQuality.ATTR_ALARM: (True, "ATTR_ALARM", False),
        AttrQuality.ATTR_CHANGING: (True, "ATTR_CHANGING", False),
        AttrQuality.ATTR_INVALID: (True, "ATTR_INVALID", False),
        AttrQuality.ATTR_VALID: (True, "ATTR_VALID", False),
        AttrQuality.ATTR_WARNING: (True, "ATTR_WARNING", False),
        None: (False, None, True),
    }

    def __init__(self, widget):
        self._widget = weakref.ref(widget)

    def widget(self):
        return self._widget()

    def modelObj(self):
        return self.widget().getModelObj()

    def value(self):
        widget, obj = self.widget(), self.modelObj()
        fgRole = widget.fgRole
        value = None
        if fgRole == "rvalue":
            value = obj.rvalue
        elif fgRole == "wvalue":
            value = obj.wvalue
        elif fgRole == "quality":
            return obj.quality

        # handle 1D and 2D values
        if obj.data_format is not DataFormat._0D:
            idx = widget.getModelIndexValue()
            if idx:
                for i in idx:
                    value = value[i]

        return value if value is None else bool(value)

    def usePreferedColor(self, widget):
        return True

    def handleEvent(self, evt_src, evt_type, evt_value):
        self.update()

    def update(self):
        widget = self.widget()

        self._updateDisplay(widget)
        self._updateToolTip(widget)

    def _updateDisplay(self, widget):
        key = None
        try:
            key = self.value()
        except Exception:
            pass
        ledMap = self.LedMap
        if widget.fgRole == "quality":
            ledMap = self.LedQualityMap
        try:
            status, color_key, trouble = ledMap[key]
        except Exception:
            status, color_key, trouble = False, "FAULT", True
        widget.setLedFromModel(status, color_key, trouble)

    def _updateToolTip(self, widget):
        widget.setToolTip(widget.getFormatedToolTip())


class _TaurusLedControllerBool(_TaurusLedController):
    def usePreferedColor(self, widget):
        # use prefered widget color if representing the boolean read or write
        # value. If representing the quality, use the quality map
        return widget.fgRole != "quality"


try:
    from taurus.core.tango import DevState  # TODO: Tango-centric

    class _TaurusLedControllerState(_TaurusLedController):
        #                key      status,       color, inTrouble

        LedMap = {
            DevState.ON: (True, "ON", False),
            DevState.OFF: (False, "OFF", False),
            DevState.CLOSE: (True, "CLOSE", False),
            DevState.OPEN: (True, "OPEN", False),
            DevState.INSERT: (True, "INSERT", False),
            DevState.EXTRACT: (True, "EXTRACT", False),
            DevState.MOVING: (True, "MOVING", False),
            DevState.STANDBY: (True, "STANDBY", False),
            DevState.FAULT: (True, "FAULT", False),
            DevState.INIT: (True, "INIT", False),
            DevState.RUNNING: (True, "RUNNING", False),
            DevState.ALARM: (True, "ALARM", False),
            DevState.DISABLE: (True, "DISABLE", False),
            DevState.UNKNOWN: (False, "UNKNOWN", False),
            None: (False, None, True),
        }

        def value(self):
            widget, obj = self.widget(), self.modelObj()
            fgRole = widget.fgRole
            value = None
            if fgRole == "rvalue":
                value = obj.rvalue
            elif fgRole == "wvalue":
                value = obj.wvalue
            elif fgRole == "quality":
                value = obj.quality
            return value

        def usePreferedColor(self, widget):
            # never use prefered widget color. Use always the map
            return False

except Exception:
    pass


class _TaurusLedControllerDesignMode(_TaurusLedController):
    def _updateDisplay(self, widget):
        widget.ledStatus = True
        if widget.ledStatus:
            widget.ledColor = widget.onColor
        else:
            widget.ledColor = widget.offColor
        widget.setAutoFillBackground(False)

    def _updateToolTip(self, widget):
        widget.setToolTip("Design mode TaurusLed")


[docs] class TaurusLed(Qt.QWidget, TaurusBaseWidget): """A widget designed to represent with a LED image the state of a device, the value of a boolean attribute or the quality of an attribute. Two available backends: painted (QPainter) or pixmap (external PNG)""" DefaultModelIndex = None DefaultFgRole = "rvalue" DefaultOnColor = "green" DefaultOffColor = "green" # new default backend (can be overridden by tauruscustomsettings.TAURUS_LED_BACKEND) DefaultBackend = "painted" # "painted" | "pixmap" _deprecatedRoles = dict(value="rvalue", w_value="wvalue") def __init__(self, parent=None, designMode=False, backend=None): name = self.__class__.__name__ self._designMode = designMode self._modelIndex = self.DefaultModelIndex self._modelIndexStr = "" self._fgRole = self.DefaultFgRole self._onColor = self.DefaultOnColor self._offColor = self.DefaultOffColor self._controller = None Qt.QWidget.__init__(self, parent) self.call__init__(TaurusBaseWidget, name, designMode=designMode) # container layout (holds the real led widget) layout = Qt.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self._backend = self._resolve_backend(backend) self._led = self._create_led_impl( self._backend, parent=self, designMode=designMode ) layout.addWidget(self._led) self.controller().update() # ---- backend selection def _resolve_backend(self, backend): if backend is not None: return str(backend).lower() # read from tauruscustomsettings if present try: from taurus import tauruscustomsettings return str( getattr(tauruscustomsettings, "TAURUS_LED_BACKEND", self.DefaultBackend) ).lower() except Exception: return self.DefaultBackend def _create_led_impl(self, backend, parent, designMode): if backend == "pixmap": return QLed(parent=parent, designMode=designMode) # default return QLedPainted(parent=parent)
[docs] def getBackend(self): return self._backend
backend = Qt.pyqtProperty("QString", getBackend, doc="LED backend: painted|pixmap") # ---- delegate commonly used API/properties to impl
[docs] def getLedStatus(self): return bool(self._led.ledStatus)
[docs] def setLedStatus(self, v): self._led.ledStatus = _to_bool(v)
[docs] def resetLedStatus(self): self._led.resetLedStatus()
ledStatus = Qt.pyqtProperty( "bool", getLedStatus, setLedStatus, resetLedStatus, doc="led status" )
[docs] def getLedColor(self): return self._led.ledColor
[docs] def setLedColor(self, v): self._led.ledColor = v
[docs] def resetLedColor(self): self._led.resetLedColor()
ledColor = Qt.pyqtProperty( "QVariant", getLedColor, setLedColor, resetLedColor, doc="led color" ) def _qcolor_from_palette_key(self, key): if key is None: return strToQColor("black") if self.fgRole == "quality": palette = ATTRIBUTE_QUALITY_PALETTE else: palette = DEVICE_STATE_PALETTE try: r, g, b = palette.rgb(key) return Qt.QColor(r, g, b) except Exception: return strToQColor("black") def _resolve_pixmap_color(self, palette_key, status=True): if palette_key in _PIXMAP_FALLBACKS: return _PIXMAP_FALLBACKS[palette_key] return self.onColor if status else self.offColor def _resolve_backend_color(self, palette_key, status=True): if self._backend == "painted": return self._qcolor_from_palette_key(palette_key) # pixmap fallback return self._resolve_pixmap_color(palette_key, status=status)
[docs] def setLedFromModel(self, status, color_key, trouble=False): self.ledStatus = status if self.controller().usePreferedColor(self) and not trouble: if status: self.ledColor = self.onColor else: self.ledColor = self.offColor else: self.ledColor = self._resolve_backend_color(color_key, status=status) if trouble: self.setAutoFillBackground(True) bg_brush = Qt.QBrush(Qt.Qt.BDiagPattern) palette = self.palette() palette.setBrush(Qt.QPalette.Window, bg_brush) palette.setBrush(Qt.QPalette.Base, bg_brush) self.setPalette(palette) else: self.setAutoFillBackground(False)
[docs] def getLedInverted(self): return bool(self._led.ledInverted)
[docs] def setLedInverted(self, inverted): self._led.ledInverted = _to_bool(inverted)
[docs] def resetLedInverted(self): self._led.resetLedInverted()
ledInverted = Qt.pyqtProperty( "bool", getLedInverted, setLedInverted, resetLedInverted, doc="led inverted mode", )
[docs] def getBlinkingInterval(self): return self._led.blinkingInterval
[docs] def setBlinkingInterval(self, v): self._led.blinkingInterval = v
[docs] def resetBlinkingInterval(self): self._led.blinkingInterval = self._led.DefaultBlinkingInterval
blinkingInterval = Qt.pyqtProperty( "int", getBlinkingInterval, setBlinkingInterval, resetBlinkingInterval, doc="blinking interval", ) # ledPattern only exists on QLed (pixmap backend)
[docs] def getLedPattern(self): if hasattr(self._led, "ledPattern"): return self._led.ledPattern return ""
[docs] def setLedPattern(self, pattern): if hasattr(self._led, "ledPattern"): self._led.ledPattern = pattern
ledPattern = Qt.pyqtProperty( "QString", getLedPattern, setLedPattern, doc="led pattern (pixmap backend only)" ) # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # TaurusBaseWidget overwriting # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] def handleEvent(self, evt_src, evt_type, evt_value): if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic): self.emitValueChanged() self.controller().handleEvent(evt_src, evt_type, evt_value)
[docs] def isReadOnly(self): return True
[docs] def setModel(self, m, **kwargs): # force to build another controller self._controller = None TaurusBaseWidget.setModel(self, m, **kwargs)
# ---- controller selection def _calculate_controller_class(self): model = self.getModelObj() if self._designMode: return _TaurusLedControllerDesignMode if model is None: return _TaurusLedController if model.isBoolean(): return _TaurusLedControllerBool if model.type == DataType.DevState and _TaurusLedControllerState is not None: return _TaurusLedControllerState return _TaurusLedController
[docs] def controller(self): ctrl = self._controller if ctrl is not None and not ctrl.__class__ == _TaurusLedController: return ctrl ctrl_klass = self._calculate_controller_class() if ctrl is not None and ctrl.__class__ == ctrl_klass: return ctrl self._controller = ctrl = ctrl_klass(self) return ctrl
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # QT property definition # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] def getFgRole(self): return self._fgRole
[docs] def setFgRole(self, fgRole): role = self._deprecatedRoles.get(fgRole, fgRole) if fgRole != role: self.deprecated( rel="4.0", dep="setFgRole(%s)" % fgRole, alt="setFgRole(%s)" % role, ) self._fgRole = str(role) self.controller().update()
[docs] def resetFgRole(self): self.setFgRole(self.DefaultFgRole)
[docs] def getOnColor(self): """Returns the preferred led on color :return: led on color :rtype: str """ return self._onColor
[docs] def setOnColor(self, color): """Sets the preferred led on color :param status: the new on color :type status: str """ color = str(color).lower() if self._backend != "painted": if not self._led.isLedColorValid(color): raise ValueError("Invalid color '%s'" % color) self._onColor = color self.controller().update()
[docs] def resetOnColor(self): """Resets the preferred led on color""" self.setOnColor(self.DefaultOnColor)
[docs] def getOffColor(self): """Returns the preferred led off color :return: led off color :rtype: str """ return self._offColor
[docs] def setOffColor(self, color): """Sets the preferred led off color :param status: the new off color :type status: str """ color = str(color).lower() if self.backend != "painted": if not self._led.isLedColorValid(color): raise ValueError("Invalid color '%s'" % color) self._offColor = color self.controller().update()
[docs] def resetOffColor(self): """Resets the preferred led color""" self.setOffColor(self.DefaultOffColor)
[docs] def getModelIndexValue(self): return self._modelIndex
[docs] def getModelIndex(self): return self._modelIndexStr
[docs] def setModelIndex(self, modelIndex): mi = str(modelIndex) if len(mi) == 0: self._modelIndex = None else: try: mi_value = eval(str(mi)) except Exception: return if type(mi_value) is int: mi_value = (mi_value,) if not isinstance(mi_value, Sequence): return self._modelIndex = mi_value self._modelIndexStr = mi self.controller().update()
[docs] def resetModelIndex(self): self.setModelIndex(self.DefaultModelIndex)
[docs] @classmethod def getQtDesignerPluginInfo(cls): d = TaurusBaseWidget.getQtDesignerPluginInfo() d.update(_QT_PLUGIN_INFO) return d
#: This property holds the unique URI string representing the model name #: with which this widget will get its data from. The convention used for #: the string can be found :ref:`here <model-concept>`. #: #: **Access functions:** #: #: * :meth:`TaurusBaseWidget.getModel` #: * :meth:`TaurusLabel.setModel` #: * :meth:`TaurusBaseWidget.resetModel` #: #: .. seealso:: :ref:`model-concept` model = Qt.pyqtProperty( "QString", TaurusBaseWidget.getModel, setModel, TaurusBaseWidget.resetModel, ) #: (deprecated)) useParentModel = Qt.pyqtProperty( "bool", TaurusBaseWidget.getUseParentModel, TaurusBaseWidget.setUseParentModel, TaurusBaseWidget.resetUseParentModel, ) #: This property holds the index inside the model value that should be #: displayed #: #: **Access functions:** #: #: * :meth:`TaurusLed.getModelIndex` #: * :meth:`TaurusLed.setModelIndex` #: * :meth:`TaurusLed.resetModelIndex` #: #: .. seealso:: :ref:`model-concept` modelIndex = Qt.pyqtProperty( "QString", getModelIndex, setModelIndex, resetModelIndex ) #: This property holds the foreground role. #: Valid values are: #: #: #. 'value' - the value is used #: #. 'w_value' - the write value is used #: #. 'quality' - the quality is used #: #: **Access functions:** #: #: * :meth:`TaurusLed.getFgRole` #: * :meth:`TaurusLed.setFgRole` #: * :meth:`TaurusLed.resetFgRole` fgRole = Qt.pyqtProperty( "QString", getFgRole, setFgRole, resetFgRole, doc="foreground role" ) #: This property holds the preferred led color #: This value is used for the cases where the model value does not contain #: enough information to distinguish between different On colors. #: For example, a bool attribute, when it is False it is displayed with the #: off led but when it is true it may be displayed On in any color. The #: prefered color would be used in this case. #: #: **Access functions:** #: #: * :meth:`TaurusLed.getOnColor` #: * :meth:`TaurusLed.setOnColor` #: * :meth:`TaurusLed.resetOnColor` onColor = Qt.pyqtProperty( "QString", getOnColor, setOnColor, resetOnColor, doc="preferred led On color", ) #: This property holds the preferred led color #: This value is used for the cases where the model value does not contain #: enough information to distinguish between different Off colors. #: For example, a bool attribute, when it is False it is displayed with the #: off led but when it is true it may be displayed On in any color. The #: prefered color would be used in this case. #: #: **Access functions:** #: #: * :meth:`TaurusLed.getOffColor` #: * :meth:`TaurusLed.setOffColor` #: * :meth:`TaurusLed.resetOffColor` offColor = Qt.pyqtProperty( "QString", getOffColor, setOffColor, resetOffColor, doc="preferred led Off color", )
def demo(): "Led" from taurus.qt.qtgui.display.demo import taurusleddemo return taurusleddemo.main() def main(): import sys import taurus.qt.qtgui.application Application = taurus.qt.qtgui.application.TaurusApplication app = Application.instance() owns_app = app is None if owns_app: import taurus.core.util.argparse parser = taurus.core.util.argparse.get_taurus_parser() parser.usage = "%prog [options] <full_attribute_name(s)>" app = Application( sys.argv, cmd_line_parser=parser, app_name="Taurus led demo", app_version="1.0", org_domain="taurus-scada.org", org_name="Tango community", ) args = app.get_command_line_args() if len(args) == 0: w = demo() else: models = map(str.lower, args) w = Qt.QWidget() layout = Qt.QGridLayout() w.setLayout(layout) for idx, model in enumerate(models): print(f"setting model {model}") led = TaurusLed(backend="pixmap") newLed = TaurusLed(backend="painted") led.model = model newLed.model = model layout.addWidget(led, 0, idx) layout.addWidget(newLed, 1, idx) w.show() if owns_app: sys.exit(app.exec_()) else: return w if __name__ == "__main__": main()