#!/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 a set of basic taurus widgets based on QCheckBox
"""
__docformat__ = "restructuredtext"
from taurus.external.qt import Qt
from taurus.core import DataType, TaurusEventType
from taurus.core.taurusattribute import TaurusAttribute
from taurus.qt.qtgui.base import TaurusBaseWidget, TaurusBaseWritableWidget
from taurus.core.util import eventfilters
import numpy
[docs]
class TaurusValueComboBox(Qt.QComboBox, TaurusBaseWritableWidget):
"""This widget shows a combobox that offers a limited choice of values that
can be set on an attribute.
"""
def __init__(self, parent=None, designMode=False):
self._previousModelName = None
self._lastValueByUser = None
name = self.__class__.__name__
self.call__init__wo_kw(Qt.QComboBox, parent)
self.call__init__(
TaurusBaseWritableWidget, name, designMode=designMode
)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# Helper methods
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
def _setCurrentIndex(self, index):
bs = self.blockSignals(True)
try:
self.setCurrentIndex(index)
finally:
self.blockSignals(bs)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# TaurusBaseWidget overwriting
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs]
def preAttach(self, *, key=""):
"""reimplemented from :class:`TaurusBaseWritableWidget`. The
key argument is there for API compatibility, but it is ignored
byt the current implementation
"""
TaurusBaseWritableWidget.preAttach(self)
self.currentIndexChanged.connect(self.writeIndexValue)
[docs]
def postDetach(self, *, key=""):
"""reimplemented from :class:`TaurusBaseWritableWidget`. The
key argument is there for API compatibility, but it is ignored
byt the current implementation"""
TaurusBaseWritableWidget.postDetach(self)
try:
self.currentIndexChanged.disconnect(self.writeIndexValue)
except (RuntimeError, TypeError):
# In new style-signal if a signal is disconnected without
# previously was connected it, it raises a TypeError (PyQt)
# or RuntimeError (PySide)
pass
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# TaurusBaseWritableWidget overwriting / Pending operations
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs]
def getValue(self):
"""
Get the value that the widget is displaying now, not the value
of the attribute.
"""
model = self.getModelObj()
if model is None:
return None
dtype = model.type
new_value = self.itemData(self.currentIndex())
if new_value is None:
return None
if dtype == DataType.Integer:
func = int
elif dtype == DataType.Float:
func = float
elif dtype == DataType.String:
func = str
elif dtype == DataType.Boolean:
func = bool
else:
return None
# TODO: check if this implementation is correct (func is not used)
_ = func # hack to avoid flake8 F841 errors regarding func (see to-do)
return new_value
[docs]
def setValue(self, value):
"""
Set the value for the widget to display, not the value of the
attribute.
"""
index = self.findData(value, pymatch=True)
self._setCurrentIndex(index)
[docs]
def findData(self, data, **kwargs):
"""
Reimplemented from :meth:`Qt.QComboBox.findData` to accept
the extra `pymatch` keyword arg. If `pymatch` is True, the
match will be attempted using python's `==` operator.
This is required to bypass some limitations imposed by C++'s QVariant .
By default, pymatch is False and behaves just as
:meth:`Qt.QComboBox.findData`
"""
pymatch = kwargs.pop("pymatch", False)
index = Qt.QComboBox.findData(self, data, **kwargs)
if pymatch and index == -1:
for i in range(self.count()):
if numpy.all(self.itemData(i) == data):
index = i
break
return index
[docs]
def updateStyle(self):
"""reimplemented from :class:`TaurusBaseWritableWidget`"""
if self.hasPendingOperations():
self.setStyleSheet(
"TaurusValueComboBox {color: blue; font-weight: bold;}"
)
else:
self.setStyleSheet("TaurusValueComboBox {}")
super(TaurusValueComboBox, self).updateStyle()
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# signals, gui events... things related to "write" in the end
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs]
@Qt.pyqtSlot(int, name="currentIndexChanged")
def writeIndexValue(self, index):
"""slot called to emit a valueChanged signal when the currentIndex is
changed (and trigger a write if AutoApply is enabled)
"""
self.emitValueChanged()
if self.getAutoApply():
self.writeValue()
[docs]
def keyPressEvent(self, event):
"""reimplemented to trigger a write when Enter (or Return) key is
pressed
"""
if event.key() in [Qt.Qt.Key_Return, Qt.Qt.Key_Enter]:
self.writeValue()
event.accept()
else:
return Qt.QComboBox.keyPressEvent(self, event)
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# TaurusValueComboBox own interface
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs]
def setValueNames(self, names):
"""
Sets the correspondence between the values to be applied and their
associated text to show in the combobox.
:param names: A sequence of (name,value) tuples, where each attribute
value gets a name for display
:type names: sequence<tuple>
"""
bs = self.blockSignals(True)
self.clear()
self.blockSignals(bs)
self.addValueNames(names)
[docs]
def addValueNames(self, names):
"""
Add new value-name associations to the combobox.
... seealso: :meth:`setValueNames`
:param names: A sequence of (name,value) tuples, where each attribute
value gets a name for display
:type names: sequence<tuple>
"""
bs = self.blockSignals(True)
try:
for k, v in names:
self.addItem(k, v)
# Ok, now we should see if the current value matches any
# of the newly added names. This is kinda a refresh:
mv = self.getModelValueObj()
if mv is not None:
self.setValue(mv.wvalue)
finally:
self.blockSignals(bs)
self.emitValueChanged()
[docs]
def getValueString(self, value, default="UNKNOWN(%s)"):
"""Returns the corresponding name in the combobox out of a value
(or a default value if not found).
:param value: value to look up
:param default: value in case it is not found. It accepts a '%s'
placeholder which will be substituted with str(value). It defaults
to 'UNKNOWN(%s)'.
:type default: str
"""
item = self.findData(value)
if item < 0:
if "%s" in default:
return default % str(value)
else:
return default
return str(self.itemText(item))
[docs]
def setQModel(self, *args, **kwargs):
"""access to :meth:`QCombobox.setModel`
.. seealso: :meth:`setModel`
"""
Qt.QComboBox.setModel(self, *args, **kwargs)
[docs]
def setModel(self, m, **kwargs):
"""Reimplemented from :meth:`TaurusBaseWritableWidget.setModel`"""
if isinstance(m, Qt.QAbstractItemModel):
self.warning(
"Deprecation warning: use setQModel() if you want to "
+ "set a Qt Item Model. The setModel() method "
+ "is reserved for Taurus models"
)
return Qt.QComboBox.setModel(self, m)
ret = TaurusBaseWritableWidget.setModel(self, m, **kwargs)
self.emitValueChanged()
return ret
[docs]
@classmethod
def getQtDesignerPluginInfo(cls):
"""reimplemented from :class:`TaurusBaseWritableWidget`"""
ret = TaurusBaseWritableWidget.getQtDesignerPluginInfo()
ret["module"] = "taurus.qt.qtgui.input"
ret["icon"] = "designer:combobox.png"
return ret
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# QT properties
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
model = Qt.pyqtProperty(
"QString",
TaurusBaseWidget.getModel,
TaurusBaseWidget.setModel,
TaurusBaseWidget.resetModel,
)
#: (deprecated))
useParentModel = Qt.pyqtProperty(
"bool",
TaurusBaseWidget.getUseParentModel,
TaurusBaseWidget.setUseParentModel,
TaurusBaseWidget.resetUseParentModel,
)
autoApply = Qt.pyqtProperty(
"bool",
TaurusBaseWritableWidget.getAutoApply,
TaurusBaseWritableWidget.setAutoApply,
TaurusBaseWritableWidget.resetAutoApply,
)
forcedApply = Qt.pyqtProperty(
"bool",
TaurusBaseWritableWidget.getForcedApply,
TaurusBaseWritableWidget.setForcedApply,
TaurusBaseWritableWidget.resetForcedApply,
)
[docs]
class TaurusAttrListComboBox(Qt.QComboBox, TaurusBaseWidget):
"""Combobox whose items reflect the items read from a 1D attribute of dtype
str
"""
def __init__(self, parent=None, designMode=False):
name = self.__class__.__name__
self.call__init__wo_kw(Qt.QComboBox, parent)
self.call__init__(TaurusBaseWidget, name)
self.insertEventFilter(eventfilters.IGNORE_CONFIG)
self.setSizeAdjustPolicy(Qt.QComboBox.AdjustToContents)
self.defineStyle()
self._lastAttrList = None
[docs]
def defineStyle(self):
"""Defines the initial style for the widget"""
self.updateStyle()
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# TaurusBaseWidget over writing
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs]
def getModelClass(self, **kwargs):
"""reimplemented from :class:`TaurusBaseWidget`"""
return TaurusAttribute
[docs]
def handleEvent(self, evt_src, evt_type, evt_value):
"""reimplemented from :class:`TaurusBaseWidget`"""
if evt_type == TaurusEventType.Error:
attrList = []
elif evt_src is None or evt_value is None:
attrList = []
else:
attrList = list(evt_value.rvalue)
attrList.sort()
if attrList != self._lastAttrList:
self._lastAttrList = attrList
self.clear()
self.addItems(attrList)
self.updateStyle()
[docs]
def updateStyle(self):
"""reimplemented from :class:`TaurusBaseWidget`"""
self.update()
[docs]
def setQModel(self, *args, **kwargs):
"""access to :meth:`QAbstractItemView.setModel`
.. seealso: :meth:`setModel`
"""
return Qt.QAbstractItemView.setModel(self, *args, **kwargs)
[docs]
def setModel(self, m, **kwargs):
"""reimplemented from :class:`TaurusBaseWidget`"""
if isinstance(m, Qt.QAbstractItemModel):
self.warning(
(
"Deprecation warning: use setQModel() if you"
+ " want to set a Qt Item Model. The setModel()"
+ " method is reserved for Taurus models"
)
)
return Qt.QAbstractItemView.setQModel(self, m)
return TaurusBaseWidget.setModel(self, m, **kwargs)
[docs]
@classmethod
def getQtDesignerPluginInfo(cls):
"""reimplemented from :class:`TaurusBaseWidget`"""
ret = TaurusBaseWidget.getQtDesignerPluginInfo()
ret["group"] = "Taurus Input"
ret["module"] = "taurus.qt.qtgui.input"
ret["icon"] = "designer:combobox.png"
return ret
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# QT properties
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
model = Qt.pyqtProperty(
"QString",
TaurusBaseWidget.getModel,
TaurusBaseWidget.setModel,
TaurusBaseWidget.resetModel,
)
#: (deprecated))
useParentModel = Qt.pyqtProperty(
"bool",
TaurusBaseWidget.getUseParentModel,
TaurusBaseWidget.setUseParentModel,
TaurusBaseWidget.resetUseParentModel,
)
# ###########################################################################
# Testing
# ###########################################################################
def _taurusAttrListTest():
"""tests taurusAttrList. Model: an attribute containing a list of
strings"""
from taurus.qt.qtgui.application import TaurusApplication
a = TaurusApplication(cmd_line_parser=None)
# model = sys.argv[1]
# model = "eval:['foo','bar']"
model = "sys/tg_test/1/string_spectrum"
w = TaurusAttrListComboBox()
w.setModel(model)
w.show()
return a.exec_()
def _taurusValueComboboxTest():
from taurus.qt.qtgui.application import TaurusApplication
"""tests TaurusValueCombobox
"""
# model = sys.argv[1]
names = [("name0", 0), ("name1", 1), ("name2", 2), ("name3", 3)]
a = TaurusApplication(cmd_line_parser=None)
w = Qt.QWidget()
w.setLayout(Qt.QVBoxLayout())
cs = []
for model in ["sys/tg_test/1/short_scalar"] * 2:
c = TaurusValueComboBox()
c.setModel(model)
c.addValueNames(names)
w.layout().addWidget(c)
cs.append(c)
# c.autoApply = True
w.show()
return a.exec_()
if __name__ == "__main__":
import sys
main = _taurusValueComboboxTest # uncomment to test TaurusValueCombobox
# main = _taurusAttrListTest # uncomment to testtaurusAttrList
sys.exit(main())