# 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
    from taurus.qt.qtgui.base import TaurusBgRoleSubscriber

    __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
    }

    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

        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

        def controllerUpdate(self):
            ctrl = self.controller()
            if ctrl is not None:
                ctrl.update()

        def showValueDialog(self, *args):
            ctrl = self.controller()
            if ctrl is not None:
                ctrl.showValueDialog(self)

        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
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        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)

        def isReadOnly(self):
            return True

        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
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def getModelIndexValue(self):
            return self._modelIndex

        def getModelIndex(self):
            return self._modelIndexStr

        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()

        def getModelMimeData(self):
            mimeData = TaurusBaseWidget.getModelMimeData(self)
            mimeData.setText(self.text())
            return mimeData

        def resetModelIndex(self):
            self.setModelIndex(self.DefaultModelIndex)

        def getBgRole(self):
            return self._bgRole

        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)
            if self._bgRole in ["tango_state"]:
                _bgRoleSubscriber = TaurusBgRoleSubscriber(self)
                _bgRoleSubscriber.setModel(f"{self.modelObj.parentObj.name}/state")
            elif "parentObj." in self._bgRole:
                _attr = self._bgRole.split(".")[1]
                if "Obj" in _attr:
                    _attr = _attr.strip("Obj")
                _bgRoleSubscriber = TaurusBgRoleSubscriber(self)
                _bgRoleSubscriber.setModel(
                    f"{self.modelObj.parentObj.name}/{_attr}"
                )

            self.controllerUpdate()

        def resetBgRole(self):
            """Reset the background role to its default value"""
            self.setBgRole(self.DefaultBgRole)

        def getFgRole(self):
            """get the foreground role for this label (see :meth:`setFgRole`)"""
            return self._fgRole

        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()

        def resetFgRole(self):
            """Reset the foreground role to its default value"""
            self.setFgRole(self.DefaultFgRole)

        def getPrefixText(self):
            return self._prefix

        def setPrefixText(self, prefix):
            self._prefix = str(prefix)
            self.controllerUpdate()

        def resetPrefixText(self):
            self.setPrefixText(self.DefaultPrefix)

        def getSuffixText(self):
            return self._suffix

        def setSuffixText(self, suffix):
            self._suffix = str(suffix)
            self.controllerUpdate()

        def resetSuffixText(self):
            self.setSuffixText(self.DefaultSuffix)

        def getPermanentText(self):
            return self._permanentText

        def _setPermanentText(self, text):
            self._permanentText = text
            if text is not None:
                self.setText_(text)

        def setText_(self, text):
            """Method to expose QLabel.setText"""
            Qt.QLabel.setText(self, text)

        def setText(self, text):
            """Reimplementation of setText to set permanentText"""
            self._setPermanentText(text)

        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()

        def setDynamicTextInteractionFlags(self, flags):
            if self.hasDynamicTextInteractionFlags():
                Qt.QLabel.setTextInteractionFlags(self, flags)

        def hasDynamicTextInteractionFlags(self):
            return self._dynamicTextInteractionFlags

        def setTextInteractionFlags(self, flags):
            Qt.QLabel.setTextInteractionFlags(self, flags)
            self._dynamicTextInteractionFlags = False

        def resetTextInteractionFlags(self):
            Qt.QLabel.resetTextInteractionFlags(self)
            self.dynamicTextInteractionFlags = True

        def getAutoTrim(self):
            """
            Whether auto-trimming of the text is enabled.

            :return:
            :rtype: bool
            """
            return self._autoTrim

        def resetAutoTrim(self):
            """Reset auto-trimming to its default value"""
            self.setAutoTrim(self.DefaultAutoTrim)

        def resetFormat(self):
            """reimplement to update controller if format is changed"""
            TaurusBaseWidget.resetFormat(self)
            self.controllerUpdate()

        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

        @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()