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


[docs] 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 {}
[docs] 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
[docs] @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
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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()
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] def contextMenuEvent(self, event): """ DEPRECATED: Until v4.5.3a, the default implementation of contextMenuEvent showed the content of taurusMenu as a context menu. But this resulted in unwanted behaviour when the widget already implemented its own context menu (see https://gitlab.com/taurus-org/taurus/-/issues/905 ) Therefore this feature was disabled in 4.5.3a. If you still want to show the contents of taurusMenu as a context menu, you can explicitly reimplement the contextMenuEvent method as:: def contextMenuEvent(self, event): self.taurusMenu.exec_(event.globalPos()) """ if self.taurusMenu is not None: if self.__explicitPopupMenu: # bck-compat: show taurusMenu as a contextMenu if it was # explicitly created via the (deprecated) "setTaurusPopupMenu" # API self.taurusMenu.exec_(event.globalPos()) return else: self.deprecated( dep="taurusMenu context Menu API", alt="custom contextMenuEvent to show taurusMenu", rel="4.5.3a", ) event.ignore()
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Mandatory methods to be implemented in subclass implementation # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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()
[docs] def getEventBufferPeriod(self): """Returns the event buffer period :return: period (in s). 0 means event buffering is disabled. :rtype: float """ return self._eventBufferPeriod
[docs] 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)
[docs] 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))
[docs] 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 = {}
[docs] 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)
[docs] 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
[docs] 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)
[docs] 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
[docs] 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)
[docs] 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
[docs] def isPaused(self): """Return the current pause state :return: wheater or not the widget is paused :rtype: bool """ return self._isPaused
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Model class methods # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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)
[docs] 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]
[docs] 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()
[docs] 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()
[docs] 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]
[docs] 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()
[docs] 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)
[docs] 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)
[docs] 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
[docs] 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)
[docs] 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
[docs] 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 )
[docs] 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()
[docs] 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
[docs] def resetFormat(self): """Reset the internal format string. It forces a recalculation in the next call to :meth:`displayValue`. """ self._format = None
[docs] 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)
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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]
[docs] 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] def getModelInConfig(self): return self._modelInConfig
[docs] def resetModelInConfig(self): return self.setModelInConfig(False)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Pending operations related methods # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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)
[docs] 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
[docs] def getPendingOperations(self): """Returns the sequence of pending operations :return: a list of pending operations :rtype: sequence<taurus.core.taurusoperation.TaurusOperation> """ return self._operations
[docs] def resetPendingOperations(self): """Clears the list of pending operations""" self._operations = []
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] def setForceDangerousOperations(self, yesno): """Forces/clears the dangerous operations :param yesno: force or not the dangerous operations :type yesno: bool """ self._forceDangerousOperations = yesno
[docs] def getForceDangerousOperations(self): """Returns if apply dangerous operations is forced :return: wheter or not apply dangerous operations is forced :rtype: bool """ return self._forceDangerousOperations
[docs] def resetForceDangerousOperations(self): """Clears forcing apply dangerous operations""" self.setForceDangerousOperations(False)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Standard Qt properties # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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]
[docs] 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 )
[docs] 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()
[docs] @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()
[docs] 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
[docs] def resetShowQuality(self): """Resets the show quality to self.__class__._showQuality""" self.setShowQuality(self.__class__._showQuality)
[docs] @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()
[docs] def getShowText(self): """Returns if showing the display value :return: True if showing the display value or False otherwise :rtype: bool """ return self._showText
[docs] 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
[docs] 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
[docs] 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
[docs] def resetModifiableByUser(self): """Equivalent to setModifiableByUser(self.__class__._modifiableByUser)""" self.setModifiableByUser(self.__class__._modifiableByUser)
[docs] def resetAutoProtectOperation(self): """Resets protecting operations""" self.setAutoProtectOperation(True)
[docs] 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
[docs] 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
[docs] 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
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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
[docs] @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)
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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)
[docs] 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" )
[docs] 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()
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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). # # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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)
[docs] 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]
[docs] 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
[docs] 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
[docs] 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
[docs] 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()
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] 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
[docs] 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
[docs] def resetDragEnabled(self): """Equivalent to setDragEnabled(self.__class__._dragEnabled)""" self.setModifiableByUser(self.__class__._dragEnabled)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Pending operations related methods: default implementation # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] @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, }
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] def getModelClass(self, *, key=_DEFAULT): """reimplemented from :class:`TaurusBaseWidget`""" return TaurusAttribute
[docs] def isReadOnly(self): """reimplemented from :class:`TaurusBaseWidget`""" return False
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # QT properties # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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
[docs] def getAutoApply(self): """whether autoApply mode is enabled or not. :return: True if autoApply is enabled. False otherwise :rtype: bool """ return self._autoApply
[docs] def resetAutoApply(self): """resets the autoApply mode (i.e.: sets it to False)""" self.setAutoApply(False)
[docs] 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
[docs] def getForcedApply(self): """whether forcedApply mode is enabled or not. :return: True if forceApply is enabled. False otherwise :rtype: bool """ return self._forcedApply
[docs] 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)
[docs] 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()
[docs] 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()
[docs] 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()
[docs] def handleEvent(self, src, evt_type, evt_value): """reimplemented from :class:`TaurusBaseWidget`""" if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic): self.emitValueChanged()
[docs] 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 # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs] 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()
[docs] 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()
[docs] 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 []
[docs] 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 )
# --------------------------------------------------------------
[docs] 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()" )
[docs] 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()" )
[docs] 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)
[docs] @classmethod def getQtDesignerPluginInfo(cls): """reimplemented from :class:`TaurusBaseWidget`""" ret = TaurusBaseWidget.getQtDesignerPluginInfo() ret["group"] = "Taurus Input" return ret