# Source code for taurus.qt.qtcore.model.taurusdatabasemodel

    #!/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/>.
    #
    # ###########################################################################

    """This module provides widgets that display the database in a tree format
    """
    # TODO: tango-centric

    from taurus.external.qt import Qt
    from taurus.core.taurusbasetypes import TaurusElementType, TaurusDevState
    import taurus.qt.qtcore.mimetypes
    from .taurusmodel import (
        TaurusBaseTreeItem,
        TaurusBaseModel,
        TaurusBaseProxyModel,
    )

    __docformat__ = "restructuredtext"

    ElemType = TaurusElementType

    def getElementTypeIcon(*args, **kwargs):
        """Wrapper to prevent loading qtgui when this module is imported"""
        from taurus.qt.qtgui.icon import getElementTypeIcon

        return getElementTypeIcon(*args, **kwargs)

    def getElementTypeToolTip(*args, **kwargs):
        """Wrapper to prevent loading qtgui when this module is imported"""
        from taurus.qt.qtgui.icon import getElementTypeToolTip

        return getElementTypeToolTip(*args, **kwargs)

    def getElementTypeSize(*args, **kwargs):
        """Wrapper to prevent loading qtgui when this module is imported"""
        from taurus.qt.qtgui.icon import getElementTypeSize

        return getElementTypeSize(*args, **kwargs)

    def getDevStateIcon(*args, **kwargs):
        """Wrapper to prevent loading qtgui when this module is imported"""
        from taurus.qt.qtgui.icon import getDevStateIcon

        return getDevStateIcon(*args, **kwargs)

    def getDevStateToolTip(*args, **kwargs):
        """Wrapper to prevent loading qtgui when this module is imported"""
        from taurus.qt.qtgui.icon import getDevStateToolTip

        return getDevStateToolTip(*args, **kwargs)

    class TaurusTreeDbBaseItem(TaurusBaseTreeItem):
        try:
            # TODO: tango-centric
            import taurus.core.tango

            @staticmethod
            def DisplayFunc(obj):
                from taurus.core.tango.tangodatabase import TangoInfo

                return TangoInfo.name(obj)

        except Exception:
            pass

    class TaurusTreeDevicePartItem(TaurusTreeDbBaseItem):
        """A node designed to represent a 'part' (or totality) of a device name"""

        def data(self, index):
            column = index.column()
            if column > 0:
                return

            model = index.model()
            role = model.role(column, self.depth())

            if role == self.role():
                return self._itemData

        def role(self):
            return ElemType.Device

    class TaurusTreeDeviceDomainItem(TaurusTreeDevicePartItem):
        """A node designed to represent a the domain part of a device name"""

        DisplayFunc = str

        def role(self):
            return ElemType.Domain

    class TaurusTreeDeviceFamilyItem(TaurusTreeDevicePartItem):
        """A node designed to represent a the family part of a device name"""

        DisplayFunc = str

        def role(self):
            return ElemType.Family

    class TaurusTreeDeviceMemberItem(TaurusTreeDevicePartItem):
        """A node designed to represent a the member part of a device name"""

        DisplayFunc = str

        def role(self):
            return ElemType.Member

    class TaurusTreeSimpleDeviceItem(TaurusTreeDbBaseItem):
        """A node designed to represent a device (without any child nodes)"""

        def hasChildren(self):
            return False

        def childCount(self):
            return 0

        def data(self, index):
            column, model = index.column(), index.model()
            role = model.role(column, self.depth())
            obj = self.itemData()
            if role == ElemType.Device or role == ElemType.Name:
                return obj.name()
            elif role == ElemType.DeviceAlias:
                return obj.alias()
            elif role == ElemType.Server:
                return obj.server().name()
            elif role == ElemType.DeviceClass:
                return obj.klass().name()
            elif role == ElemType.Exported:
                return obj.state()
            elif role == ElemType.Host:
                return obj.host()
            elif role == ElemType.Domain:
                return obj.domain()
            elif role == ElemType.Family:
                return obj.family()
            elif role == ElemType.Member:
                return obj.member()

        def mimeData(self, index):
            return self.itemData().name()

        def role(self):
            return ElemType.Device

    class TaurusTreeDeviceItem(TaurusTreeDbBaseItem):
        """A node designed to represent a device"""

        def child(self, row):
            self.updateChilds()
            return super(TaurusTreeDeviceItem, self).child(row)

        def hasChildren(self):
            return True
            nb = super(TaurusTreeDeviceItem, self).childCount()
            if nb > 0:
                return True
            data = self.itemData()
            if data.state() != TaurusDevState.Ready:
                return False
            return True

        def childCount(self):
            nb = super(TaurusTreeDeviceItem, self).childCount()
            if nb > 0:
                return nb
            data = self.itemData()
            if data.state() != TaurusDevState.Ready:
                return 0
            self.updateChilds()
            return super(TaurusTreeDeviceItem, self).childCount()

        def updateChilds(self):
            if len(self._childItems) > 0:
                return
            for attr in self._itemData.attributes():
                c = TaurusTreeAttributeItem(self._model, attr, self)
                self.appendChild(c)
            return

        def data(self, index):
            column, model = index.column(), index.model()
            role = model.role(column, self.depth())
            obj = self.itemData()
            if role == ElemType.Device or role == ElemType.Name:
                return obj.name()
            elif role == ElemType.DeviceAlias:
                return obj.alias()
            elif role == ElemType.Server:
                return obj.server().name()
            elif role == ElemType.DeviceClass:
                return obj.klass().name()
            elif role == ElemType.Exported:
                return obj.state()
            elif role == ElemType.Host:
                return obj.host()
            elif role == ElemType.Domain:
                return obj.domain()
            elif role == ElemType.Family:
                return obj.family()
            elif role == ElemType.Member:
                return obj.member()

        def mimeData(self, index):
            return self.itemData().fullName()

        def role(self):
            return ElemType.Device

    class TaurusTreeAttributeItem(TaurusTreeDbBaseItem):
        """A node designed to represent an attribute"""

        def data(self, index):
            column, model = index.column(), index.model()
            role = model.role(column, self.depth())
            if role == ElemType.Attribute or role == ElemType.Name:
                data = self.itemData()
                data_info = data.info()
                ret = data.name()
                if data_info and hasattr(data_info, "label"):
                    ret = "'" + data_info.label + "' (" + ret + ")"
                return ret

        def toolTip(self, index):
            if index.column() > 0:
                return TaurusTreeDbBaseItem.toolTip(self, index)
            data = self.itemData()
            di = data.info()
            if di is None:
                return data.name()
            ret = '<TABLE border="0" cellpadding="1" cellspacing="0">'
            items = dict(
                name="'" + di.label + "' (" + data.name() + ")",
                description=di.description.replace("<", "&lt;").replace(
                    ">", "&gt;"
                ),
                unit=di.unit,
                limits="[%s, %s]" % (di.min_value, di.max_value),
                alarms="[%s, %s]" % (di.alarms.min_alarm, di.alarms.max_alarm),
                warnings="[%s, %s]"
                % (di.alarms.min_warning, di.alarms.max_warning),
            )

            for id, value in items.items():
                ret += (
                    '<TR><TD WIDTH="80" ALIGN="RIGHT" VALIGN="MIDDLE">'
                    + "<B>%s:</B></TD><TD>%s</TD></TR>"
                ) % (id.capitalize(), value)
            ret += "</TABLE>"
            return ret

        def mimeData(self, index):
            return self.itemData().fullName()

        def role(self):
            return ElemType.Attribute

    class TaurusTreeServerNameItem(TaurusTreeDbBaseItem):
        """A node designed to represent the server name part of a server"""

        DisplayFunc = str

        def data(self, index):
            column, model = index.column(), index.model()
            role = model.role(column, self.depth())

            if role == ElemType.ServerName or role == ElemType.Name:
                return self._itemData

        def role(self):
            return ElemType.ServerName

    class TaurusTreeServerItem(TaurusTreeDbBaseItem):
        """A node designed to represent a server"""

        def data(self, index):
            column, model = index.column(), index.model()
            role = model.role(column, self.depth())

            if role == ElemType.Server or role == ElemType.Name:
                return self._itemData.name()
            elif role == ElemType.ServerName:
                return self._itemData.serverName()
            elif role == ElemType.ServerInstance:
                return self._itemData.serverInstance()
            elif role == ElemType.Exported:
                return self._itemData.state()
            elif role == ElemType.Host:
                return self._itemData.host()

        def mimeData(self, index):
            return self.itemData().fullName()

        def role(self):
            return ElemType.Server

    class TaurusTreeFullServerItem(TaurusTreeDbBaseItem):
        """A node designed to represent a server"""

        def data(self, index):
            column, model = index.column(), index.model()
            role = model.role(column, self.depth())

            if role == ElemType.Server or role == ElemType.Name:
                return self._itemData.fullName()
            elif role == ElemType.ServerName:
                return self._itemData.serverName()
            elif role == ElemType.ServerInstance:
                return self._itemData.fullName()
            elif role == ElemType.Exported:
                return self._itemData.state()
            elif role == ElemType.Host:
                return self._itemData.host()

        def mimeData(self, index):
            return self.itemData().fullName()

        def role(self):
            return ElemType.Server

    class TaurusTreeDeviceClassItem(TaurusTreeDbBaseItem):
        """A node designed to represent a device class"""

        def data(self, index):
            column, model = index.column(), index.model()
            role = model.role(column, self.depth())

            if role == ElemType.Name or role == ElemType.DeviceClass:
                return self._itemData.name()

        def mimeData(self, index):
            return self.itemData().fullName()

        def role(self):
            return ElemType.DeviceClass

    class TaurusDbBaseModel(TaurusBaseModel):
        """The base class for all Taurus database Qt models.
        By default, this model represents a plain device perspective of the
        underlying database.
        """

        ColumnNames = "Device", "Alias", "Server", "Class", "Alive", "Host"
        ColumnRoles = (
            (ElemType.Device, ElemType.Device),
            ElemType.DeviceAlias,
            ElemType.Server,
            ElemType.DeviceClass,
            ElemType.Exported,
            ElemType.Host,
        )

        def createNewRootItem(self):
            return TaurusTreeDbBaseItem(self, self.ColumnNames)

        def refresh(self, refresh_source=False):
            data = self.dataSource()
            if refresh_source and data is not None:
                data.refreshCache()
            TaurusBaseModel.refresh(self, refresh_source=refresh_source)

        def roleIcon(self, taurus_role):
            return getElementTypeIcon(taurus_role)

        def columnIcon(self, column):
            return self.roleIcon(self.role(column))

        def roleToolTip(self, taurus_role):
            return getElementTypeToolTip(taurus_role)

        def columnToolTip(self, column):
            return self.roleToolTip(self.role(column))

        def roleSize(self, taurus_role):
            return getElementTypeSize(taurus_role)

        def columnSize(self, column):
            taurus_role = self.role(column)
            s = self.roleSize(taurus_role)
            return s

        def mimeTypes(self):
            return [
                "text/plain",
                taurus.qt.qtcore.mimetypes.TAURUS_MODEL_LIST_MIME_TYPE,
                taurus.qt.qtcore.mimetypes.TAURUS_MODEL_MIME_TYPE,
            ]

        def mimeData(self, indexes):
            ret = Qt.QMimeData()
            data = []
            for index in indexes:
                if not index.isValid():
                    continue
                # item = index.internalPointer()
                # data = item.mimeData(index)
                # stream << item.mimeData(index)
                tree_item = index.internalPointer()
                mime_data_item = tree_item.mimeData(index)
                if mime_data_item is None:
                    continue
                data.append(bytes(mime_data_item, encoding="utf8"))
            ret.setData(
                taurus.qt.qtcore.mimetypes.TAURUS_MODEL_LIST_MIME_TYPE,
                b"\r\n".join(data),
            )
            ret.setText(", ".join(map(str, data)))
            if len(data) == 1:
                ret.setData(
                    taurus.qt.qtcore.mimetypes.TAURUS_MODEL_MIME_TYPE, data[0]
                )
            return ret

        def pyData(self, index, role):
            if not index.isValid():
                return None

            item = index.internalPointer()
            column, depth = index.column(), item.depth()
            taurus_role = self.role(column, depth)

            ret = None
            if role == Qt.Qt.DisplayRole:
                if taurus_role != ElemType.Exported:
                    ret = item.data(index)
            elif role == Qt.Qt.DecorationRole:
                if column == 0:
                    ret = self.roleIcon(taurus_role)
                if taurus_role == ElemType.Exported:
                    state = item.data(index)
                    ret = getDevStateIcon(state)
            elif role == Qt.Qt.ToolTipRole:
                ret = item.toolTip(index)
                if ret is None:
                    data = item.data(index)
                    if data is not None:
                        if taurus_role == ElemType.Exported:
                            ret = getDevStateToolTip(data)
                        else:
                            ret = self.roleToolTip(taurus_role)
            # elif role == Qt.Qt.SizeHintRole:
            #    ret = self.columnSize(column)
            elif role == Qt.Qt.FontRole:
                ret = self.DftFont
            return ret

        def setupModelData(self, data):
            if data is None:
                return

            from taurus.core.tango.tangodatabase import TangoDatabase

            if isinstance(data, TangoDatabase):
                data = data.cache()
            devices = data.devices()

            rootItem = self._rootItem
            for dev_name in data.getDeviceNames():
                dev = devices[dev_name]
                devItem = TaurusTreeSimpleDeviceItem(self, dev, rootItem)
                rootItem.appendChild(devItem)

    class TaurusDbSimpleDeviceModel(TaurusDbBaseModel):
        """A Qt model that structures device elements in 1 level tree with
        device name as node leafs. This model contains only 1 column.
        """

        ColumnNames = ("Device",)
        ColumnRoles = ((ElemType.Device, ElemType.Device),)

    class TaurusDbSimpleDeviceAliasModel(TaurusDbBaseModel):
        """A Qt model that structures device elements in 1 level tree with
        device alias as node leafs. This model contains only 1 column.
        """

        ColumnNames = ("Alias",)
        ColumnRoles = ((ElemType.DeviceAlias, ElemType.DeviceAlias),)

        def setupModelData(self, data):
            if data is None:
                return

            from taurus.core.tango.tangodatabase import TangoDatabase

            if isinstance(data, TangoDatabase):
                data = data.cache()
            devices = data.devices()

            rootItem = self._rootItem
            for dev_name in data.getDeviceNames():
                dev = devices[dev_name]
                if dev.alias() is not None:
                    devItem = TaurusTreeSimpleDeviceItem(self, dev, rootItem)
                    rootItem.appendChild(devItem)

    class TaurusDbPlainDeviceModel(TaurusDbBaseModel):
        """A Qt model that structures device elements in 1 level tree. Device
        nodes will have attribute child nodes if the device is running.
        """

        ColumnNames = "Device", "Alias", "Server", "Class", "Alive", "Host"
        ColumnRoles = (
            (ElemType.Device, ElemType.Device, ElemType.Attribute),
            ElemType.DeviceAlias,
            ElemType.Server,
            ElemType.DeviceClass,
            ElemType.Exported,
            ElemType.Host,
        )

        def setupModelData(self, data):
            if data is None:
                return

            from taurus.core.tango.tangodatabase import TangoDatabase

            if isinstance(data, TangoDatabase):
                data = data.cache()
            devices = data.devices()

            rootItem = self._rootItem
            for dev_name in data.getDeviceNames():
                dev = devices[dev_name]
                devItem = TaurusTreeDeviceItem(self, dev, rootItem)
                rootItem.appendChild(devItem)

    class TaurusDbDeviceModel(TaurusDbBaseModel):
        """A Qt model that structures device elements in a 3 level tree organized
        as:

            - <domain>
            - <family>
            - <member>
        """

        ColumnRoles = (
            (
                ElemType.Device,
                ElemType.Domain,
                ElemType.Family,
                ElemType.Member,
                ElemType.Attribute,
            ),
            ElemType.DeviceAlias,
            ElemType.Server,
            ElemType.DeviceClass,
            ElemType.Exported,
            ElemType.Host,
        )

        def setupModelData(self, data):
            if data is None:
                return
            try:
                # TODO: Tango-centric
                # TODO: is this try needed?
                from taurus.core.tango.tangodatabase import TangoDatabase
            except ImportError:
                return
            if isinstance(data, TangoDatabase):
                data = data.deviceTree()

            rootItem = self._rootItem
            for domain in data:
                families = data[domain]
                domainItem = TaurusTreeDeviceDomainItem(
                    self, domain.upper(), rootItem
                )
                for family in families:
                    members = families[family]
                    familyItem = TaurusTreeDeviceFamilyItem(
                        self, family.upper(), domainItem
                    )
                    for member in members:
                        dev = members[member]
                        memberItem = TaurusTreeDeviceItem(
                            self, dev, parent=familyItem
                        )
                        familyItem.appendChild(memberItem)
                    domainItem.appendChild(familyItem)
                rootItem.appendChild(domainItem)

    class TaurusDbPlainServerModel(TaurusDbBaseModel):
        ColumnNames = "Server", "Alive", "Host"
        ColumnRoles = (
            (ElemType.Server, ElemType.ServerInstance),
            ElemType.Exported,
            ElemType.Host,
        )

        def setupModelData(self, data):
            if data is None:
                return

            from taurus.core.tango.tangodatabase import TangoDatabase

            if isinstance(data, TangoDatabase):
                data = data.cache()

            servers = data.servers()
            rootItem = self._rootItem

            for server_name, server in servers.items():
                serverInstanceItem = TaurusTreeFullServerItem(
                    self, server, rootItem
                )
                rootItem.appendChild(serverInstanceItem)

    class TaurusDbServerModel(TaurusDbBaseModel):
        """A Qt model that structures server elements in a tree organized
        as:

            - <Server name>
            - <Server instance>
            - <Class>
            - <Device>
            - <Attribute>
        """

        ColumnNames = "Server", "Alive", "Host"
        ColumnRoles = (
            (
                ElemType.Server,
                ElemType.ServerName,
                ElemType.ServerInstance,
                ElemType.DeviceClass,
                ElemType.Device,
                ElemType.Attribute,
            ),
            ElemType.Exported,
            ElemType.Host,
        )

        def setupModelData(self, data):
            if data is None:
                return

            from taurus.core.tango.tangodatabase import TangoDatabase

            if isinstance(data, TangoDatabase):
                data = data.cache()

            servers, klasses, devices = (
                data.servers(),
                data.klasses(),
                data.devices(),
            )
            rootItem = self._rootItem
            server_dict = {}

            server_names = data.getServerNames()
            for server_name in server_names:
                server = servers[server_name]
                name = server.serverName()
                # keep an instance
                instance = server.serverInstance()  # noqa: F841

                serverNameItem = server_dict.get(name)
                if serverNameItem is None:
                    serverNameItem = TaurusTreeServerNameItem(self, name, rootItem)
                    rootItem.appendChild(serverNameItem)
                    server_dict[name] = serverNameItem
                    # rootItem.appendChild(serverNameItem)

                serverInstanceItem = TaurusTreeServerItem(
                    self, server, serverNameItem
                )
                serverNameItem.appendChild(serverInstanceItem)

                klass_names = server.getClassNames()
                device_names = server.getDeviceNames()
                for klass_name in klass_names:
                    klass = klasses[klass_name]
                    klassItem = TaurusTreeDeviceClassItem(
                        self, klass, serverInstanceItem
                    )
                    serverInstanceItem.appendChild(klassItem)
                    for dev_name in device_names:
                        dev = devices[dev_name]
                        if dev.klass() == klass:
                            devItem = TaurusTreeDeviceItem(self, dev, klassItem)
                            klassItem.appendChild(devItem)

    class TaurusDbDeviceClassModel(TaurusDbBaseModel):
        """A Qt model that structures class elements in a tree organized as:

        * <Class>
            * <Device>
                * <Attribute>
        """

        ColumnNames = "Class", "Alive", "Host"
        ColumnRoles = (
            (
                ElemType.DeviceClass,
                ElemType.DeviceClass,
                ElemType.Device,
                ElemType.Attribute,
            ),
            ElemType.Exported,
            ElemType.Host,
        )

        def setupModelData(self, data):
            if data is None:
                return

            from taurus.core.tango.tangodatabase import TangoDatabase

            if isinstance(data, TangoDatabase):
                data = data.cache()

            rootItem = self._rootItem
            klasses, devices = data.klasses(), data.devices()
            dev_nb = 0
            for klass_name in data.getClassNames():
                klass = klasses[klass_name]
                klassItem = TaurusTreeDeviceClassItem(self, klass, rootItem)
                for dev_name in klass.getDeviceNames():
                    dev_nb += 1
                    dev = devices[dev_name]
                    devItem = TaurusTreeDeviceItem(self, dev, klassItem)
                    klassItem.appendChild(devItem)
                rootItem.appendChild(klassItem)

    class TaurusDbBaseProxyModel(TaurusBaseProxyModel):
        pass

    class TaurusDbDeviceProxyModel(TaurusDbBaseProxyModel):
        """A Qt filter & sort model for model for the taurus models:
        - TaurusDbBaseModel
        - TaurusDbDeviceModel
        - TaurusDbSimpleDeviceModel
        - TaurusDbPlainDeviceModel
        """

        def filterAcceptsRow(self, sourceRow, sourceParent):
            sourceModel = self.sourceModel()
            idx = sourceModel.index(sourceRow, 0, sourceParent)
            treeItem = idx.internalPointer()
            regexp = self.filterRegExp()

            # if domain node, check if it will potentially have any children
            if isinstance(treeItem, TaurusTreeDeviceDomainItem):
                domain = treeItem.display()
                devices = sourceModel.getDomainDevices(domain)
                for device in devices:
                    if self.deviceMatches(device, regexp):
                        return True
                return False

            # if family node, check if it will potentially have any children
            if isinstance(treeItem, TaurusTreeDeviceFamilyItem):
                domain = treeItem.parent().display()
                family = treeItem.display()
                devices = sourceModel.getFamilyDevices(domain, family)
                for device in devices:
                    if self.deviceMatches(device, regexp):
                        return True
                return False

            if (
                isinstance(treeItem, TaurusTreeDeviceItem)
                or isinstance(treeItem, TaurusTreeSimpleDeviceItem)
                or isinstance(treeItem, TaurusTreeDeviceMemberItem)
            ):
                device = treeItem.itemData()
                return self.deviceMatches(device, regexp)
            return True

        def deviceMatches(self, device, regexp):
            name = device.name()

            if regexp.indexIn(name) != -1:
                return True
            name = device.alias()
            if name is None:
                return False
            return regexp.indexIn(name) != -1

    class TaurusDbServerProxyModel(TaurusDbBaseProxyModel):
        """A Qt filter & sort model for the TaurusDbServerModel"""

        def filterAcceptsRow(self, sourceRow, sourceParent):
            sourceModel = self.sourceModel()
            idx = sourceModel.index(sourceRow, 0, sourceParent)
            treeItem = idx.internalPointer()
            regexp = self.filterRegExp()

            # if server name node, check if it will potentially have any children
            if isinstance(treeItem, TaurusTreeServerNameItem):
                serverName = treeItem.display()
                serverInstances = sourceModel.getServerNameInstances(serverName)
                for serverInstance in serverInstances:
                    if regexp.indexIn(serverInstance.name()) != -1:
                        return True
                return False

            if isinstance(treeItem, TaurusTreeServerItem):
                # return treeItem.qdisplay().contains(regexp)
                return regexp.indexIn(treeItem.display()) != -1

            return True

    class TaurusDbDeviceClassProxyModel(TaurusDbBaseProxyModel):
        """A Qt filter & sort model for the TaurusDbDeviceClassModel"""

        def filterAcceptsRow(self, sourceRow, sourceParent):
            sourceModel = self.sourceModel()
            idx = sourceModel.index(sourceRow, 0, sourceParent)
            treeItem = idx.internalPointer()

            if not isinstance(treeItem, TaurusTreeDeviceClassItem):
                return True

            regexp = self.filterRegExp()

            # return treeItem.qdisplay().contains(regexp)
            return regexp.indexIn(treeItem.display()) != -1