#!/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()