Source code for taurus.qt.qtgui.panel.taurusmodelchooser

#!/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)) 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 setButtonsPos(self, buttonsPos): # we must delete the previous layout before we can set a new one currlayout = self.layout() if currlayout is not None: currlayout.deleteLater() Qt.QCoreApplication.sendPostedEvents( currlayout, Qt.QEvent.DeferredDelete ) # add to layout if buttonsPos is None: self.setLayout(Qt.QVBoxLayout()) self.layout().addWidget(self._deviceTree) elif buttonsPos == Qt.Qt.BottomToolBarArea: self._toolbar.setOrientation(Qt.Qt.Horizontal) self.setLayout(Qt.QVBoxLayout()) self.layout().addWidget(self._deviceTree) self.layout().addWidget(self._toolbar) elif buttonsPos == Qt.Qt.TopToolBarArea: self._toolbar.setOrientation(Qt.Qt.Horizontal) self.setLayout(Qt.QVBoxLayout()) self.layout().addWidget(self._toolbar) self.layout().addWidget(self._deviceTree) elif buttonsPos == Qt.Qt.LeftToolBarArea: self._toolbar.setOrientation(Qt.Qt.Vertical) self.setLayout(Qt.QHBoxLayout()) self.layout().addWidget(self._toolbar) self.layout().addWidget(self._deviceTree) elif buttonsPos == Qt.Qt.RightToolBarArea: self._toolbar.setOrientation(Qt.Qt.Vertical) self.setLayout(Qt.QHBoxLayout()) self.layout().addWidget(self._deviceTree) self.layout().addWidget(self._toolbar) else: raise ValueError("Invalid buttons position")
[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)) 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)