# Source code for taurus.qt.qtgui.base.taurusbase

    #!/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 the set of base classes from which the Qt taurus
    widgets should inherit to be considered valid taurus widgets. """

    import sys
    import copy
    import inspect
    import threading
    import pkg_resources
    from types import MethodType

    from taurus.external.qt import Qt
    from enum import Enum

    import taurus
    from taurus.core.util import eventfilters
    from taurus.core.util.timer import Timer
    from taurus.core.taurusbasetypes import TaurusElementType, TaurusEventType
    from taurus.core.taurusattribute import TaurusAttribute
    from taurus.core.taurusdevice import TaurusDevice
    from taurus.core.taurusconfiguration import TaurusConfigurationProxy
    from taurus.core.tauruslistener import TaurusListener, TaurusExceptionListener
    from taurus.core.taurusoperation import WriteAttrOperation
    from taurus.core.util.eventfilters import filterEvent
    from taurus.core.util.log import deprecation_decorator
    from taurus.qt.qtcore.util import baseSignal
    from taurus.qt.qtcore.configuration import BaseConfigurableClass
    from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE
    from taurus.qt.qtcore.mimetypes import TAURUS_DEV_MIME_TYPE
    from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_MIME_TYPE
    from taurus.qt.qtgui.util import ActionFactory

    from taurus.core.units import Quantity

    __docformat__ = "restructuredtext"

    DefaultNoneValue = "-----"

    class _DEFAULT:
        """A default"""

        pass

    class _ModelType(Enum):
        MLIST = 0

    #: MLIST is a constant that can be passed as the value of the `key`
    #: kwarg in the multi-model API to indicate that the class should act as a
    #: model-container regarding to this key (e.g.
    #: `TaurusBaseComponent.setModel(m, key=MLIST)` will treat `m` as
    #: a *list* of models.
    MLIST = _ModelType.MLIST

    def defaultFormatter(dtype=None, basecomponent=None, **kwargs):
        """
        Default formatter callable. Returns a format string based on dtype
        and the mapping provided by :attr:`TaurusBaseComponent.defaultFormatDict`

        :param dtype: data type
        :type dtype: object
        :param basecomponent: widget whose display is to be formatted
        :type basecomponent: object
        :param kwargs: other keyword arguments (ignored)
        :return: The format string corresponding to the given dtype.
        :rtype: str
        """
        if issubclass(dtype, Enum):
            dtype = Enum
        return basecomponent.defaultFormatDict.get(dtype, "{0}")

    expFormatter = "{:2.3e}"
    floatFormatter = "{:.5f}"
    kkFormatter = "{:.7g}"

    def kkFormatter2(*a, **kw):
        return {}

    class TaurusBaseComponent(TaurusListener, BaseConfigurableClass):
        """A generic Taurus component.

        .. note::
            Any class which inherits from TaurusBaseComponent is expected to also
            inherit from QObject (or from a QObject derived class).
        .. note::
            :meth:`getSignaller` is now unused and deprecated. This is because
            `taurusEvent` is implemented using :func:`baseSignal`, that doesn't
            require the class to inherit from QObject.
        """

        _modifiableByUser = False
        _showQuality = True
        _eventBufferPeriod = 0

        # Python format string or Formatter callable
        # (None means that the default formatter will be used)
        FORMAT = None

        # Dictionary mapping dtypes to format strings
        defaultFormatDict = {
            float: "{:.{bc.modelObj.precision}f}",
            Enum: "{0.name}",
            Quantity: "{:~.{bc.modelObj.precision}f}",
        }

        taurusEvent = baseSignal("taurusEvent", object, object, object)
        # accepted model keys
        modelKeys = [""]

        #: MLIST is a constant that can be passed as the value of the `key`
        #: kwarg in the multi-model API to indicate that the class should act as a
        #: model-container regarding to this key (e.g.
        #: `TaurusBaseComponent.setModel(m, key=MLIST)` will treat `m` as
        #: a *list* of models.
        MLIST = _ModelType.MLIST

        def __init__(self, name="", parent=None, designMode=False, **kwargs):
            """Initialization of TaurusBaseComponent"""
            self._modelObj = dict([(k, None) for k in self.modelKeys])
            self._modelName = dict([(k, "") for k in self.modelKeys])
            self._modelFragmentName = dict([(k, None) for k in self.modelKeys])
            self.noneValue = DefaultNoneValue
            self._designMode = designMode
            self.call__init__(TaurusListener, name, parent)

            BaseConfigurableClass.__init__(self)

            # --------------------------------------------------------------
            # Deprecated API for context menu
            self.taurusMenu = None  # deprecated since 4.5.3a. Do not use
            self.taurusMenuData = ""  # deprecated since 4.5.3a. Do not use
            self.__explicitPopupMenu = False
            # --------------------------------------------------------------

            # attributes storing property values
            self._format = None
            self._localModelName = dict([(k, "") for k in self.modelKeys])
            self._useParentModel = False
            self._showText = True
            self._attached = dict([(k, False) for k in self.modelKeys])
            self._dangerMessage = ""
            self._isDangerous = False
            self._forceDangerousOperations = False
            self._eventFilters = []
            self._preFilters = []
            self._isPaused = False
            self._operations = []
            self._modelInConfig = False
            self._autoProtectOperation = True

            self._bufferedEvents = {}
            self._bufferedEventsTimer = None
            self.setEventBufferPeriod(self._eventBufferPeriod)

            if parent is not None and hasattr(parent, "_exception_listener"):
                self._exception_listener = parent._exception_listener
            else:
                self._exception_listener = set([TaurusExceptionListener()])

            # Use default formatter if none has been set by the class
            if self.FORMAT is None:
                self.setFormat(
                    getattr(
                        taurus.tauruscustomsettings,
                        "DEFAULT_FORMATTER",
                        defaultFormatter,
                    )
                )

            # register configurable properties
            self.registerConfigProperty(
                self.isModifiableByUser,
                self.setModifiableByUser,
                "modifiableByUser",
            )
            self.registerConfigProperty(
                self.getModelInConfig, self.setModelInConfig, "ModelInConfig"
            )
            self.registerConfigProperty(
                self.getFormat, self.setFormat, "formatter"
            )
            self.resetModelInConfig()

            # connect taurusEvent signal to filterEvent
            try:
                self.taurusEvent.connect(self.filterEvent)
            except Exception as e:
                self.warning("Could not connect taurusEvent signal: %r", e)

        def _addModelKey(self, key):
            """Add a supported key, updating all involved members"""
            if key not in self.modelKeys:
                self.modelKeys.append(key)
            if key not in self._attached:
                self._attached[key] = False
            if key not in self._modelName:
                self._modelName[key] = ""
            if key not in self._localModelName:
                self._localModelName[key] = ""
            if key not in self._modelObj:
                self._modelObj[key] = None
            if key not in self._modelFragmentName:
                self._modelFragmentName[key] = None

        def _removeModelKey(self, key):
            """Remove a supported key, updating all involved members"""
            if key in self.modelKeys:
                self.modelKeys.remove(key)
            self._attached.pop(key, None)
            self._modelName.pop(key, None)
            self._localModelName.pop(key, None)
            self._modelObj.pop(key, None)
            self._modelFragmentName.pop(key, None)

        def _wrap_old_api(self, method, *a, **kw):
            """Calls method with `*a` and `**kw` (after removing the `key` from
            `kw` if method does not accept it and key is the default one).

            Note that `kw` must contain `"key"`
            """
            key = self._checkModelKey(kw["key"])  # key must be in *kw
            s = inspect.getfullargspec(method)
            _supports_key = s.varkw is not None or "key" in s.args + s.kwonlyargs
            if not _supports_key and key == self.modelKeys[0]:
                kw.pop("key")
                self.deprecated(
                    rel="5.1.0",
                    dep="{0} reimplemented without 'key' support".format(
                        method.__qualname__
                    ),
                )
            return method(*a, **kw)

        def _checkModelKey(self, key):
            """Check that the given model key is accepted. If key is _DEFAULT,
            self.modelKeys[0] is returned.

            :param key: a model key
            :type key: hashable object
            :return: key
            :raises KeyError: if the model key is not valid for this object
            """
            if key in self.modelKeys:
                return key
            if key is _DEFAULT:
                return self.modelKeys[0]
            raise KeyError(
                "Unsupported model key {} for {}. Valid keys: {}".format(
                    key, self, self.modelKeys
                )
            )

        @property
        def modelObj(self):
            return self._modelObj[self.modelKeys[0]]

        @modelObj.setter
        def modelObj(self, value):
            self._modelObj[self.modelKeys[0]] = value

        @property
        def modelName(self):
            return self._modelName[self.modelKeys[0]]

        @modelName.setter
        def modelName(self, value):
            self._modelName[self.modelKeys[0]] = value

        @property
        def modelFragmentName(self):
            return self._modelFragmentName[self.modelKeys[0]]

        @modelFragmentName.setter
        def modelFragmentName(self, value):
            self._modelFragmentName[self.modelKeys[0]] = value

        @staticmethod
        def get_registered_formatters():
            """Static method to get the registered formatters
            :return: dict of registered formatter (keys are the registered names)
            :rtype: dict
            """
            # Note: this is an experimental feature introduced in v 4.6.4a
            # It may be removed or changed in future releases

            formatters = {}
            for ep in pkg_resources.iter_entry_points("taurus.qt.formatters"):
                try:
                    formatters[ep.name] = ep.load()
                except Exception as e:
                    taurus.warning(
                        'Cannot load "%s" formatter. Reason: %r', ep.name, e
                    )

            return formatters

        @deprecation_decorator(rel="4.0")
        def getSignaller(self):
            return self

        def deleteLater(self):
            """Reimplements the Qt.QObject deleteLater method to ensure that the
            this object stops listening its model.
            """
            self._setUseParentModel(False)
            self.resetModel()
            Qt.QObject.deleteLater(self)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Utility methods
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def getTaurusManager(self):
            """Returns the taurus manager singleton. This is just a helper method.
            It is the equivalent of doing::

                import taurus
                manager = taurus.Manager()

            :return: the TaurusManager
            :rtype: taurus.core.taurusmanager.TaurusManager
            """
            return taurus.Manager()

        def getTaurusFactory(self, scheme=""):
            """Returns the taurus factory singleton for the given scheme.
            This is just a helper method. It is the equivalent of doing::

                import taurus
                factory = taurus.Factory(scheme)

            :param scheme: the scheme. If scheme is an empty string, or is not
                passed, the scheme will be obtained from the model name. For
                backwards compatibility (but deprecated), passing None is
                equivalent to 'tango'.
            :type scheme: str or None
            :return: the TaurusFactory
            :rtype: taurus.core.taurusfactory.TaurusFactory
            """
            if scheme == "":
                scheme = taurus.getSchemeFromName(self.getModelName() or "")
            if scheme is None:
                scheme = "tango"

            return taurus.Factory(scheme)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Context menu behavior
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Mandatory methods to be implemented in subclass implementation
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def updateStyle(self):
            """Method called when the component detects an event that triggers a
            change in the style.
            Default implementation doesn't do anything. Overwrite when necessary
            """
            pass

        @deprecation_decorator(rel="4.3.2")
        def getParentTaurusComponent(self):
            return self._getParentTaurusComponent()

        def _getParentTaurusComponent(self):
            """Returns a parent Taurus component or None if no parent
            :class:`taurus.qt.qtgui.base.TaurusBaseComponent` is found.

            .. note:: the parentModel API does not support multi-models

            :raises: RuntimeError
            """
            raise RuntimeError(
                "Not allowed to call "
                + "TaurusBaseComponent::getParentTaurusComponent()"
            )

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Event handling chain
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def setEventBufferPeriod(self, period):
            """Set the period at wich :meth:`fireBufferedEvents` will be called.
            If period is 0, the event buffering is disabled (i.e., events are fired
            as soon as they are received)

            :param period: period in seconds for the automatic event firing.
                period=0 will disable the event buffering.
            :type period: float
            """
            self._eventBufferPeriod = period
            if period == 0:
                if self._bufferedEventsTimer is not None:
                    self._bufferedEventsTimer.stop()
                    self._bufferedEventsTimer = None
                    self.fireBufferedEvents()  # flush the buffer
            else:
                self._eventsBufferLock = threading.RLock()
                self._bufferedEventsTimer = Timer(
                    period, self.fireBufferedEvents, self
                )
                self._bufferedEventsTimer.start()

        def getEventBufferPeriod(self):
            """Returns the event buffer period

            :return: period (in s). 0 means event buffering is disabled.
            :rtype: float
            """
            return self._eventBufferPeriod

        def eventReceived(self, evt_src, evt_type, evt_value):
            """The basic implementation of the event handling chain is as
            follows:

                - eventReceived just calls :meth:`fireEvent` which emits a
                  "taurusEvent" PyQt signal that is connected
                  (by :meth:`preAttach`)
                  to the :meth:`filterEvent` method.
                - After filtering, :meth:`handleEvent` is invoked with the
                  resulting filtered event

            .. note::
                in the earlier steps of the chain (i.e., in :meth:`eventReceived`/
                :meth:`fireEvent`), the code is executed in a Python thread, while
                from eventFilter ahead, the code is executed in a Qt thread.
                When writing widgets, one should normally work on the Qt thread
                (i.e. reimplementing :meth:`handleEvent`)

            :param evt_src: object that triggered the event
            :type evt_src: object
            :param evt_type: type of event
            :type evt_type: taurus.core.taurusbasetypes.TaurusEventType
            :param evt_value: event value
            :type evt_value: object
            """
            evt = filterEvent(
                evt_src, evt_type, evt_value, filters=self._preFilters
            )
            if evt is not None:
                self.fireEvent(*evt)

        def fireEvent(self, evt_src=None, evt_type=None, evt_value=None):
            """Emits a "taurusEvent" signal.
            It is unlikely that you need to reimplement this method in subclasses.
            Consider reimplementing :meth:`eventReceived` or :meth:`handleEvent`
            instead depending on whether you need to execute code in the python
            or Qt threads, respectively

            :param evt_src: object that triggered the event
            :type evt_src: object or None
            :param evt_type:  type of event
            :type evt_type: taurus.core.taurusbasetypes.TaurusEventType or None
            :param evt_value: event value
            :type evt_value: object or None
            """
            if self._eventBufferPeriod:
                # If we have an active event buffer delay, store the event...
                with self._eventsBufferLock:
                    self._bufferedEvents[(evt_src, evt_type)] = (
                        evt_src,
                        evt_type,
                        evt_value,
                    )
            else:
                # if we are not buffering, directly emit the signal
                try:
                    self.taurusEvent.emit(evt_src, evt_type, evt_value)
                except Exception:
                    pass  # self.error('%s.fireEvent(...) failed!'%type(self))

        def fireBufferedEvents(self):
            """Fire all events currently buffered (and flush the buffer)

            Note: this method is normally called from an event buffer timer thread
                  but it can also be called any time the buffer needs to be flushed
            """
            with self._eventsBufferLock:
                for evt in self._bufferedEvents.values():
                    self.taurusEvent.emit(*evt)
                self._bufferedEvents = {}

        def filterEvent(self, evt_src=-1, evt_type=-1, evt_value=-1):
            """The event is processed by each and all filters in strict order
            unless one of them returns None (in which case the event is discarded)

            :param evt_src: object that triggered the event
            :type evt_src: object
            :param evt_type: type of event
            :type evt_type: taurus.core.taurusbasetypes.TaurusEventType
            :param evt_value: event value
            :type evt_value: object
            """
            evt = evt_src, evt_type, evt_value

            if evt == (-1, -1, -1):
                # @todo In an ideal world the signature of this method should be
                # (evt_src, evt_type, evt_value). However there's a bug in PyQt:
                # If a signal is disconnected between the moment it is emitted and
                # the moment the slot is called, then the slot is called without
                # parameters (!?). We added this default values to detect if
                # this is the case without printing an error message each time.
                # If this gets fixed, we should remove this line.
                return

            evt = filterEvent(*evt, filters=self._eventFilters)
            if evt is not None:
                self.handleEvent(*evt)

        def handleEvent(self, evt_src, evt_type, evt_value):
            """Event handling. Default implementation does nothing.
            Reimplement as necessary

            :param evt_src: object that triggered the event
            :type evt_src: object or None
            :param evt_type: type of event
            :type evt_type: taurus.core.taurusbasetypes.TaurusEventType or None
            :param evt_value: event value
            :type evt_value: object or None
            """
            pass

        def setEventFilters(self, filters=None, preqt=False):
            """sets the taurus event filters list. The filters are run in order,
            using each output to feed the next filter. A filter must be a function
            that accepts 3 arguments ``(evt_src, evt_type, evt_value)`` If the
            event is to be ignored, the filter must return None. If the event is
            not to be ignored, filter must return a ``(evt_src, evt_type,
            evt_value)`` tuple which may (or not) differ from the input.

            For a library of common filters, see taurus/core/util/eventfilters.py

            :param filters: a sequence of filters
            :type filters: sequence
            :param preqt: If true, set the pre-filters (that are applied in
                eventReceived, at the python thread), otherwise, set the filters to
                be applied at the main Qt thread (default)
            :type preqt: bool

            *Note*: If you are setting a filter that applies a transformation on
            the parameters, you may want to generate a fake event to force the last
            value to be filtered as well. This can be done as in this example::

                TaurusBaseComponent.fireEvent( TaurusBaseComponent.getModelObj(),
                    taurus.core.taurusbasetypes.TaurusEventType.Periodic,
                    TaurusBaseComponent.getModelObj().getValueObj())

            See also: insertEventFilter
            """
            if filters is None:
                filters = []
            if preqt:
                self._preFilters = list(filters)
            else:
                self._eventFilters = list(filters)

        def getEventFilters(self, preqt=False):
            """Returns the list of event filters for this widget

            :param preqt: If true, return the pre-filters (that are applied in
                eventReceived, at the python thread), otherwise, return the filters
                to be applied at the main Qt thread (default)
            :type preqt: bool
            :return: the event filters
            :rtype: sequence<callable>
            """
            return self._preFilters if preqt else self._eventFilters

        def insertEventFilter(self, filter, index=-1, preqt=False):
            """insert a filter in a given position

            :param filter: ) a filter
            :type filter: callable(evt_src, evt_type, evt_value
            :param index: index to place the filter (default = -1 meaning place at
                the end)
            :type index: int
            :param preqt: If true, set the pre-filters (that are applied in
                eventReceived, at the python thread), otherwise, set the filters to
                be applied at the main Qt thread (default)
            :type preqt: bool

            See also: setEventFilters
            """
            if preqt:
                self._preFilters.insert(index, filter)
            else:
                self._eventFilters.insert(index, filter)

        def setPaused(self, paused=True):
            """Toggles the pause mode.

            :param paused: whether or not to pause (default = True)
            :type paused: bool
            """
            if paused == self._isPaused:
                return  # nothing to do
            if paused:  # pausing
                self.insertEventFilter(eventfilters.IGNORE_ALL, 0)
                self.debug("paused")
            else:  # unpausing
                try:
                    self._eventFilters.remove(eventfilters.IGNORE_ALL)
                    self.debug("Unpaused")
                except ValueError:
                    self.warning("Unpause failed")
            self._isPaused = paused

        def isPaused(self):
            """Return the current pause state

            :return: wheater or not the widget is paused
            :rtype: bool
            """
            return self._isPaused

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Model class methods
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def getModelClass(self, *, key=_DEFAULT):
            """Return the class object for the widget.
            Default behavior is to do a 'best effort' to determine which model
            type corresponds to the current model name.
            Overwrite as necessary.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: The class object corresponding to the type of Taurus model
                this widget handles or None if no valid class is found.
            :rtype: class TaurusModel or None
            """
            if self.__class__.findModelClass is TaurusBaseComponent.findModelClass:
                # findModelClass is not reimplemented so use the new API
                return self._findModelClass(key=key)
            return self._wrap_old_api(self.findModelClass, key=key)

        @deprecation_decorator(rel="5.1.0", alt="getModelClass")
        def findModelClass(self):
            return self._findModelClass()

        def _findModelClass(self, *, key=_DEFAULT):
            """Do a "best effort" to determine which model type corresponds to the
            given model name.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: The class object corresponding to the type of Taurus model
                this widget handles or None if no valid class is found.
            :rtype: class TaurusModel or None
            """
            key = self._checkModelKey(key)
            if key is MLIST:
                return None
            model = self._wrap_old_api(self.getModel, key=key)
            if self._getUseParentModel():
                return self._findRelativeModelClass(model)
            else:
                return self._findAbsoluteModelClass(model)

        def _findAbsoluteModelClass(self, absolute_name):
            return taurus.Manager().findObjectClass(absolute_name)

        def _findRelativeModelClass(self, relative_name):
            parent_widget = self._getParentTaurusComponent()
            if parent_widget is None:
                return None

            parent_obj = parent_widget.getModelObj()
            if parent_obj is None:
                return None

            if relative_name is None or len(relative_name) == 0:
                return parent_widget.getModelClass()

            obj = parent_obj.getChildObj(relative_name)
            if obj is None:
                return None
            if isinstance(obj, TaurusConfigurationProxy):
                return obj.getRealConfigClass()
            else:
                return obj.__class__

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Model related methods
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        @deprecation_decorator(rel="5.1.0")
        def setModelName(self, modelName, parent=None, *, key=_DEFAULT):
            return self._setModelName(modelName, parent=parent, key=key)

        def _setModelName(self, modelName, parent=None, *, key=_DEFAULT):
            """This method will detach from the previous taurus model (if any), it
            will set the new model to the given modelName and it will attach
            this component to the new taurus model.

            :param modelName: the new taurus model name (according to the taurus
                convention)
            :type modelName: str
            :param parent: the parent or None (default) if this component does not
                have a parent Taurus component
            :type parent: TaurusBaseComponent
            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            key = self._checkModelKey(key)
            modelName = str(modelName)
            if parent:
                modelClass = self._wrap_old_api(self.getModelClass, key=key)
                if modelClass is not None:
                    parent_model = self._getParentModelObj()
                    modelName = modelClass.buildModelName(parent_model, modelName)
            self._wrap_old_api(self._detach, key=key)
            self._modelName[key] = modelName
            # update modelFragmentName
            try:
                scheme = self.getTaurusManager().getScheme(modelName)
                factory = taurus.Factory(scheme)
                v = factory.getAttributeNameValidator()
                self._modelFragmentName[key] = v.getUriGroups(
                    self._modelName[key]
                )["fragment"]
            except (AttributeError, TypeError):
                self._modelFragmentName[key] = None
            self._wrap_old_api(self._attach, key=key)

        def getModelName(self, *, key=_DEFAULT):
            """Returns the current model name.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: the model name
            :rtype: str
            """
            key = self._checkModelKey(key)
            return self._modelName[key]

        def getFullModelName(self, *, key=_DEFAULT):
            """Returns the full name of the model object for the given key.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: the model name
            :rtype: str
            """
            key = self._checkModelKey(key)
            if key is MLIST:
                return None
            obj = self._wrap_old_api(self.getModelObj, key=key)
            if obj is None:
                return None
            return obj.getFullName()

        def getModelNames(self):
            """Returns a dict of key:modelname

            :return: dictionary mapping keys to model names
            :rtype: dict
            """
            return copy.copy(self._modelName)

        @deprecation_decorator(rel="4.3.2")
        def getParentModelName(self):
            return self._getParentModelName()

        def _getParentModelName(self):
            """Returns the parent model name or an empty string if the component
            has no parent

            :return: the parent model name
            :rtype: str
            """
            try:
                p = self._getParentTaurusComponent()
                if p is None:
                    return ""
            except Exception:
                return ""
            return p.getModelName()

        @deprecation_decorator(rel="4.3.2")
        def getParentModelObj(self):
            return self._getParentModelObj()

        def _getParentModelObj(self):
            """Returns the parent model object or None if the component has no
            parent or if the parent's model is None

            .. note:: the parentModel API does not support multi-models

            :return: the parent taurus model object
            :rtype: taurus.core.taurusmodel.TaurusModel or None
            """
            try:
                p = self._getParentTaurusComponent()
                if p is None:
                    return None
            except Exception:
                return None
            return p.getModelObj()

        def getModelObj(self, *, key=_DEFAULT):
            """Returns the taurus model object associated to the given key or None
            if no taurus model is associated.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: the taurus model object
            :rtype: taurus.core.taurusmodel.TaurusModel or None
            """
            key = self._checkModelKey(key)
            return self._modelObj[key]

        def getModelType(self, *, key=_DEFAULT):
            """Returns the taurus model type associated with this key or
            taurus.core.taurusbasetypes.TaurusElementType.Unknown if no taurus
            model is associated.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: the taurus model type
            :rtype: taurus.core.taurusbasetypes.TaurusElementType
            """
            model_obj = self._wrap_old_api(self.getModelObj, key=key)
            if model_obj is None:
                return TaurusElementType.Unknown
            return model_obj.getTaurusElementType()

        def getModelValueObj(self, cache=True, *, key=_DEFAULT):
            """Returns the current value of the taurus  model associated with this
            key or None if no taurus model is associated.

            :param cache: if set to True (default) use the cache value. If set to
                False will force a connection to the server.
            :type cache: bool
            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: the tango value object.
            :rtype: TaurusAttrValue
            """
            model_obj = self._wrap_old_api(self.getModelObj, key=key)
            if model_obj is None:
                return None
            return model_obj.getValueObj(cache=cache)

        def getModelFragmentObj(self, fragmentName=None, *, key=_DEFAULT):
            """Returns a fragment object of the model. A fragment of a model is a
            python attribute of the model object.

            Fragment names including dots will be used to recursively get fragments
            of fragments.

            For a simple fragmentName (no dots), this is roughly equivalent to
            getattr(self.getModelObj(), fragmentName)

            If the model does not have that fragment, :class:`AttributeError` is
            raised (other exceptions may be raised when accessing the fragment as
            well)

            :param fragmentName: the returned value will correspond to the given
                fragmentName. If None passed, self.modelFragmentName will be used,
                and if None is set, the defaultFragmentName of the model will be
                used instead.
            :type fragmentName: str or None
            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: the member of the modelObj referred by the fragment.
            :rtype: obj
            """
            key = self._checkModelKey(key)
            if fragmentName is None:  # no fragmentName passed in kwargs
                fragmentName = self._modelFragmentName[key]
            if key is MLIST:
                return None
            return self._modelObj[key].getFragmentObj(fragmentName)

        def getModelIndexValue(self):
            """
            Called inside getDisplayValue to use with spectrum attributes.
            By default not used, but some widget might want to support this
            feature.

            .. note::
                getModelIndexValue() is only supported for the default model key

            Override when needed.
            """
            return None

        def getFormatedToolTip(self, cache=True):
            """Returns a string with contents to be displayed in a tooltip.

            :param cache: if set to True (default) use the cache value. If set to
                False will force a connection to the server.
            :type cache: bool
            :return: a tooltip
            :rtype: str
            """
            if self.modelObj is None:
                return self.getNoneValue()
            obj = self.modelObj.getDisplayDescrObj()
            return self.toolTipObjToStr(obj)

        def toolTipObjToStr(self, toolTipObj):
            """Converts a python dict to a tooltip string.

            :param toolTipObj: a python object
            :type toolTipObj: dict
            :return: a tooltip
            :rtype: str
            """
            if toolTipObj is None:
                return self.getNoneValue()
            ret = '<TABLE width="500" border="0" cellpadding="1" cellspacing="0">'

            for id, value in toolTipObj:
                ret += (
                    '<TR><TD WIDTH="80" ALIGN="RIGHT" VALIGN="MIDDLE">'
                    + "<B>%s:</B></TD><TD>%s</TD></TR>"
                ) % (id.capitalize(), value)
            ret += "</TABLE>"
            return ret

        def displayValue(self, v):
            """
            Returns a string representation of the given value

            This method will use a format string which is determined
            dynamically from :attr:`FORMAT`.

            By default `TaurusBaseComponent.FORMAT` is set to
            :func:`defaultFormatter`, which makes use of
            :attr:`defaultFormatDict`.

            In order to customize the formatting behaviour, one can
            use :meth:`setFormat` to alter the formatter of an specific instance
            (recommended) or change :attr:`defaultFormatDict` or
            :attr:`FORMAT` directly at class level.

            The formatter can be set to a python format string [1] or a callable
            that returns a python format string.
            If a callable is used, it will be called with the following
            keyword arguments:

            - dtype: the data type of the value to be formatted
            - basecomponent: the affected widget

            The following are some examples for customizing the formatting::

            - Change the format for widget instance `foo`::

                foo.setFormat("{:.2e}")

            - Change FORMAT for all widgets (using a string)::

                TaurusBaseComponent.FORMAT = "{:.2e}"

            - Change FORMAT for all TaurusLabels (using a callable)::

                def baseFormatter(dtype=None, basecomponent=None, **kwargs):
                    return "{:.1f}"

                TaurusLabel.FORMAT = baseFormatter

            - Use the defaultFormatDict but modify the format string for
              dtype=str::

                TaurusLabel.defaultFormatDict.update({"str": "{!r}"})

            .. seealso:: :attr:`tauruscustomsettings.DEFAULT_FORMATTER`,
               `--default-formatter` option in :class:`TaurusApplication`,
               :meth:`TaurusBaseWidget.onSetFormatter`

            [1] https://docs.python.org/2/library/string.html

            :param v: the value to be translated to string
            :type v: object
            :return: a string representing the given value
            :rtype: str
            """
            if self._format is None:
                try:
                    self._updateFormat(type(v))
                except Exception as e:
                    self.warning(
                        (
                            "Cannot update format. Reverting to default."
                            + " Reason: %r"
                        ),
                        e,
                    )
                    self.setFormat(defaultFormatter)
            try:
                fmt_v = self._format.format(v, bc=self)
            except Exception:
                self.debug(
                    "Invalid format %r for %r. Using '{0}'", self._format, v
                )
                fmt_v = "{0}".format(v)

            return fmt_v

        def _updateFormat(self, dtype, **kwargs):
            """Method to update the internal format string used by
            :meth:`displayValue`
            The internal format string is calculated using :attribute:`FORMAT`,
            which can be a string or a callable that returns a string
            (see :meth:`displayValue`).

            :param dtype: data type
            :type dtype: object
            :param kwargs: keyword arguments that will be passed to
                :attribute:`FORMAT` if it is a callable
            """
            if self.FORMAT is None or isinstance(self.FORMAT, str):
                self._format = self.FORMAT
            else:
                # unbound method to callable
                if isinstance(self.FORMAT, MethodType):
                    self.FORMAT = self.FORMAT.__func__
                self._format = self.FORMAT(
                    dtype=dtype, basecomponent=self, **kwargs
                )

        def setFormat(self, format):
            """Method to set the `FORMAT` attribute for this instance.
            It also resets the internal format string, which will be recalculated
            in the next call to :meth:`displayValue`

            :param format: A format string or a formatter callable (or the callable
                name in "full.module.callable" format)
            :type format: str or callable
            """
            # Check if the format is a callable string representation
            if isinstance(format, str):
                try:
                    moduleName, formatterName = format.rsplit(".", 1)
                    __import__(moduleName)
                    module = sys.modules[moduleName]
                    format = getattr(module, formatterName)
                except Exception:
                    format = str(format)
            self.FORMAT = format
            self.resetFormat()

        def getFormat(self):
            """Method to get the `FORMAT` attribute for this instance.

            :return: a string of the current format. It could be a python format
                string or a callable string representation.
            :rtype: str
            """
            if isinstance(self.FORMAT, str):
                formatter = self.FORMAT
            else:
                formatter = "{0}.{1}".format(
                    self.FORMAT.__module__, self.FORMAT.__name__
                )
            return formatter

        def resetFormat(self):
            """Reset the internal format string. It forces a recalculation
            in the next call to :meth:`displayValue`.
            """
            self._format = None

        def getDisplayValue(self, cache=True, fragmentName=None, *, key=_DEFAULT):
            """Returns a string representation of the model value associated with
            this component.

            :param cache: (ignored, just for bck-compat).
            :type cache: bool
            :param fragmentName: the returned value will correspond to the given
                fragmentName. If None passed, self.modelFragmentName will be used,
                and if None is set, the defaultFragmentName of the model will be
                used instead.
            :type fragmentName: str or None
            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: a string representation of the model value.
            :rtype: str
            """
            key = self._checkModelKey(key)
            if self._modelObj[key] is None:
                return self.getNoneValue()
            try:
                v = self._wrap_old_api(
                    self.getModelFragmentObj, fragmentName=fragmentName, key=key
                )
            except Exception:
                return self.getNoneValue()

            # Note: the getModelIndexValue API is only supported for the default
            # model key
            idx = self.getModelIndexValue()
            if v is not None and idx:
                try:
                    for i in idx:
                        v = v[i]
                except Exception as e:
                    self.debug("Problem with applying model index: %r", e)
                    return self.getNoneValue()
            return self.displayValue(v)

        def setNoneValue(self, v):
            """Sets the new string representation when no model or no model value
            exists.

            :param v: the string representation for an invalid value
            :type v: str
            """
            self.noneValue = v

        def getNoneValue(self):
            """Returns the current string representation when no valid model or
            model value exists.

            :return: a string representation for an invalid value
            :rtype: str
            """
            return self.noneValue

        def isChangeable(self):
            """Tells if this component value can be changed by the user. Default
            implementation will return True if and only if:

                - this component is attached to a valid taurus model and
                - the taurus model is writable and
                - this component is not read-only

            :return: True if this component value can be changed by the user or
                False otherwise
            :rtype: bool
            """
            res = False
            if self.modelObj is not None:
                res = self.modelObj.isWritable()
            res = res and not self.isReadOnly()
            return res

        def isReadOnly(self):
            """Determines if this component is read-only or not in the sense that
            the user can interact with it. Default implementation returns True.

            Override when necessary.

            :return: whether or not this component is read-only
            :rtype: bool
            """
            return True

        def isAttached(self, *, key=_DEFAULT):
            """Determines if this component is attached to the taurus model.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: True if the component is attached or False otherwise.
            :rtype: bool
            """
            key = self._checkModelKey(key)
            return self._attached[key]

        def preAttach(self, *, key=_DEFAULT):
            """Called inside self.attach() before actual attach is performed.
            Default implementation does nothing.

            Override when necessary.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            pass

        def postAttach(self, *, key=_DEFAULT):
            """Called inside self.attach() after actual attach is performed.
            Default implementation does not do anything.

            Override when necessary.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            pass

        def preDetach(self, *, key=_DEFAULT):
            """Called inside self.detach() before actual deattach is performed.
            Default implementation does nothing.

            Override when necessary.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            pass

        def postDetach(self, *, key=_DEFAULT):
            """Called inside self.detach() after actual deattach is performed.
            Default implementation does not do anything.

            Override when necessary.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            pass

        def _attach(self, *, key=_DEFAULT):
            """Attaches the component to the taurus model.
            In general it should not be necessary to overwrite this method in a
            subclass.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: True if success in attachment or False otherwise.
            :rtype: bool
            """
            key = self._checkModelKey(key)
            if key is MLIST:
                self._modelObj[key] = None
                return

            if self._attached[key]:
                return self._attached[key]

            self._wrap_old_api(self.preAttach, key=key)

            cls = self._wrap_old_api(self.getModelClass, key=key)

            if cls is None:
                self._attached[key] = False
                # self.trace("Failed to attach: Model class not found")
            elif self._modelName[key] == "":
                self._attached[key] = False
                self._modelObj[key] = None
            else:
                try:
                    self._modelObj[key] = taurus.Manager().getObject(
                        cls, self._modelName[key]
                    )
                    if self._modelObj[key] is not None:
                        self._modelObj[key].addListener(self)
                        self._attached[key] = True
                        # -------------------------------------------------------
                        # Note: the changeLogName call has been disabled because
                        # appending the model name to the log name does not scale
                        # well with multi-models:
                        #
                        # self.changeLogName(self.log_name + "." + self.modelName)
                        # -------------------------------------------------------
                except Exception:
                    self._modelObj[key] = None
                    self._attached[key] = False
                    self.debug(
                        "Exception occurred while trying to attach '%s'"
                        % self._modelObj[key]
                    )
                    self.traceback()

            self._wrap_old_api(self.postAttach, key=key)

            return self._attached

        def _detach(self, *, key=_DEFAULT):
            """Detaches the component from the taurus model"""
            key = self._checkModelKey(key)

            self._wrap_old_api(self.preDetach, key=key)

            if self._attached[key]:
                m = self._modelObj[key]
                if m is not None:
                    m.removeListener(self)

                # -------------------------------------------------------
                # Note: the changeLogName call has been disabled because
                # appending the model name to the log name does not scale
                # well with multi-models:
                #
                # pos = self.log_name.find(".")
                # if pos >= 0:
                #     new_log_name = self.log_name[: self.log_name.rfind(".")]
                #     self.changeLogName(new_log_name)
                # -------------------------------------------------------
                self._modelObj[key] = None
                self._attached[key] = False
                self.fireEvent(m, TaurusEventType.Change, None)

            self._wrap_old_api(self.postDetach, key=key)

        def setModelInConfig(self, yesno):
            """
            Sets whether the model-related properties should be stored for this
            widget when creating the config dict with :meth:`createConfig` (and
            restored when calling :meth:`applyConfig`).
            By default this is not enabled.
            The following properties are affected by this:

            - "model"

            :param yesno: If True, the model-related properties will be registered
                as config properties. If False, they will be unregistered.
            :type yesno: bool

            .. seealso:: :meth:`registerConfigProperty`, :meth:`createConfig`,
                         :meth:`applyConfig`
            """
            if yesno == self._modelInConfig:
                return
            if yesno:
                self.registerConfigProperty(self.getModel, self.setModel, "model")
            else:
                self.unregisterConfigurableItem("model", raiseOnError=False)
            self._modelInConfig = yesno

        def getModelInConfig(self):
            return self._modelInConfig

        def resetModelInConfig(self):
            return self.setModelInConfig(False)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Pending operations related methods
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def applyPendingOperations(self, ops=None):
            """Applies operations without caring about danger messages.
            Use :meth:`TaurusBaseWidget.safeApplyOperation` if you want to warn the
            user before applying

            :param ops: list of operations to apply. If None is given (default) the
                component fetches the pending operations
            :type ops: sequence<taurus.core.taurusoperation.TaurusOperation> or
                None
            """
            self.debug("Apply changes")
            if ops is None:
                ops = self.getPendingOperations()

            if self.isAutoProtectOperation():
                import taurus.qt.qtgui.dialog

                @taurus.qt.qtgui.dialog.protectTaurusMessageBox
                def go():
                    self.getTaurusManager().applyPendingOperations(ops)

                go()
            else:
                self.getTaurusManager().applyPendingOperations(ops)

        def hasPendingOperations(self):
            """Returns if the component has pending operations

            :return: True if there are pending operations or False otherwise
            :rtype: bool
            """
            return len(self.getPendingOperations()) > 0

        def getPendingOperations(self):
            """Returns the sequence of pending operations

            :return: a list of pending operations
            :rtype: sequence<taurus.core.taurusoperation.TaurusOperation>
            """
            return self._operations

        def resetPendingOperations(self):
            """Clears the list of pending operations"""
            self._operations = []

        def setDangerMessage(self, dangerMessage=""):
            """Sets the danger message when applying an operation. If dangerMessage
            is None, the apply operation is considered safe

            :param dangerMessage: the danger message. If None is given (default)
                the apply operation is considered safe
            :type dangerMessage: str or None
            """
            self._dangerMessage = dangerMessage
            self._isDangerous = len(dangerMessage) > 0

        def getDangerMessage(self):
            """Returns the current apply danger message or None if the apply
            operation is safe

            :return: the apply danger message
            :rtype: str or None
            """
            return self._dangerMessage

        def resetDangerMessage(self):
            """Clears the danger message. After this method is executed the apply
            operation for this component will be considered safe."""
            self.setDangerMessage(None)

        def isDangerous(self):
            """Returns if the apply operation for this component is dangerous

            :return: wheter or not the apply operation for this component is
                dangerous
            :rtype: bool
            """
            return self._isDangerous

        def setForceDangerousOperations(self, yesno):
            """Forces/clears the dangerous operations

            :param yesno: force or not the dangerous operations
            :type yesno: bool
            """
            self._forceDangerousOperations = yesno

        def getForceDangerousOperations(self):
            """Returns if apply dangerous operations is forced

            :return: wheter or not apply dangerous operations is forced
            :rtype: bool
            """
            return self._forceDangerousOperations

        def resetForceDangerousOperations(self):
            """Clears forcing apply dangerous operations"""
            self.setForceDangerousOperations(False)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Standard Qt properties
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def getModel(self, *, key=_DEFAULT):
            """Returns the model name for this component.

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            :return: the model name.
            :rtype: str
            """
            key = self._checkModelKey(key)
            return self._localModelName[key]

        def setModel(self, model, *, key=_DEFAULT):
            """Sets/unsets the model name for the given key.
            If key is `MLIST`, a model is set for each name in the model
            sequence, new model keys are automatically added to the object's
            ``modelList`` attribute and the corresponding models are attached using
            those keys. The new keys are of the form ``(MLIST, i)`` where ``i`` is
            the index of the corresponding model name in the model sequence.

            :param model: the new model name. If key is `MLIST`, model is expected
                to be a sequence of model names.
            :type model: str (or sequence of str if key is MLIST)
            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            key = self._checkModelKey(key)
            if self.__class__.setModelCheck is TaurusBaseComponent.setModelCheck:
                # setModelCheck is not reimplemented so use the new API
                self._setModelCheck(model, key=key)
            else:
                self._wrap_old_api(self.setModelCheck, model, key=key)
            self.resetFormat()
            self.updateStyle()

        @deprecation_decorator(rel="5.1.0", alt="setModel")
        def setModelCheck(self, model, check=True, *, key=_DEFAULT):
            return self._setModelCheck(model, check=check, key=key)

        def _setModelCheck(self, model, check=True, *, key=_DEFAULT):
            """Sets the component taurus model. Setting the check argument to True
            (default) will check if the current model is equal to the given
            argument. If so then nothing is done. This should be the standard way
            to call this method since it will avoid recursion.

            :param model: the new model name
            :type model: str
            :param check: whether or not to check against the actual model name
            :type check: bool
            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            key = self._checkModelKey(key)
            if model is None:
                model = ""
            if key is MLIST:
                model = tuple([str(m) for m in model])
            else:
                model = str(model)
            if (
                check
                and key in self._localModelName
                and model == self._localModelName[key]
            ):
                return

            self._localModelName[key] = model

            if key is MLIST:
                self._modelName[key] = model
                # find existing model keys created by a MLIST model
                old_mlist_keys = [
                    k
                    for k in self.modelKeys
                    if isinstance(k, tuple) and MLIST in k
                ]
                # set each model listed in this MLIST model
                for i, m in enumerate(model):
                    k = key, i  # compound the MLIST with an index
                    if k in old_mlist_keys:
                        # we are reusing it, remove from old_mlist_keys
                        old_mlist_keys.remove(k)
                    self._addModelKey(k)
                    try:
                        self._setModelCheck(m, key=k)
                    except Exception as e:
                        self.warning(
                            "Problem setting model '%s' with key '%s'. Reason: %r",
                            m,
                            k,
                            e,
                        )
                        self._removeModelKey(k)
                # clean any remaining non-replaced old MLIST keys
                for k in old_mlist_keys:
                    self._removeModelKey(k)
                return
            parent_widget = None
            if key == self.modelKeys[0]:  # parent supported only for single-model
                try:
                    # if this widget has a buddy, check to see if it is a valid
                    # TaurusWidget
                    buddy_func = getattr(self, "buddy")
                    buddy_widget = buddy_func()
                    if buddy_widget and isinstance(
                        buddy_widget, TaurusBaseComponent
                    ):
                        parent_widget = buddy_widget
                    elif self._getUseParentModel():
                        parent_widget = self._getParentTaurusComponent()
                except Exception:
                    if self._getUseParentModel():
                        parent_widget = self._getParentTaurusComponent()

            if self.__class__.setModelName is TaurusBaseComponent.setModelName:
                # setModelName is not reimplemented so use the new API
                self._setModelName(model, parent=parent_widget, key=key)
            else:
                self._wrap_old_api(
                    self.setModelName, model, parent=parent_widget, key=key
                )

        def resetModel(self, *, key=_DEFAULT):
            """Sets the model name to the empty string

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            self._wrap_old_api(self.setModel, "", key=key)

        @deprecation_decorator(rel="4.3.2")
        def getUseParentModel(self):
            return self._getUseParentModel()

        def _getUseParentModel(self):
            """Returns whether this component is using the parent model

            .. note:: the parentModel API does not support multi-models

            :return: True if using parent model or False otherwise
            :rtype: bool
            """
            return getattr(self, "_useParentModel", False)

        @deprecation_decorator(
            rel="4.3.2",
            alt="explicit models including the parent model",
        )
        def setUseParentModel(self, yesno):
            return self._setUseParentModel(yesno)

        @Qt.pyqtSlot(bool)
        def _setUseParentModel(self, yesno):
            """Sets/unsets using the parent model

            :param yesno: whether or not to use parent model
            :type yesno: bool
            """
            if yesno == self._useParentModel:
                return
            self._useParentModel = yesno
            # force a recalculation of the model
            self._setModelCheck(self.getModel(), False)

        @deprecation_decorator(rel="4.3.2")
        def resetUseParentModel(self):
            return self._resetUseParentModel()

        def _resetUseParentModel(self):
            """Resets the usage of parent model to False"""
            self._setUseParentModel(False)
            self.updateStyle()

        @Qt.pyqtSlot(bool)
        def setShowQuality(self, showQuality):
            """Sets/unsets the show quality property

            :param showQuality: whether or not to show the quality
            :type showQuality: bool
            """
            if showQuality == self._showQuality:
                return
            self._showQuality = showQuality
            self.updateStyle()

        def getShowQuality(self):
            """Returns if showing the quality as a background color

            :return: True if showing the quality or False otherwise
            :rtype: bool
            """
            return self._showQuality

        def resetShowQuality(self):
            """Resets the show quality to self.__class__._showQuality"""
            self.setShowQuality(self.__class__._showQuality)

        @Qt.pyqtSlot(bool)
        def setShowText(self, showText):
            """Sets/unsets showing the display value of the model

            :param showText: whether or not to show the display value
            :type showText: bool
            """
            if showText == self._showText:
                return
            self._showText = showText
            self.fireEvent(
                self.getModelObj(), TaurusEventType.Change, self.getModelValueObj()
            )
            self.updateStyle()

        def getShowText(self):
            """Returns if showing the display value

            :return: True if showing the display value or False otherwise
            :rtype: bool
            """
            return self._showText

        def resetShowText(self):
            """Resets the showing of the display value to True"""
            self.setShowText(True)

        @deprecation_decorator(rel="4.5.3a")
        def setTaurusPopupMenu(self, menuData):
            """Sets/unsets the taurus popup menu

            :param menuData: an xml representing the popup menu
            :type menuData: str
            """
            self.taurusMenuData = str(menuData)
            self.__explicitPopupMenu = True
            factory = ActionFactory()
            self.taurusMenu = factory.getNewMenu(self, self.taurusMenuData)

        @deprecation_decorator(rel="4.5.3a")
        def getTaurusPopupMenu(self):
            """Returns an xml string representing the current taurus popup menu

            :return: an xml string representing the current taurus popup menu
            :rtype: str
            """
            return self.taurusMenuData

        @deprecation_decorator(rel="4.5.3a")
        def resetTaurusPopupMenu(self):
            """Resets the taurus popup menu to empty"""
            self.taurusMenuData = ""
            self.__explicitPopupMenu = False

        def isModifiableByUser(self):
            """whether the user can change the contents of the widget

            :return: True if the user is allowed to modify the look&feel
            :rtype: bool
            """
            return self._modifiableByUser

        def setModifiableByUser(self, modifiable):
            """sets whether the user is allowed to modify the look&feel

            :param modifiable: True allows the user to modify certain properties of
                the widget. False restricts those modifications
            :type modifiable: bool
            """
            self._modifiableByUser = modifiable

        def resetModifiableByUser(self):
            """Equivalent to
            setModifiableByUser(self.__class__._modifiableByUser)"""
            self.setModifiableByUser(self.__class__._modifiableByUser)

        def resetAutoProtectOperation(self):
            """Resets protecting operations"""
            self.setAutoProtectOperation(True)

        def isAutoProtectOperation(self):
            """Tells if this widget's operations are protected against exceptions

            :return: True if operations are protected against exceptions or False
                otherwise
            :rtype: bool
            """
            return self._autoProtectOperation

        def setAutoProtectOperation(self, protect):
            """Sets/unsets this widget's operations are protected against
            exceptions

            :param protect: wheater or not to protect widget operations
            :type protect: bool
            """
            self._autoProtectOperation = protect

    class TaurusBaseWidget(TaurusBaseComponent):
        """The base class for all Qt Taurus widgets.

        .. note::
            Any class which inherits from TaurusBaseWidget is expected to also
            inherit from QWidget (or from a QWidget derived class)
        """

        modelChanged = baseSignal("modelChanged", "QString")
        valueChangedSignal = baseSignal("valueChanged")

        _dragEnabled = False

        def __init__(self, name="", parent=None, designMode=False, **kwargs):
            self._disconnect_on_hide = False
            self._supportedMimeTypes = None
            self._autoTooltip = True
            self.call__init__(
                TaurusBaseComponent, name, parent=parent, designMode=designMode
            )
            self._setText = self._findSetTextMethod()

        @deprecation_decorator(rel="4.6.2", alt="onSetFormatter")
        def showFormatterDlg(self):
            """deprecated because it does not distinguish between a user cancelling
            the dialog and the user selecting `None` as a formatter.
            """
            formatter, ok = self.showFormatterDlg()
            if not ok:
                return None
            else:
                return formatter

        def __showFormatterDlg(self):
            """
            Shows a dialog to get the formatter from the user.

            :return: formatter: python format string or formatter callable (in
                string version) or None
            """
            # add formatters from plugins
            known_formatters = TaurusBaseWidget.get_registered_formatters()
            # add default formatter

            default = getattr(
                taurus.tauruscustomsettings, "DEFAULT_FORMATTER", defaultFormatter
            )
            known_formatters["<DEFAULT_FORMATTER>"] = default
            # add the formatter of this class
            cls = self.__class__
            known_formatters["<{}.FORMAT>".format(cls.__name__)] = cls.FORMAT
            # add current formatter of this object
            if self.FORMAT not in known_formatters.values():
                known_formatters[str(self.FORMAT)] = self.FORMAT

            names, formatters = list(zip(*known_formatters.items()))

            url = (
                "http://taurus-scada.org/devel/api/taurus/qt/qtgui/base/"
                + "_TaurusBaseComponent.html"
            )

            choice, ok = Qt.QInputDialog.getItem(
                self,
                "Set formatter",
                'Choose/Enter a formatter (<a href="{}">help</a>)'.format(url),
                names,
                current=formatters.index(self.FORMAT),
                editable=True,
            )
            if not ok:
                return None, False

            if choice in names:
                return known_formatters[choice], True
            else:
                return choice, True

        def onSetFormatter(self):
            """Slot to allow interactive setting of the Formatter.

            .. seealso:: :meth:`TaurusBaseWidget.__showFormatterDlg`,
                         :meth:`TaurusBaseComponent.displayValue`,
                         :attr:`tauruscustomsettings.DEFAULT_FORMATTER`
            """
            format, ok = self.__showFormatterDlg()
            if ok:
                self.debug(
                    "Default format has been changed to: {0}".format(format)
                )
                self.setFormat(format)
            return format

        # It makes the GUI to hang... If this needs implementing, we should
        # reimplement it using the Qt parent class, not QWidget...
        # def destroy(self):
        #    '''Reimplements the Qt.QWidget destroy method to ensure that this
        #    object
        #    stops listening its model.'''
        #    self.setUseParentModel(False)
        #    self.resetModel()
        #    Qt.QWidget.destroy(self)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Helper methods
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def getQtClass(self, bases=None):
            """Returns the parent Qt class for this widget

            :param bases: the list of class objects. If None is given (default) it
                uses the object base classes from __bases__
            :type bases: sequence<class> or None
            :return: the QWidget class object
            :rtype: QWidget class
            """
            bases = bases or self.__class__.__bases__
            for klass in bases:
                is_taurusbasewidget = issubclass(klass, TaurusBaseWidget)
                if issubclass(klass, Qt.QWidget):
                    if is_taurusbasewidget:
                        return self.getQtClass(klass.__bases__)
                    return klass
                elif is_taurusbasewidget:
                    return self.getQtClass(klass.__bases__)
            return None

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Qt properties from TaurusBaseComponent that need to be overwritten
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        _UseParentMsg = False

        @Qt.pyqtSlot(bool)
        def setUseParentModel(self, yesno):
            """
            ..note:: Deprecated since v4.3.2

            Sets/unsets using the parent model.

            .. note:: in some cases you may need to call
                :meth:`recheckTaurusParent`
                after reparenting of some of this widget's ancestors

            :param yesno: whether or not to use parent model
            :type yesno: bool

            .. seealso:: :meth:`recheckTaurusParent`
            """
            is_same = yesno == self._useParentModel
            if not is_same:
                self._updateUseParentModel(yesno)
                if (
                    yesno
                    and self._designMode
                    and not TaurusBaseWidget._UseParentMsg
                ):
                    TaurusBaseWidget._UseParentMsg = True
                    Qt.QMessageBox.information(
                        self,
                        "UseParentModel usage note",
                        "Using the UseParentModel feature may require you to call "
                        + "recheckTaurusParent() manually for this widget "
                        + "after calling setupUi in your code."
                        + "See the documentation of "
                        + "TaurusBaseWidget.recheckTaurusParent()",
                    )
            TaurusBaseComponent.setUseParentModel(self, yesno)

        def _updateUseParentModel(self, yesno):
            parent_widget = self._getParentTaurusComponent()
            if parent_widget:
                if yesno:
                    parent_widget.modelChanged.connect(self.parentModelChanged)
                else:
                    parent_widget.modelChanged.disconnect(self.parentModelChanged)

        def recheckTaurusParent(self):
            """
            Forces the widget to recheck its Taurus parent. Taurus Widgets will in
            most situations keep track of changes in their taurus parenting, but in
            some special cases (which unfortunately tend to occur when using Qt
            Designer) they may not update it correctly.

            If this happens, you can manually call this method.

            For more information, check the :download:`issue demo example
            </devel/examples/parentmodel_issue_demo.py>`
            """
            self._updateUseParentModel(True)

        def _setModelCheck(self, model, check=True, *, key=_DEFAULT):
            """Sets the component taurus model. Setting the check argument to True
            (default) will check if the current model is equal to the given
            argument. If so then nothing is done. This should be the standard way
            to call this method since it will avoid recursion.

            :param model: the new model name
            :type model: str
            :param check: whether or not to check against the actual model name
            :type check: bool
            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            key = self._checkModelKey(key)
            if model is None:
                model = ""
            if key is MLIST:
                model = tuple([str(m) for m in model])
            else:
                model = str(model)

            send_signal = model != self._localModelName[key]
            TaurusBaseComponent._setModelCheck(self, model, check=check, key=key)

            if send_signal:
                # emit a signal informing the child widgets that the model has
                # changed
                self.modelChanged.emit(model)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Default Qt signal handlers. Overwrite them as necessary
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def changeEvent(self, evt):
            """overwrites QWidget.changeEvent(self, evt) to handle the
            ParentChangeEvent in case this widget is using the parent model. Always
            calls the QWidget.changeEvent in order not to lose events
            """
            if self._getUseParentModel():
                evt_type = evt.type()
                if evt_type == Qt.QEvent.ParentChange:
                    # disconnect from old parent
                    if self._parentTaurusComponent:
                        self._parentTaurusComponent.modelChanged.disconnect(
                            self.parentModelChanged
                        )
                    self._updateUseParentModel(True)
                    self._setModelCheck(self.getModel(), False)
            self.getQtClass().changeEvent(self, evt)

        def parentModelChanged(self, parentmodel_name):
            """Invoked when the Taurus parent model changes

            :param parentmodel_name: the new name of the parent model
            :type parentmodel_name: str
            """
            self.debug("Parent model changed to '%s'" % parentmodel_name)
            parentmodel_name = str(parentmodel_name)
            if self._getUseParentModel():
                # force an update of the interpretation of the model property
                model = self.getModel()
                self._setModelCheck(model, False)
                self.modelChanged.emit(model)
            else:
                self.debug(
                    "received event from parent although not using parent model"
                )

        def handleEvent(self, evt_src, evt_type, evt_value):
            """very basic and generalistic handling of events.

            Override when necessary.

            :param evt_src: object that triggered the event
            :type evt_src: object or None
            :param evt_type: type of event
            :type evt_type: taurus.core.taurusbasetypes.TaurusEventType or None
            :param evt_value: event value
            :type evt_value: object or None
            """
            # Update the text shown by the widget
            if self._setText:
                text = ""
                if self.getShowText():
                    if isinstance(evt_src, TaurusAttribute):
                        if evt_type in (
                            TaurusEventType.Change,
                            TaurusEventType.Periodic,
                        ):
                            text = self.displayValue(evt_value.rvalue)
                        elif evt_type == TaurusEventType.Error:
                            text = self.getNoneValue()
                        elif evt_type == TaurusEventType.Config:
                            text = self.getDisplayValue()
                    else:
                        text = self.getDisplayValue()
                self._setText(text)

            # update tooltip
            if self._autoTooltip:
                self.setToolTip(self.getFormatedToolTip())

            # TODO: update whatsThis

            # update appearance
            self.updateStyle()

        def setModelInConfig(self, yesno):
            """
            extends :meth:`TaurusBaseComponent.setModelInConfig` to include also
            the "useParentModel" property

            .. seealso:: :meth:`TaurusBaseComponent.setModelInConfig`
            """
            if yesno == self._modelInConfig:
                return
            if yesno:
                self.registerConfigProperty(
                    self._getUseParentModel,
                    self._setUseParentModel,
                    "useParentModel",
                )
            else:
                self.unregisterConfigurableItem(
                    "useParentModel", raiseOnError=False
                )

            TaurusBaseComponent.setModelInConfig(self, yesno)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Mandatory overwrite from TaurusBaseComponent
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def updateStyle(self):
            """Updates the widget style. Default implementation just calls
            QWidget.update()

            Override when necessary.
            """
            self.update()

        @deprecation_decorator(rel="4.3.2")
        def getParentTaurusComponent(self):
            return self._getParentTaurusComponent()

        def _getParentTaurusComponent(self):
            """Returns the first taurus component in the widget hierarchy or None
            if no taurus component is found

            :return: the parent taurus base component
            :rtype: TaurusBaseComponent or None
            """
            p = self.parentWidget()
            while p and not isinstance(p, TaurusBaseWidget):
                p = p.parentWidget()
            if isinstance(p, TaurusBaseWidget):
                self._parentTaurusComponent = p
            else:
                self._parentTaurusComponent = p = None
            return p

        def setDisconnectOnHide(self, disconnect):
            """Sets/unsets disconnection on hide event

            :param disconnect: whether or not to disconnect on hide event
            :type disconnect: bool
            """
            if not self.visible() and not disconnect:
                self.info(
                    "Ignoring setDisconnectOnHide to False: widget is not visible"
                )
                return
            self._disconnect_on_hide = disconnect

        def hideEvent(self, event):
            """Override of the QWidget.hideEvent()"""
            if self._disconnect_on_hide:
                try:
                    if self.getModelName():
                        self._detach()
                    event.accept()
                except Exception:
                    self.warning("Exception received while trying to hide")
                    self.traceback()

        def showEvent(self, event):
            """Override of the QWidget.showEvent()"""
            if self._disconnect_on_hide:
                try:
                    if self.getModelName():
                        self._attach()
                    event.accept()
                except Exception:
                    self.warning("Exception received while trying to show")
                    self.traceback()

        def closeEvent(self, event):
            """Override of the QWidget.closeEvent()"""
            try:
                self._detach()
                event.accept()
            except Exception:
                self.warning("Exception received while trying to close")
                self.traceback()

        def handleException(self, e):
            for h in self._exception_listener:
                h.exceptionReceived(e)

        def _findSetTextMethod(self):
            """Determine if this widget is able to display the text value of the
            taurus model. It searches through the possible Qt methods to display
            text.

            :return: a python method or None if no suitable method is found.
            :rtype: callable
            """
            setMethod = None
            try:
                setMethod = getattr(self, "setText")
            except AttributeError:
                try:
                    setMethod = getattr(self, "setTitle")
                except AttributeError:
                    try:
                        setMethod = getattr(self, "display")
                    except AttributeError:
                        # it seems the widget has no way to update a value
                        pass

            return setMethod

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Drag&Drop related methods:
        #    default implementation allows setting the model by dropping it on the
        #    widget (if the widget allows modifications by the user).
        #
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def setModifiableByUser(self, modifiable):
            """Reimplemented to acept/reject drops based on whether the widget is
            modifiable by the user. See
            :meth:`TaurusBaseComponent.setModifiableByUser()`
            """
            TaurusBaseComponent.setModifiableByUser(self, modifiable)
            self.setAcceptDrops(modifiable)

        def getSupportedMimeTypes(self):
            """
            returns a list of supported mimeTypes that this widget support (ordered
            by priority). If none is set explicitely via
            :meth:`setSupportedMimeTypes`, a best effort will be tried based on the
            model class

            ..seealso: :meth:`setSupportedMimeTypes`

            This provides only a very basic implementation. Reimplement in derived
            classes if needed

            :return: list of MIME type names
            :rtype: list<str>
            """
            if self._supportedMimeTypes is not None:
                return self._supportedMimeTypes
            # fallback guess based on modelclass
            try:
                modelclass = self.getModelClass()
            except Exception:
                return []
            if modelclass == TaurusDevice:
                return [TAURUS_DEV_MIME_TYPE, TAURUS_MODEL_MIME_TYPE]
            elif modelclass == TaurusAttribute:
                return [TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE]
            else:
                return [TAURUS_MODEL_MIME_TYPE]

        def setSupportedMimeTypes(self, mimetypes):
            """
            sets the mimeTypes that this widget support

            :param mimetypes: list (ordered by priority) of MIME type names
            :type mimetypes: list<str>
            """
            self._supportedMimeTypes = mimetypes

        def dragEnterEvent(self, event):
            """reimplemented to support drag&drop of models. See
            :class:`QWidget`
            """
            if self.isModifiableByUser():
                supported = self.getSupportedMimeTypes()
                for f in event.mimeData().formats():
                    if f in supported:
                        event.acceptProposedAction()
                        return

        def getDropEventCallback(self):
            """returns the method to be called when a dropping event occurs.
            The default implementation returns `self.setModel`. Reimplement
            it subclasses to call different methods.

            :return: method to be called when a dropping event occurs.
            :rtype: callable
            """
            return self.setModel

        def dropEvent(self, event):
            """reimplemented to support drag&drop of models. See
            :class:`QWidget`
            """
            mtype = self.handleMimeData(
                event.mimeData(), self.getDropEventCallback()
            )
            if mtype is None:
                self.info("Invalid model")
            else:
                event.acceptProposedAction()

        def handleMimeData(self, mimeData, method):
            """Selects the most appropriate data from the given mimeData object
            (in the order returned by :meth:`getSupportedMimeTypes`) and passes
            it to the given method.

            :param mimeData: the MIME data object from which the model is to be
                extracted
            :type mimeData: QMimeData
            :param method: a method that accepts a string as argument. This method
                will be called with the data from the mimeData object
            :type method: callable<str>
            :return: returns the MimeType used if the model was successfully set,
                or None if the model could not be set
            :rtype: str or None
            """
            supported = self.getSupportedMimeTypes()
            formats = mimeData.formats()
            for mtype in supported:
                if mtype in formats:
                    d = bytes(mimeData.data(mtype)).decode("utf-8")
                    if d is None:
                        return None
                    try:
                        method(d)
                        return mtype
                    except Exception:
                        self.debug(
                            "Invalid data (%s) for MIMETYPE=%s"
                            % (repr(d), repr(mtype))
                        )
                        self.traceback(taurus.Debug)
                        return None

        def getModelMimeData(self):
            """Returns a MimeData object containing the model data. The default
            implementation fills the `TAURUS_MODEL_MIME_TYPE`. If the widget's
            Model class is Attribute or Device, it also fills
            `TAURUS_ATTR_MIME_TYPE` or `TAURUS_DEV_MIME_TYPE`, respectively

            :return: MimeData object containing the model data.
            :rtype: QMimeData
            """
            mimeData = Qt.QMimeData()
            modelname = str(self.getModelName()).encode(encoding="utf8")
            mimeData.setData(TAURUS_MODEL_MIME_TYPE, modelname)
            try:
                modelclass = self.getModelClass()
            except Exception:
                modelclass = None
            if modelclass and issubclass(modelclass, TaurusDevice):
                mimeData.setData(TAURUS_DEV_MIME_TYPE, modelname)
            elif modelclass and issubclass(modelclass, TaurusAttribute):
                mimeData.setData(TAURUS_ATTR_MIME_TYPE, modelname)
            return mimeData

        def mousePressEvent(self, event):
            """reimplemented to record the start position for drag events.
            See :class:`~PyQt5.Qt.QWidget`
            """
            if self._dragEnabled and event.button() == Qt.Qt.LeftButton:
                # I need to copy it explicitly to avoid a bug with PyQt4.4
                self.dragStartPosition = Qt.QPoint(event.pos())
            self.getQtClass().mousePressEvent(self, event)

        def mouseMoveEvent(self, event):
            """reimplemented to provide drag events.
            See :class:`~PyQt5.Qt.QWidget`
            """
            if not self._dragEnabled or not event.buttons() & Qt.Qt.LeftButton:
                return self.getQtClass().mouseMoveEvent(self, event)
            if (
                event.pos() - self.dragStartPosition
            ).manhattanLength() < Qt.QApplication.startDragDistance():
                return self.getQtClass().mouseMoveEvent(self, event)
            ret = self.getQtClass().mouseMoveEvent(
                self, event
            )  # call the superclass
            event.accept()  # do  not propagate
            drag = Qt.QDrag(self)
            drag.setMimeData(self.getModelMimeData())
            drag.exec_(Qt.Qt.CopyAction, Qt.Qt.CopyAction)
            return ret

        def isDragEnabled(self):
            """whether the user can drag data from this widget

            :return: True if the user can drag data
            :rtype: bool
            """
            return self._dragEnabled

        def setDragEnabled(self, enabled):
            """sets whether the user is allowed to drag data from this widget

            :param enabled: if True, dragging is enabled. False disables
            :type enabled: bool
            """
            self._dragEnabled = enabled

        def resetDragEnabled(self):
            """Equivalent to setDragEnabled(self.__class__._dragEnabled)"""
            self.setModifiableByUser(self.__class__._dragEnabled)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Pending operations related methods: default implementation
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def updatePendingOpsStyle(self):
            """This method should be reimplemented by derived classes that want to
            change their appearance depending whether there are pending operations
            or not"""
            pass

        def emitValueChanged(self, *args):
            """Connect the specific XXXXChanged signals from derived classes to
            this method in order to have a unified signal which can be used by
            Taurus Widgets"""

            self.valueChangedSignal.emit()
            self.updatePendingOpsStyle()  # by default, update its own style

        def safeApplyOperations(self, ops=None):
            """Applies the given operations (or the pending operations if None
            passed)

            :param ops: list of operations to apply. If None is given (default) the
                component fetches the pending operations
            :type ops: sequence<taurus.core.taurusoperation.TaurusOperation> or
                None

            :return: False if the apply was aborted by the user or if the widget is
                in design mode. True otherwise.
            :rtype: bool
            """

            if ops is None:
                ops = self.getPendingOperations()

            # Check if we need to take care of dangerous operations
            if self.getForceDangerousOperations():
                dangerMsgs = []
            else:
                dangerMsgs = [
                    op.getDangerMessage()
                    for op in ops
                    if len(op.getDangerMessage()) > 0
                ]
            # warn the user if need be
            if len(dangerMsgs) == 1:
                result = Qt.QMessageBox.warning(
                    self,
                    "Potentially dangerous action",
                    "%s\nProceed?" % dangerMsgs[0],
                    Qt.QMessageBox.Ok | Qt.QMessageBox.Cancel,
                    Qt.QMessageBox.Ok,
                )
                if result != Qt.QMessageBox.Ok:
                    return False

            elif len(dangerMsgs) > 1:
                warningDlg = Qt.QMessageBox(
                    Qt.QMessageBox.Warning,
                    " %d potentially dangerous actions" % len(dangerMsgs),
                    (
                        "You are about to apply %d actions that may be "
                        + "potentially dangerous. Proceed?"
                    )
                    % len(dangerMsgs),
                    Qt.QMessageBox.Ok | Qt.QMessageBox.Cancel,
                    self,
                )
                details = "\n".join(dangerMsgs)
                warningDlg.setDetailedText(details)
                result = warningDlg.exec_()
                if result != Qt.QMessageBox.Ok:
                    return False

            if self._designMode:
                self.info("Refusing to apply operation while in design mode")
                return False

            self.applyPendingOperations(ops)
            return True

        def setAutoTooltip(self, yesno):
            """Determines if the widget should automatically generate a tooltip
            based on the current widget model.

            :param yesno: True to automatically generate tooltip or False otherwise
            :type yesno: bool
            """
            self._autoTooltip = yesno

        def getAutoTooltip(self):
            """Returns if the widget is automatically generating a tooltip based
            on the current widget model.

            :return: True if automatically generating tooltip or False otherwise
            :rtype: bool
            """
            return self._autoTooltip

        @classmethod
        def getQtDesignerPluginInfo(cls):
            """Returns pertinent information in order to be able to build a valid
            QtDesigner widget plugin.

            The dictionary returned by this method should contain *at least* the
            following keys and values:

            - 'module' : a string representing the full python module name (ex.:
              'taurus.qt.qtgui.base')
            - 'icon' : a string representing valid resource icon (ex.:
              'designer:combobox.png')
            - 'container' : a bool telling if this widget is a container widget or
               not.

            This default implementation returns the following dictionary::

                { 'group'     : 'Taurus [Unclassified]',
                  'icon'      : 'logos:taurus.png',
                  'container' : False }

            :return: a map with pertinent designer information
            :rtype: dict
            """
            return {
                "group": "Taurus [Unclassified]",
                "icon": "logos:taurus.png",
                "container": False,
            }

    class TaurusBaseWritableWidget(TaurusBaseWidget):
        """The base class for all taurus input widgets

        it emits the applied signal when the value has been applied.
        """

        applied = baseSignal("applied")

        def __init__(
            self, name="", taurus_parent=None, designMode=False, **kwargs
        ):
            self.call__init__(
                TaurusBaseWidget, name, parent=taurus_parent, designMode=designMode
            )

            self._lastValue = None

            # Overwrite not to show quality by default
            self._showQuality = False

            # Don't do auto-apply by default
            self._autoApply = False

            # Don't force a writing to attribute when there are not pending
            # operations
            self._forcedApply = False

            self.valueChangedSignal.connect(self.updatePendingOperations)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # TaurusBaseWidget overwriting
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def getModelClass(self, *, key=_DEFAULT):
            """reimplemented from :class:`TaurusBaseWidget`"""
            return TaurusAttribute

        def isReadOnly(self):
            """reimplemented from :class:`TaurusBaseWidget`"""
            return False

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

        def setAutoApply(self, auto):
            """
            Sets autoApply mode. In autoApply mode, the widget writes the value
            automatically whenever it is changed by the user (e.g., when
            :meth:`notifyValueChanged` is called). If False, a value changed just
            flags a "pending operation" which needs to be applied manually by
            the user before the value gets written.

            :param auto: True for setting autoApply mode. False for disabling
            :type auto: bool
            """
            self._autoApply = auto

        def getAutoApply(self):
            """whether autoApply mode is enabled or not.

            :return: True if autoApply is enabled. False otherwise
            :rtype: bool
            """
            return self._autoApply

        def resetAutoApply(self):
            """resets the autoApply mode (i.e.: sets it to False)"""
            self.setAutoApply(False)

        def setForcedApply(self, forced):
            """Sets the forcedApply mode. In forcedApply mode, values are written
            even if there are not pending operations (e.g. even if the displayed
            value is the same as the currently applied one).

            .. seealso: :meth:`forceApply` and :meth:`writeValue`

            :param forced: True for setting forcedApply mode. False for disabling
            :type forced: bool
            """
            self._forcedApply = forced

        def getForcedApply(self):
            """whether forcedApply mode is enabled or not.

            :return: True if forceApply is enabled. False otherwise
            :rtype: bool
            """
            return self._forcedApply

        def resetForcedApply(self):
            """resets the forcedApply mode (i.e.: sets it to False)"""
            self.setForcedApply(False)

        @deprecation_decorator(rel="4.0", alt="notifyValueChanged")
        def valueChanged(self, *args):
            return self.notifyValueChanged(*args)

        def notifyValueChanged(self, *args):
            """Subclasses should connect some particular signal to this method for
            indicating that something has changed.
            e.g., a QLineEdit should connect its "textChanged" signal...

            :param args: arbitrary signal parameters (ignored in this
                basic implementation)
            """
            self.emitValueChanged()
            if self._autoApply:
                self.writeValue()

        def writeValue(self, forceApply=False):
            """Writes the value to the attribute, either by applying pending
            operations or (if the ForcedApply flag is True), it writes directly
            when no operations are pending

            It emits the applied signal if apply is not aborted.

            :param forceApply: If True, it behaves as in forceApply mode (even if
                the forceApply mode is disabled by :meth:`setForceApply`)
            :type forceApply: bool
            """
            if self.hasPendingOperations():
                applied = self.safeApplyOperations()
                if applied:
                    self.applied.emit()
                return

            # maybe we want to force an apply even if there are no pending ops...
            kmods = Qt.QCoreApplication.instance().keyboardModifiers()
            controlpressed = bool(kmods & Qt.Qt.ControlModifier)
            if self.getForcedApply() or forceApply or controlpressed:
                self.forceApply()

        def forceApply(self):
            """It (re)applies the value regardless of pending operations.
            WARNING: USE WITH CARE. In most cases what you need is to make sure
            that pending operations are properly created, not calling this method

            It emits the applied signal if apply is not aborted.

            .. seealso: :meth:`setForceApply` and :meth:`writeValue`
            """
            try:
                v = self.getValue()
                op = WriteAttrOperation(
                    self.getModelObj(), v, self.getOperationCallbacks()
                )
                op.setDangerMessage(self.getDangerMessage())
                applied = self.safeApplyOperations([op])
                if applied:
                    self.applied.emit()
                self.info("Force-Applied value = %s" % str(v))
            except Exception:
                self.error("Unexpected exception in forceApply")
                self.traceback()

        def handleEvent(self, src, evt_type, evt_value):
            """reimplemented from :class:`TaurusBaseWidget`"""
            if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic):
                self.emitValueChanged()

        def postAttach(self, *, key=_DEFAULT):
            """reimplemented from :class:`TaurusBaseWidget` to update the
            widget value using the default model's wvalue

            :param key: the model key. Defaults to first element of .modelKeys
            :type key: object
            """
            key = self._checkModelKey(key)
            TaurusBaseWidget.postAttach(self, key=key)

            # only update value when it is the default model being attached
            if key == self.modelKeys[0] and self.isAttached():
                try:
                    v = self.getModelValueObj().wvalue
                except Exception:
                    v = None
                self.setValue(v)

        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
        # Pending operations related methods
        # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

        def resetPendingOperations(self):
            """reimplemented from :class:`TaurusBaseWidget`"""
            if self.isAttached():
                try:
                    v = self.getModelValueObj().wvalue
                except Exception:
                    v = None
                self.setValue(v)
            TaurusBaseWidget.resetPendingOperations(self)
            self.updateStyle()

        def updatePendingOperations(self):
            """reimplemented from :class:`TaurusBaseWidget`"""
            model = self.getModelObj()
            try:
                model_value = model.getValueObj().wvalue
                wigdet_value = self.getValue()
                if model.areStrValuesEqual(model_value, wigdet_value):
                    self._operations = []
                else:
                    operation = WriteAttrOperation(
                        model, wigdet_value, self.getOperationCallbacks()
                    )
                    operation.setDangerMessage(self.getDangerMessage())
                    self._operations = [operation]
            except Exception:
                self._operations = []
            self.updateStyle()

        def getOperationCallbacks(self):
            """returns the operation callbacks (i.e., a sequence of methods that
            will be called after an operation is executed (this default
            implementation it returns an empty list).

            :return: list of operations
            :rtype: sequence<callable>
            """
            return []

        def getDisplayValue(self, cache=True, fragmentName=None, *, key=_DEFAULT):
            """Reimplemented from class:`TaurusBaseWidget`"""
            # --------------------------------------------------------------
            # The widgets inheriting from this class interact with
            # writable models and therefore the fragmentName should fall back to
            # 'wvalue' instead of 'rvalue'.
            # But changing it now is delicate due to risk of introducing API
            # incompatibilities for widgets already assuming the current default.
            # So instead of reimplementing it here, the fix was constrained to
            # TaurusValueLineEdit.getDisplayValue()
            # TODO: Consider reimplementing this to use wvalue by default
            return TaurusBaseWidget.getDisplayValue(
                self, cache=cache, fragmentName=fragmentName, key=key
            )

            # --------------------------------------------------------------

        def getValue(self):
            """
            This method must be implemented in derived classes to return
            the value to be written. Note that this may differ
            from the displayed value (e.g. for a numeric value being
            edited by a QLineEdit-based widget, the displayed value will
            be a string while getValue will return a number)
            """
            raise NotImplementedError(
                "Not allowed to call TaurusBaseWritableWidget.getValue()"
            )

        def setValue(self, v):
            """
            This method must be implemented in derived classes to provide
            a (widget-specific) way of updating the displayed value based
            on a given attribute value

            # noqa: DAR401

            :param v: The attribute value
            """
            raise NotImplementedError(
                "Not allowed to call TaurusBaseWritableWidget.setValue()"
            )

        def updateStyle(self):
            """reimplemented from :class:`TaurusBaseWidget`"""
            if self._autoTooltip:
                toolTip = self.getFormatedToolTip()
                if self.hasPendingOperations():
                    v_str = str(self.getValue())
                    model_v_str = getattr(
                        self.getModelValueObj(), "wvalue", "-----"
                    )
                    toolTip += (
                        "<hr/>Displayed value (%s) differs from applied value (%s)"
                        % (v_str, model_v_str)
                    )
                self.setToolTip(toolTip)

        @classmethod
        def getQtDesignerPluginInfo(cls):
            """reimplemented from :class:`TaurusBaseWidget`"""
            ret = TaurusBaseWidget.getQtDesignerPluginInfo()
            ret["group"] = "Taurus Input"
            return ret