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

#!/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): 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 resetFormat(self): """reimplement to update controller if format is changed""" TaurusBaseWidget.resetFormat(self) self.controllerUpdate()
[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()