#!/usr/bin/env python
# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus. If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################
"""
AttributeChooser.py: widget for choosing (a list of) attributes from a tango DB
"""
import sys
import pkg_resources
import taurus
from taurus.external.qt import Qt, QtCore
from taurus.external.qt.compat import PY_OBJECT
import taurus.core
from taurus.qt.qtgui.container import TaurusWidget
from taurus.qt.qtgui.tree import TaurusDbTreeWidget
from taurus.core.util.containers import CaselessList
from .taurusmodellist import TaurusModelList
[docs]
class TaurusModelSelector(Qt.QTabWidget):
"""TaurusModelSelector is a QTabWidget container for
TaurusModelSelectorItem.
"""
# TODO add action to add new TaurusModelSelectorItem
# TODO add mechanism to allow manual plugin activation
# (instead of relying on installation)
modelsAdded = Qt.pyqtSignal(PY_OBJECT)
def __init__(self, parent=None):
Qt.QTabWidget.__init__(self, parent=parent)
self.currentChanged.connect(self.__setTabItemModel)
# ---------------------------------------------------------------------
# Note: this is an experimental feature
# It may be removed or changed in future releases
# Discover the taurus.modelselector plugins
ep_group_name = "taurus.model_selector.items"
for ep in pkg_resources.iter_entry_points(ep_group_name):
try:
ms_class = ep.load()
ms_item = ms_class(parent=self)
self.__addItem(ms_item, ep.name)
except Exception as e:
err = "Invalid TaurusModelSelectorItem plugin: {}\n{}".format(
ep.module_name, e
)
taurus.warning(err)
# ---------------------------------------------------------------------
def __setTabItemModel(self):
w = self.currentWidget()
c = self.cursor()
try:
if not w.model:
self.setCursor(QtCore.Qt.WaitCursor)
w.setModel(w.default_model)
except Exception as e:
taurus.warning("Problem setting up selector: %r", e)
finally:
self.setCursor(c)
def __addItem(self, widget, name, model=None):
if model is not None:
widget.default_model = model
widget.modelsAdded.connect(self.modelsAdded)
self.addTab(widget, name)
[docs]
class TaurusModelSelectorItem(TaurusWidget):
"""Base class for ModelSelectorItem.
It defines the minimal API to be defined in the specialization
"""
modelsAdded = Qt.pyqtSignal(PY_OBJECT)
_dragEnabled = True
# TODO add action for setModel
def __init__(self, parent=None, **kwargs):
TaurusWidget.__init__(self, parent)
self._default_model = None
[docs]
def getSelectedModels(self):
raise NotImplementedError(
(
"getSelectedModels must be implemented"
+ " in TaurusModelSelectorItem subclass"
)
)
[docs]
def getModelMimeData(self):
"""Reimplemented from TaurusBaseComponent"""
models = self.getSelectedModels()
md = Qt.QMimeData()
# md.setText(", ".join(models))
md.setText(" ".join(models))
models_bytes = [bytes(m, encoding="utf-8") for m in models]
md.setData(
taurus.qt.qtcore.mimetypes.TAURUS_MODEL_LIST_MIME_TYPE,
b"\r\n".join(models_bytes),
)
return md
def _get_default_model(self):
"""
Reimplement to return a default model to initialize the widget
"""
raise NotImplementedError(
(
"default_model must be implemented"
+ " in TaurusModelSelectorItem subclass"
)
)
def _set_default_model(self, model):
"""
Set default model to initialize the widget
"""
self._default_model = model
# Reimplement this property
default_model = property(fget=_get_default_model, fset=_set_default_model)
[docs]
class TaurusModelSelectorTree(TaurusModelSelectorItem):
addModels = Qt.pyqtSignal("QStringList")
def __init__(
self, parent=None, selectables=None, buttonsPos=None, designMode=None
):
TaurusModelSelectorItem.__init__(self, parent)
if selectables is None:
selectables = [
taurus.core.taurusbasetypes.TaurusElementType.Attribute,
taurus.core.taurusbasetypes.TaurusElementType.Member,
taurus.core.taurusbasetypes.TaurusElementType.Device,
]
self._selectables = selectables
# tree
self._deviceTree = TaurusDbTreeWidget(
perspective=taurus.core.taurusbasetypes.TaurusElementType.Device
)
self._deviceTree.getQModel().setSelectables(self._selectables)
# toolbar
self._toolbar = Qt.QToolBar("TangoSelector toolbar")
self._toolbar.setIconSize(Qt.QSize(16, 16))
self._toolbar.setFloatable(False)
self._addSelectedAction = self._toolbar.addAction(
Qt.QIcon.fromTheme("list-add"), "Add selected", self.onAddSelected
)
# defines the layout
self.setButtonsPos(buttonsPos)
self.modelChanged.connect(self._deviceTree.setModel)
[docs]
def getSelectedModels(self):
selected = []
try:
from taurus.core.tango.tangodatabase import (
TangoDevInfo,
TangoAttrInfo,
)
except Exception:
return selected
# TODO: Tango-centric
for item in self._deviceTree.selectedItems():
nfo = item.itemData()
if isinstance(nfo, TangoDevInfo):
selected.append(nfo.fullName())
elif isinstance(nfo, TangoAttrInfo):
selected.append(
"%s/%s" % (nfo.device().fullName(), nfo.name())
)
else:
self.info("Unknown item '%s' in selection" % repr(nfo))
return selected
[docs]
def onAddSelected(self):
self.addModels.emit(self.getSelectedModels())
[docs]
def treeView(self):
return self._deviceTree.treeView()
[docs]
@classmethod
def getQtDesignerPluginInfo(cls):
ret = TaurusWidget.getQtDesignerPluginInfo()
ret["module"] = "taurus.qt.qtgui.panel"
ret["icon"] = "designer:listview.png"
ret["container"] = False
ret["group"] = "Taurus Views"
return ret
class TangoModelSelectorItem(TaurusModelSelectorTree):
"""A taurus model selector item for Tango models"""
# TODO: Tango-centric (move to Taurus-Tango plugin)
def __init__(
self,
parent=None,
selectables=None,
buttonsPos=Qt.Qt.RightToolBarArea,
designMode=None,
):
TaurusModelSelectorTree.__init__(
self,
parent=parent,
selectables=selectables,
buttonsPos=buttonsPos,
designMode=designMode,
)
def onAddSelected(self):
"""
Reimplemented from TaurusModelSelectorTree to emit modelsAdded
signal instead of addModels
"""
self.modelsAdded.emit(self.getSelectedModels())
def _get_default_model(self):
"""Reimplemented from TaurusModelSelectorItem"""
if self._default_model is None:
f = taurus.Factory("tango")
self._default_model = f.getAuthority().getFullName()
return self._default_model
default_model = property(
fget=_get_default_model,
fset=TaurusModelSelectorTree._set_default_model,
)
[docs]
class TaurusModelChooser(TaurusWidget):
"""A widget that allows the user to select a list of models from a tree
representing devices and attributes from a Tango server.
The user selects models and adds them to a list. Then the user should click
on the update button to notify that the selection is ready.
signals::
- "updateModels" emitted when the user clicks on the update button. It
passes a list<str> of models that have been selected.
"""
updateModels = Qt.pyqtSignal("QStringList")
UpdateAttrs = Qt.pyqtSignal(["QStringList"], ["QMimeData"])
def __init__(
self,
parent=None,
selectables=None,
host=None,
designMode=None,
singleModel=False,
):
"""Creator of TaurusModelChooser
:param parent: parent for the dialog
:type parent: QObject
:param selectables: if passed, only elements of the tree whose type is
in the list will be selectable.
:type selectables: list<TaurusElementType>
:param host: Tango host to be explored by the chooser
:type host: QObject
:param designMode: needed for taurusdesigner but ignored here
:type designMode: bool
:param singleModel: If True, the selection will be of just one model.
Otherwise (default) a list of models can be selected
:type singleModel: bool
"""
TaurusWidget.__init__(self, parent)
if host is None:
try: # TODO: Tango-centric!
host = taurus.Factory("tango").getAuthority().getFullName()
except Exception as e:
taurus.info("Cannot populate Tango Tree: %r", e)
self._allowDuplicates = False
self.setLayout(Qt.QVBoxLayout())
self.tree = TaurusModelSelectorTree(
selectables=selectables, buttonsPos=Qt.Qt.BottomToolBarArea
)
self.tree.setModel(host)
self.list = TaurusModelList()
self.list.setSelectionMode(Qt.QAbstractItemView.ExtendedSelection)
applyBT = Qt.QToolButton()
applyBT.setToolButtonStyle(Qt.Qt.ToolButtonTextBesideIcon)
applyBT.setText("Apply")
applyBT.setIcon(Qt.QIcon("status:available.svg"))
self.setSingleModelMode(singleModel)
# toolbar
self._toolbar = self.tree._toolbar
self._toolbar.addAction(self.list.removeSelectedAction)
self._toolbar.addAction(self.list.removeAllAction)
self._toolbar.addAction(self.list.moveUpAction)
self._toolbar.addAction(self.list.moveDownAction)
self._toolbar.addSeparator()
self._toolbar.addWidget(applyBT)
self.layout().addWidget(self.tree)
self.layout().addWidget(self.list)
# Workaround for UseSetParentModel issues
self.modelChanged.connect(self.tree.setModel)
# connections:
self.tree.addModels.connect(self.addModels)
applyBT.clicked.connect(self._onUpdateModels)
[docs]
def getListedModels(self, asMimeData=False):
"""returns the list of models that have been added
:param asMimeData: If False (default), the return value will be a list
of models. If True, the return value is a `QMimeData` containing at
least `TAURUS_MODEL_LIST_MIME_TYPE` and `text/plain` MIME types. If
only one model was selected, the mime data also contains a
TAURUS_MODEL_MIME_TYPE.
:type asMimeData: bool
:return: the type of return depends on the value of `asMimeData`
:rtype: list<str> or QMimeData
"""
models = self.list.getModelList()
if self.isSingleModelMode():
models = models[:1]
if asMimeData:
md = Qt.QMimeData()
md.setData(
taurus.qt.qtcore.mimetypes.TAURUS_MODEL_LIST_MIME_TYPE,
bytes("\r\n".join(models), encoding="utf8"),
)
# md.setText(", ".join(models))
md.setText(" ".join(models))
if len(models) == 1:
md.setData(
taurus.qt.qtcore.mimetypes.TAURUS_MODEL_MIME_TYPE,
bytes(models[0], encoding="utf8"),
)
return md
return models
[docs]
def setListedModels(self, models):
"""adds the given list of models to the widget list"""
self.list.model().clearAll()
self.list.addModels(models)
[docs]
def resetListedModels(self):
"""equivalent to setListedModels([])"""
self.list.model().clearAll()
[docs]
def updateList(self, attrList):
"""for backwards compatibility with AttributeChooser only. Use
:meth:`setListedModels` instead
"""
self.info(
"ModelChooser.updateList() is provided for "
+ "backwards compatibility only. Use setListedModels() instead"
)
self.setListedModels(attrList)
[docs]
def addModels(self, models):
"""Add given models to the selected models list"""
if len(models) == 0:
models = [""]
if self.isSingleModelMode():
self.resetListedModels()
if self._allowDuplicates:
self.list.addModels(models)
else:
listedmodels = CaselessList(self.getListedModels())
for m in models:
if m not in listedmodels:
listedmodels.append(m)
self.list.addModels([m])
[docs]
def onRemoveSelected(self):
"""
Remove the list-selected models from the list
"""
self.list.removeSelected()
def _onUpdateModels(self):
models = self.getListedModels()
self.updateModels.emit(models)
if (
taurus.core.taurusbasetypes.TaurusElementType.Attribute
in self.tree._selectables
):
# for backwards compatibility with the old AttributeChooser
self.UpdateAttrs.emit(models)
[docs]
def setSingleModelMode(self, single):
"""sets whether the selection should be limited to just one model
(single=True) or not (single=False)
"""
if single:
self.tree.treeView().setSelectionMode(
Qt.QAbstractItemView.SingleSelection
)
else:
self.tree.treeView().setSelectionMode(
Qt.QAbstractItemView.ExtendedSelection
)
self._singleModelMode = single
[docs]
def isSingleModelMode(self):
"""returns True if the selection is limited to just one model. Returns
False otherwise.
:return:
:rtype: bool
"""
return self._singleModelMode
[docs]
def resetSingleModelMode(self):
"""equivalent to setSingleModelMode(False)"""
self.setSingleModelMode(self, False)
[docs]
@staticmethod
def modelChooserDlg(
parent=None,
selectables=None,
host=None,
asMimeData=False,
singleModel=False,
windowTitle="Model Chooser",
listedModels=None,
):
"""Static method that launches a modal dialog containing a
TaurusModelChooser
:param parent: parent for the dialog
:type parent: QObject
:param selectables: if passed, only elements of the tree whose type is
in the list will be selectable.
:type selectables: list<TaurusElementType>
:param host: Tango host to be explored by the chooser
:type host: QObject
:param asMimeData: If False (default), a list of models will be.
returned. If True, a `QMimeData` object will be returned instead.
See :meth:`getListedModels` for a detailed description of this
QMimeData object.
:type asMimeData: bool
:param singleModel: If True, the selection will be of just one model.
Otherwise (default) a list of models can be selected
:type singleModel: bool
:param windowTitle: Title of the dialog (default="Model Chooser")
:type windowTitle: str
:param listedModels: List of model names for initializing the model
list
:type listedModels: list<str>
:return: Returns a models,ok tuple. models can be either a list of
models or a QMimeData object, depending on `asMimeData`. ok is True
if the dialog was accepted (by clicking on the "update" button) and
False otherwise
:rtype: list,bool or QMimeData,bool
"""
dlg = Qt.QDialog(parent)
dlg.setWindowTitle(windowTitle)
dlg.setWindowIcon(Qt.QIcon("logos:taurus.png"))
layout = Qt.QVBoxLayout()
w = TaurusModelChooser(
parent=parent,
selectables=selectables,
host=host,
singleModel=singleModel,
)
if listedModels is not None:
w.setListedModels(listedModels)
layout.addWidget(w)
dlg.setLayout(layout)
w.updateModels.connect(dlg.accept)
dlg.exec_()
return w.getListedModels(asMimeData=asMimeData), (
dlg.result() == dlg.Accepted
)
[docs]
@classmethod
def getQtDesignerPluginInfo(cls):
ret = TaurusWidget.getQtDesignerPluginInfo()
ret["module"] = "taurus.qt.qtgui.panel"
ret["icon"] = "designer:listview.png"
ret["container"] = False
ret["group"] = "Taurus Views"
return ret
singleModelMode = Qt.pyqtProperty(
"bool", isSingleModelMode, setSingleModelMode, resetSingleModelMode
)
def main(args):
if len(sys.argv) > 1:
host = sys.argv[1]
else:
host = None
_ = Qt.QApplication(args)
print(TaurusModelChooser.modelChooserDlg(host=host))
sys.exit()
if __name__ == "__main__":
main(sys.argv)