#!/usr/bin/env python
# ###########################################################################
#
# 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 QLabel
"""
from collections.abc import Sequence
import re
import string
from taurus.core.taurusbasetypes import (
TaurusElementType,
TaurusEventType,
AttrQuality,
TaurusDevState,
)
from taurus.external.qt import Qt
from taurus.qt.qtgui.base import TaurusBaseWidget
from taurus.qt.qtgui.base import TaurusBaseController
from taurus.qt.qtgui.base import TaurusScalarAttributeControllerHelper
from taurus.qt.qtgui.base import TaurusConfigurationControllerHelper
from taurus.qt.qtgui.base import updateLabelBackground
__docformat__ = "restructuredtext"
_QT_PLUGIN_INFO = {
"module": "taurus.qt.qtgui.display",
"group": "Taurus Display",
"icon": "designer:label.png",
}
TaurusModelType = TaurusElementType
EventType = TaurusEventType
class _SafeFormatter(string.Formatter):
"""
Like default formatter but leaves unmatched keys in the result string
instead of raising an exception.
Inspired by:
https://github.com/silx-kit/pyFAI/blob/0.19/pyFAI/utils/stringutil.py#L41
"""
def get_field(self, field_name, args, kwargs):
try:
return string.Formatter.get_field(self, field_name, args, kwargs)
except KeyError:
return "{%s}" % field_name, field_name
class TaurusLabelController(TaurusBaseController):
StyleSheetTemplate = (
"border-style: outset; border-width: 2px; border-color: {0}; {1}"
)
def __init__(self, label):
self._text = ""
self._trimmedText = False
self._trimPattern = re.compile("<[^<]*>")
TaurusBaseController.__init__(self, label)
def _setStyle(self):
TaurusBaseController._setStyle(self)
label = self.label()
# if update as palette
if self.usePalette():
label.setFrameShape(Qt.QFrame.Box)
label.setFrameShadow(Qt.QFrame.Raised)
label.setLineWidth(1)
def label(self):
return self.widget()
def showValueDialog(self, label):
Qt.QMessageBox.about(label, "Full text", self._text)
def _needsStateConnection(self):
label = self.label()
ret = "state" in (label.fgRole, label.bgRole)
return ret
def _updateForeground(self, label):
fgRole, value = label.fgRole, ""
# handle special cases (that are not covered with fragment)
if fgRole.lower() == "state":
try:
value = self.state().name
except AttributeError:
pass # protect against calls with state not instantiated
elif fgRole.lower() in ("", "none"):
pass
else:
value = label.getDisplayValue(fragmentName=fgRole)
self._text = text = label.prefixText + value + label.suffixText
# Checks that the display fits in the widget and sets it to "..." if
# it does not fit the widget
self._trimmedText = self._shouldTrim(label, text)
if self._trimmedText:
text = "<a href='...'>...</a>"
label.setText_(text)
def _shouldTrim(self, label, text):
if not label.autoTrim:
return False
text = re.sub(self._trimPattern, "", text)
font_metrics = Qt.QFontMetrics(label.font())
size, textSize = label.size().width(), font_metrics.width(text)
return textSize > size
def _updateToolTip(self, label):
if not label.getAutoTooltip():
return
toolTip = label.getFormatedToolTip()
if self._trimmedText:
toolTip = "<p><b>Value:</b> %s</p><hr>%s" % (self._text, toolTip)
label.setToolTip(toolTip)
_updateBackground = updateLabelBackground
class TaurusLabelControllerAttribute(
TaurusScalarAttributeControllerHelper, TaurusLabelController
):
def __init__(self, label):
TaurusScalarAttributeControllerHelper.__init__(self)
TaurusLabelController.__init__(self, label)
def _setStyle(self):
TaurusLabelController._setStyle(self)
label = self.label()
label.setDynamicTextInteractionFlags(
Qt.Qt.TextSelectableByMouse | Qt.Qt.LinksAccessibleByMouse
)
class TaurusLabelControllerConfiguration(
TaurusConfigurationControllerHelper, TaurusLabelController
):
def __init__(self, label):
TaurusConfigurationControllerHelper.__init__(self)
TaurusLabelController.__init__(self, label)
def _setStyle(self):
TaurusLabelController._setStyle(self)
label = self.label()
label.setDynamicTextInteractionFlags(Qt.Qt.NoTextInteraction)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~~-~-
# Design time controllers for label
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~~-~-
class TaurusLabelControllerDesignMode(object):
def _updateLength(self, lcd):
lcd.setNumDigits(6)
def getDisplayValue(self, write=False, **kwargs):
v = self.w_value()
if not write:
v = self.value()
return "%6.2f" % v
def value(self):
return 99.99
def w_value(self):
return 0.0
def quality(self):
return AttrQuality.ATTR_VALID
def state(self):
return TaurusDevState.Ready
def _updateToolTip(self, lcd):
lcd.setToolTip("Some random value for design purposes only")
class TaurusLabelControllerAttributeDesignMode(
TaurusLabelControllerDesignMode, TaurusLabelControllerAttribute
):
def __init__(self, label):
TaurusLabelControllerDesignMode.__init__(self)
TaurusLabelControllerAttribute.__init__(self, label)
class TaurusLabelControllerConfigurationDesignMode(
TaurusLabelControllerDesignMode, TaurusLabelControllerConfiguration
):
def __init__(self, label):
TaurusLabelControllerDesignMode.__init__(self)
TaurusLabelControllerConfiguration.__init__(self, label)
def getDisplayValue(self, write=False, **kwargs):
return "%6.2f" % -99.99
def _updateToolTip(self, lcd):
lcd.setToolTip(
"Some random configuration value for design purposes only"
)
_CONTROLLER_MAP = {
None: None,
TaurusModelType.Unknown: None,
TaurusModelType.Attribute: TaurusLabelControllerAttribute,
TaurusModelType.Configuration: TaurusLabelControllerConfiguration,
}
_DESIGNER_CONTROLLER_MAP = {
None: TaurusLabelControllerAttributeDesignMode,
TaurusModelType.Unknown: TaurusLabelControllerAttributeDesignMode,
TaurusModelType.Attribute: TaurusLabelControllerAttributeDesignMode,
TaurusModelType.Configuration: TaurusLabelControllerConfigurationDesignMode, # noqa
}
[docs]
class TaurusLabel(Qt.QLabel, TaurusBaseWidget):
DefaultPrefix = ""
DefaultSuffix = ""
DefaultBgRole = "quality"
DefaultFgRole = "rvalue"
DefaultShowText = True
DefaultModelIndex = None
DefaultAutoTrim = True
DefaultAlignment = Qt.Qt.AlignRight | Qt.Qt.AlignVCenter
_deprecatedRoles = dict(value="rvalue", w_value="wvalue")
def __init__(self, parent=None, designMode=False):
self._safeFormatter = _SafeFormatter()
self._prefix = self.DefaultPrefix
self._suffix = self.DefaultSuffix
self._permanentText = None
self._bgRole = self.DefaultBgRole
self._fgRole = self.DefaultFgRole
self._modelIndex = self.DefaultModelIndex
self._autoTrim = self.DefaultAutoTrim
self._modelIndexStr = ""
self._controller = None
self._dynamicTextInteractionFlags = True
name = self.__class__.__name__
self.call__init__wo_kw(Qt.QLabel, parent)
self.call__init__(TaurusBaseWidget, name, designMode=designMode)
self.setAlignment(self.DefaultAlignment)
self.linkActivated.connect(self.showValueDialog)
# if we are in design mode there will be no events so we force the
# creation of a controller object
if self._designMode:
self.controllerUpdate()
# register configurable properties
self.registerConfigProperty(
self.getPermanentText, self._setPermanentText, "permanentText"
)
def _calculate_controller_class(self):
ctrl_map = _CONTROLLER_MAP
if self._designMode:
ctrl_map = _DESIGNER_CONTROLLER_MAP
model_type = self.getModelType()
# ugly workaround to adapt TaurusLabel to tep14 without refactoring
# TODO: proper refactoring of TaurusValue to supress the Conf API
if model_type == TaurusModelType.Attribute and self.modelFragmentName:
model_type = TaurusModelType.Configuration
ctrl_klass = ctrl_map.get(model_type, TaurusLabelController)
return ctrl_klass
[docs]
def controller(self):
ctrl = self._controller
# if there is a controller object and it is not the base controller...
if ctrl is not None and not ctrl.__class__ == TaurusLabelController:
return ctrl
# if there is a controller object and it is still the same class...
ctrl_klass = self._calculate_controller_class()
if ctrl_klass is None:
return None
elif ctrl.__class__ == ctrl_klass:
return ctrl
self._controller = ctrl = ctrl_klass(self)
return ctrl
[docs]
def controllerUpdate(self):
ctrl = self.controller()
if ctrl is not None:
ctrl.update()
[docs]
def showValueDialog(self, *args):
ctrl = self.controller()
if ctrl is not None:
ctrl.showValueDialog(self)
[docs]
def resizeEvent(self, event):
# recheck the display every time we resize to make sure the text should
# become trimmed or not
if not getattr(self, "_inResize", False):
self._inResize = True
self.controllerUpdate()
self._inResize = False
Qt.QLabel.resizeEvent(self, event)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# TaurusBaseWidget overwriting
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs]
def handleEvent(self, evt_src, evt_type, evt_value):
if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic):
self.emitValueChanged()
ctrl = self.controller()
if ctrl is not None:
ctrl.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
self._permanentText = None
TaurusBaseWidget.setModel(self, m, **kwargs)
if self.modelFragmentName:
self.setFgRole(self.modelFragmentName)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# QT property definition
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[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) == int:
mi_value = (mi_value,)
if not isinstance(mi_value, Sequence):
return
self._modelIndex = mi_value
self._modelIndexStr = mi
self.controllerUpdate()
[docs]
def getModelMimeData(self):
mimeData = TaurusBaseWidget.getModelMimeData(self)
mimeData.setText(self.text())
return mimeData
[docs]
def resetModelIndex(self):
self.setModelIndex(self.DefaultModelIndex)
[docs]
def getBgRole(self):
return self._bgRole
[docs]
def setBgRole(self, bgRole):
"""
Set the background role. The label background will be set according
to the current palette and the role. Valid roles are:
- 'none' : no background
- 'state' a color depending on the device state
- 'quality' a color depending on the attribute quality
- 'rvalue' a color depending on the rvalue of the attribute
- <arbitrary member name> a color based on the value of an arbitrary
member of the model object (warning: experimental feature!)
.. warning:: the <arbitrary member name> support is still experimental
and its API may change in future versions
"""
self._bgRole = str(bgRole)
self.controllerUpdate()
[docs]
def resetBgRole(self):
"""Reset the background role to its default value"""
self.setBgRole(self.DefaultBgRole)
[docs]
def getFgRole(self):
"""get the foreground role for this label (see :meth:`setFgRole`)"""
return self._fgRole
[docs]
def setFgRole(self, fgRole):
"""Set what is shown as the foreground (the text) of the label
Valid Roles are:
- 'rvalue' the read value of the attribute
- 'wvalue' the write value of the attribute
- 'none' : no text
- 'quality' - the quality of the attribute is displayed
- 'state' - the device state
"""
# warn about deprecated roles
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.controllerUpdate()
[docs]
def resetFgRole(self):
"""Reset the foreground role to its default value"""
self.setFgRole(self.DefaultFgRole)
[docs]
def getPrefixText(self):
return self._prefix
[docs]
def setPrefixText(self, prefix):
self._prefix = str(prefix)
self.controllerUpdate()
[docs]
def resetPrefixText(self):
self.setPrefixText(self.DefaultPrefix)
[docs]
def getSuffixText(self):
return self._suffix
[docs]
def setSuffixText(self, suffix):
self._suffix = str(suffix)
self.controllerUpdate()
[docs]
def resetSuffixText(self):
self.setSuffixText(self.DefaultSuffix)
[docs]
def getPermanentText(self):
return self._permanentText
def _setPermanentText(self, text):
self._permanentText = text
if text is not None:
self.setText_(text)
[docs]
def setText_(self, text):
"""Method to expose QLabel.setText"""
Qt.QLabel.setText(self, text)
[docs]
def setText(self, text):
"""Reimplementation of setText to set permanentText"""
self._setPermanentText(text)
[docs]
def setAutoTrim(self, trim):
"""Enable/disable auto-trimming of the text. If trim is True, the text
in the label will be trimmed when it doesn't fit in the available space
:param trim:
:type trim: bool
"""
self._autoTrim = trim
self.controllerUpdate()
[docs]
def setDynamicTextInteractionFlags(self, flags):
if self.hasDynamicTextInteractionFlags():
Qt.QLabel.setTextInteractionFlags(self, flags)
[docs]
def hasDynamicTextInteractionFlags(self):
return self._dynamicTextInteractionFlags
[docs]
def setTextInteractionFlags(self, flags):
Qt.QLabel.setTextInteractionFlags(self, flags)
self._dynamicTextInteractionFlags = False
[docs]
def resetTextInteractionFlags(self):
Qt.QLabel.resetTextInteractionFlags(self)
self.dynamicTextInteractionFlags = True
[docs]
def getAutoTrim(self):
"""
Whether auto-trimming of the text is enabled.
:return:
:rtype: bool
"""
return self._autoTrim
[docs]
def resetAutoTrim(self):
"""Reset auto-trimming to its default value"""
self.setAutoTrim(self.DefaultAutoTrim)
[docs]
def displayValue(self, v):
"""Reimplementation of displayValue for TaurusLabel"""
if self._permanentText is None:
value = TaurusBaseWidget.displayValue(self, v)
else:
value = self._permanentText
dev = None
attr = None
modeltype = self.getModelType()
if modeltype == TaurusElementType.Device:
dev = self.getModelObj()
elif modeltype == TaurusElementType.Attribute:
attr = self.getModelObj()
dev = attr.getParent()
try:
v = self._safeFormatter.format(value, dev=dev, attr=attr)
except Exception as e:
self.warning(
"Error formatting display (%r). Reverting to raw string", e
)
v = value
return v
[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:`TaurusLabel.getModelIndex`
#: * :meth:`TaurusLabel.setModelIndex`
#: * :meth:`TaurusLabel.resetModelIndex`
#:
#: .. seealso:: :ref:`model-concept`
modelIndex = Qt.pyqtProperty(
"QString", getModelIndex, setModelIndex, resetModelIndex
)
#: This property holds a prefix text
#:
#: **Access functions:**
#:
#: * :meth:`TaurusLabel.getPrefixText`
#: * :meth:`TaurusLabel.setPrefixText`
#: * :meth:`TaurusLabel.resetPrefixText`
prefixText = Qt.pyqtProperty(
"QString",
getPrefixText,
setPrefixText,
resetPrefixText,
doc="prefix text",
)
#: This property holds a suffix text
#:
#: **Access functions:**
#:
#: * :meth:`TaurusLabel.getSuffixText`
#: * :meth:`TaurusLabel.setSuffixText`
#: * :meth:`TaurusLabel.resetSuffixText`
suffixText = Qt.pyqtProperty(
"QString",
getSuffixText,
setSuffixText,
resetSuffixText,
doc="suffix text",
)
#: This property holds the foreground role (the text).
#: Valid values are:
#:
#: #. ''/'None' - no value is displayed
#: #. 'rvalue' - the value is displayed
#: #. 'wvalue' - the write value is displayed
#: #. 'quality' - the quality is displayed
#: #. 'state' - the device state is displayed
#:
#: **Access functions:**
#:
#: * :meth:`TaurusLabel.getFgRole`
#: * :meth:`TaurusLabel.setFgRole`
#: * :meth:`TaurusLabel.resetFgRole`
fgRole = Qt.pyqtProperty(
"QString", getFgRole, setFgRole, resetFgRole, doc="foreground role"
)
#: This property holds the background role.
#: Valid values are ''/'None', 'quality', 'state'
#:
#: **Access functions:**
#:
#: * :meth:`TaurusLabel.getBgRole`
#: * :meth:`TaurusLabel.setBgRole`
#: * :meth:`TaurusLabel.resetBgRole`
bgRole = Qt.pyqtProperty(
"QString", getBgRole, setBgRole, resetBgRole, doc="background role"
)
#: Specifies wether the text will be trimmed when it doesn't fit in the
#: available space
#:
#: **Access functions:**
#:
#: * :meth:`TaurusLabel.getAutoTrim`
#: * :meth:`TaurusLabel.setAutoTrim`
#: * :meth:`TaurusLabel.resetAutoTrim`
autoTrim = Qt.pyqtProperty(
"bool", getAutoTrim, setAutoTrim, resetAutoTrim, doc="auto trim text"
)
#: Specifies whether the user can drag data from this widget
#:
#: **Access functions:**
#:
#: * :meth:`TaurusLabel.isDragEnabled`
#: * :meth:`TaurusLabel.setDragEnabled`
#: * :meth:`TaurusLabel.resetDragEnabled`
dragEnabled = Qt.pyqtProperty(
"bool",
TaurusBaseWidget.isDragEnabled,
TaurusBaseWidget.setDragEnabled,
TaurusBaseWidget.resetDragEnabled,
doc="enable dragging",
)
#: Specifies how the label should interact with user input if it displays
#: text.
#:
#: **Access functions:**
#:
#: * :meth:`TaurusLabel.textInteractionFlags`
#: * :meth:`TaurusLabel.setTextInteractionFlags`
#: * :meth:`TaurusLabel.resetTextInteractionFlags`
textInteractionFlags = Qt.pyqtProperty(
Qt.Qt.TextInteractionFlag,
Qt.QLabel.textInteractionFlags,
setTextInteractionFlags,
resetTextInteractionFlags,
doc=(
"Specifies how the label should interact "
+ "with user input if it displays text."
),
)
def demo():
"Label"
from .demo import tauruslabeldemo
return tauruslabeldemo.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 label demo",
app_version="1.0",
org_domain="Taurus",
org_name="Tango community",
)
args = app.get_command_line_args()
if len(args) == 0:
w = demo()
else:
w = Qt.QWidget()
layout = Qt.QGridLayout()
w.setLayout(layout)
for model in args:
label = TaurusLabel()
label.model = model
layout.addWidget(label)
w.show()
if owns_app:
sys.exit(app.exec_())
else:
return w
if __name__ == "__main__":
main()