# Source code for taurus.qt.qtgui.input.tauruslineedit

    #!/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 QLineEdit
    """
    import re
    import sys
    import numpy
    from taurus.external.qt import Qt, PYQT4, PYSIDE
    from taurus.core.units import Quantity
    from taurus.qt.qtgui.base import TaurusBaseWritableWidget
    from taurus.qt.qtgui.util import PintValidator
    from taurus.core import DataType, DataFormat, TaurusEventType

    __docformat__ = "restructuredtext"

    class TaurusValueLineEdit(Qt.QLineEdit, TaurusBaseWritableWidget):
        """
        A taurus-aware ``QLineEdit``. It will display the value (or fragment
        of the value) referenced by its model. It is a
        :class:`~taurus.qt.qtgui.base.TaurusBaseWritableWidget`
        and as such it does not apply the changes straight away to the model
        (unless ``autoApply`` is enabled), but instead shows that there are pending
        operations which can be applied by pressing "ENTER".

        When used with numerical value Attributes as its model, it provides some
        extended behaviour:

        - It represents out-of-limit values using different colours (for warning,
          range, invalid,...)
        - It uses a validator that is range-aware
        - The mouse wheel and keyboard arrows can be enabled for doing value
          increments

        .. note::
            when used with models whose value is a pint `Quantity`, the text
            is parsed by pint and therefore one can write e.g. `2 3 mm` which is
            equivalent to `6 mm` !
        """

        _bytesEncoding = sys.stdin.encoding

        def __init__(self, qt_parent=None, designMode=False):
            name = self.__class__.__name__
            self.call__init__wo_kw(Qt.QLineEdit, qt_parent)
            self.call__init__(
                TaurusBaseWritableWidget, name, designMode=designMode
            )
            self._enableWheelEvent = False
            self._wheel_delta_reminder = 0
            self._last_value = None
            self._singleStep = 1.0
            self._allow_auto_enable = True

            self.setAlignment(Qt.Qt.AlignRight)
            self.setValidator(None)

            self.textChanged.connect(self.notifyValueChanged)
            self.returnPressed.connect(self.writeValue)
            self.valueChangedSignal.connect(self.updatePendingOperations)
            self.editingFinished.connect(self._onEditingFinished)

        def _updateValidator(self, value):
            """This method sets a validator depending on the data type"""
            val = None
            if value is not None and isinstance(value.wvalue, Quantity):
                val = self.validator()
                if not isinstance(val, PintValidator):
                    val = PintValidator(self)
                    self.setValidator(val)
                attr = self.getModelObj()
                if attr is not None:
                    bottom, top = attr.range
                    if bottom != val.bottom:
                        val.setBottom(bottom)
                    if top != val.top:
                        val.setTop(top)
                units = value.wvalue.units
                if units != val.units:
                    val.setUnits(units)

            # @TODO Other validators can be configured for other types
            #       (e.g. with string lengths, tango names,...)
            else:
                self.setValidator(None)
                self.debug("Validator disabled")
            return val

        def __decimalDigits(self, fmt):
            """returns the number of decimal digits from a format string
            (or None if they are not defined)
            """
            try:
                if fmt[-1].lower() in ["f", "g"] and "." in fmt:
                    return int(fmt[:-1].split(".")[-1])
                else:
                    return None
            except Exception:
                return None

        def _onEditingFinished(self):
            """slot for performing autoapply only when edition is finished"""
            if self._autoApply:
                self.writeValue()

        # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # TaurusBaseWritableWidget overwriting
        # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def notifyValueChanged(self, *args):
            """reimplement to avoid autoapply on every partial edition"""
            self.emitValueChanged()

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

            # handle the case in which the line edit is not yet initialized
            if self._last_value is None:
                try:
                    value = self.getModelObj().read(cache=True)
                    self._updateValidator(value)
                    if value is not None:
                        self.setValue(value.wvalue)
                except Exception as e:
                    self.info("Failed attempt to initialize value: {}".format(e))

            if self._allow_auto_enable:
                # use QLineEdit.setEnabled in order to avoid changing
                # _allow_auto_enable status
                Qt.QLineEdit.setEnabled(self, evt_type != TaurusEventType.Error)

            if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic):
                self._updateValidator(evt_value)

            TaurusBaseWritableWidget.handleEvent(
                self, evt_src, evt_type, evt_value
            )

            if evt_type == TaurusEventType.Error:
                self.updateStyle()
                self._last_value = None

        def setEnabled(self, enabled):
            """Reimplement from :class:`QLineEdit` to avoid autoenabling if the
            widget is explicitly disabled (but allow auto-disabling if the
            widget is explicitly enabled)
            """
            self._allow_auto_enable = enabled
            return Qt.QLineEdit.setEnabled(self, enabled)

        def isTextValid(self):
            """
            Validates current text

            :return: Returns False if there is a validator and the current text is
                not Acceptable. Returns True otherwise.
            :rtype: bool
            """
            val = self.validator()
            if val is None:
                return True
            return val.validate(str(self.text()), 0)[0] == val.Acceptable

        def updateStyle(self):
            """Reimplemented from :class:`TaurusBaseWritableWidget`"""
            TaurusBaseWritableWidget.updateStyle(self)

            value = self.getValue()

            if value is None or not self.isTextValid() or not self.isEnabled():
                # invalid value
                color, weight = "gray", "normal"
            else:
                # check if there are pending operations
                if self.hasPendingOperations():
                    color, weight = "blue", "bold"
                else:
                    color, weight = "black", "normal"
                # also check alarms (if applicable)
                modelObj = self.getModelObj()
                if modelObj and modelObj.type in [
                    DataType.Integer,
                    DataType.Float,
                ]:
                    min_, max_ = modelObj.alarms
                    if (min_ is not None and value < min_) or (
                        max_ is not None and value > max_
                    ):
                        color = "orange"
            # apply style
            style = "TaurusValueLineEdit {{color: {}; font-weight: {}}}".format(
                color,
                weight,
            )
            self.setStyleSheet(style)

        def wheelEvent(self, evt):
            """Wheel event handler"""
            if not self.getEnableWheelEvent() or Qt.QLineEdit.isReadOnly(self):
                return Qt.QLineEdit.wheelEvent(self, evt)
            model = self.getModelObj()
            if model is None or not model.isNumeric():
                return Qt.QLineEdit.wheelEvent(self, evt)

            evt.accept()
            if PYQT4 or PYSIDE:
                self._wheel_delta_reminder += evt.delta()
            else:
                self._wheel_delta_reminder += evt.angleDelta().y()

            numSteps = self._wheel_delta_reminder // 120
            self._wheel_delta_reminder -= numSteps * 120
            self._stepBy(numSteps)

        def keyPressEvent(self, evt):
            """Key press event handler"""
            if evt.key() in (Qt.Qt.Key_Return, Qt.Qt.Key_Enter):
                Qt.QLineEdit.keyPressEvent(self, evt)
                evt.accept()
                return
            if Qt.QLineEdit.isReadOnly(self):
                return Qt.QLineEdit.keyPressEvent(self, evt)
            model = self.getModelObj()
            if model is None or not model.isNumeric():
                return Qt.QLineEdit.keyPressEvent(self, evt)

            if evt.key() == Qt.Qt.Key_Up:
                numSteps = 1
            elif evt.key() == Qt.Qt.Key_Down:
                numSteps = -1
            else:
                return Qt.QLineEdit.keyPressEvent(self, evt)

            evt.accept()
            self._stepBy(numSteps)

        def _stepBy(self, steps):
            value = self.getValue()
            self.setValue(value + Quantity(steps * self._singleStep, value.units))

            if self.getAutoApply():
                self.editingFinished.emit()
            else:
                kmods = Qt.QCoreApplication.instance().keyboardModifiers()
                controlpressed = bool(kmods & Qt.Qt.ControlModifier)
                if controlpressed:
                    self.writeValue(forceApply=True)

        def setValue(self, v):
            """Set the displayed text from a given value object"""
            # Support displaying the value without units (enabled by fragment)
            # Other fragments are ignored by setValue
            if self.modelFragmentName == "wvalue.magnitude":
                try:
                    validator = self.validator()
                    if validator is None:
                        value = self.getModelValueObj()
                        validator = self._updateValidator(value)
                    units = validator.units
                    v = v.to(units).magnitude
                except Exception as e:
                    self.debug("Cannot enforce fragment. Reason: {}".format(e))
            self._last_value = v
            self.setText(str(self.displayValue(v)).strip())

        def getValue(self):
            text = self.text()
            model_obj = self.getModelObj()
            if model_obj is None:
                return None
            val = self.validator()
            try:
                model_type = model_obj.type
                model_format = model_obj.data_format
                if model_type in [DataType.Integer, DataType.Float]:
                    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    # workaround for
                    # https://github.com/hgrecco/pint/issues/614
                    # see it also in PintValidator.validate()
                    if re.match(r"^[\+\-]?\s*0+[1-9]+[^.]*$", text.strip()):
                        return None
                    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    try:
                        q = Quantity(text)
                        # allow implicit units (assume wvalue.units implicitly)
                        if q.unitless:
                            q = Quantity(q.magnitude, val.units)
                        return q
                    except Exception as e:
                        self.debug('Problem with input "{}": {}'.format(text, e))
                        return None
                elif model_type == DataType.Boolean:
                    if model_format == DataFormat._0D:
                        return bool(int(eval(text)))
                    else:
                        return numpy.array(eval(text), dtype=int).astype(bool)
                elif model_type == DataType.String:
                    if model_format == DataFormat._0D:
                        return str(text)
                    else:
                        return numpy.array(eval(text), dtype=str).tolist()
                elif model_type == DataType.Bytes:
                    return bytes(text, self._bytesEncoding)
                else:
                    raise TypeError(
                        'Unsupported model type "{}"'.format(model_type)
                    )
            except Exception as e:
                msg = 'Cannot return value for "{}". Reason: {}'.format(text, e)
                if text in (str(None), self.getNoneValue()):
                    self.debug(msg)
                else:
                    self.warning(msg)
                return None

        def setEnableWheelEvent(self, b):
            self._enableWheelEvent = b

        def getEnableWheelEvent(self):
            return self._enableWheelEvent

        def resetEnableWheelEvent(self):
            self.setEnableWheelEvent(False)

        def getSingleStep(self):
            return self._singleStep

        def setSingleStep(self, step):
            self._singleStep = step

        def resetSingleStep(self):
            self.setSingleStep(1.0)

        @classmethod
        def getQtDesignerPluginInfo(cls):
            ret = TaurusBaseWritableWidget.getQtDesignerPluginInfo()
            ret["module"] = "taurus.qt.qtgui.input"
            ret["icon"] = "designer:lineedit.png"
            return ret

        # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # QT properties
        # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        model = Qt.pyqtProperty(
            "QString",
            TaurusBaseWritableWidget.getModel,
            TaurusBaseWritableWidget.setModel,
            TaurusBaseWritableWidget.resetModel,
        )

        #: (deprecated))
        useParentModel = Qt.pyqtProperty(
            "bool",
            TaurusBaseWritableWidget.getUseParentModel,
            TaurusBaseWritableWidget.setUseParentModel,
            TaurusBaseWritableWidget.resetUseParentModel,
        )

        autoApply = Qt.pyqtProperty(
            "bool",
            TaurusBaseWritableWidget.getAutoApply,
            TaurusBaseWritableWidget.setAutoApply,
            TaurusBaseWritableWidget.resetAutoApply,
        )

        forcedApply = Qt.pyqtProperty(
            "bool",
            TaurusBaseWritableWidget.getForcedApply,
            TaurusBaseWritableWidget.setForcedApply,
            TaurusBaseWritableWidget.resetForcedApply,
        )

        enableWheelEvent = Qt.pyqtProperty(
            "bool", getEnableWheelEvent, setEnableWheelEvent, resetEnableWheelEvent
        )

    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 lineedit demo",
                app_version="1.0",
                org_domain="Taurus",
                org_name="Tango community",
            )

        args = app.get_command_line_args()

        form = Qt.QWidget()
        layout = Qt.QVBoxLayout()
        form.setLayout(layout)
        if len(args) == 0:
            models = ["sys/tg_test/1/double_scalar", "sys/tg_test/1/double_scalar"]
        else:
            models = args
        for model in models:
            w = TaurusValueLineEdit()
            w.setModel(model)
            layout.addWidget(w)
        form.resize(300, 50)
        form.show()

        if owns_app:
            sys.exit(app.exec_())
        else:
            return form

    if __name__ == "__main__":
        sys.exit(main())