# 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

    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)

    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

        def getSelectedModels(self):
            raise NotImplementedError(
                (
                    "getSelectedModels must be implemented"
                    + " in TaurusModelSelectorItem subclass"
                )
            )

        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)

    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)

        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")

        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

        def onAddSelected(self):
            self.addModels.emit(self.getSelectedModels())

        def treeView(self):
            return self._deviceTree.treeView()

        @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,
        )

    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)

        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

        def setListedModels(self, models):
            """adds the given list of models to the widget list"""
            self.list.model().clearAll()
            self.list.addModels(models)

        def resetListedModels(self):
            """equivalent to setListedModels([])"""
            self.list.model().clearAll()

        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)

        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])

        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)

        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

        def isSingleModelMode(self):
            """returns True if the selection is limited to just one model. Returns
            False otherwise.

            :return:
            :rtype: bool
            """
            return self._singleModelMode

        def resetSingleModelMode(self):
            """equivalent to setSingleModelMode(False)"""
            self.setSingleModelMode(self, False)

        @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
            )

        @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)