class DSBrowser(QWidget):
    """browser for datastorage databases
       Nodes are identified by a string, containing fields separated by '|'.
       - first filed is a capital letter:
       'R'oot, 'P'roject, sensor'G'roup, 'S'ensor and 'C'hart
       - second field is the database folder
       - third filed is the path of the node in the database
       - for charts the fourth field is the chart name (third is path of sensorgroup in this case)
    """
    def __init__(self):
        QWidget.__init__(self)
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setMargin(0)

        self.toolBar = QFrame(self)
        self.toolBarLayout = QHBoxLayout(self.toolBar)
        self.toolBarLayout.setMargin(2)
        self.toolBarLayout.setSpacing(2)
        self.layout.addWidget(self.toolBar)

        self.loadButton = QToolButton(self.toolBar)
        self.loadButton.setText(QCoreApplication.translate('DataStorageBrowser', 'Open...'))
        self.loadButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.fileOpen)))
        self.loadButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Open a datastorage database'))
        self.toolBarLayout.addWidget(self.loadButton)
        self.connect(self.loadButton, SIGNAL('pressed()'), self.loadDatabase)

        self.expandButton = QToolButton(self.toolBar)
        self.expandButton.setText('Expand/Collapse')
        self.expandButton.setIcon(QIcon(QPixmap(Icons.exp_col)))
        self.expandButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Expand or collapse the whole tree'))
        self.toolBarLayout.addWidget(self.expandButton)
        self.connect(self.expandButton, SIGNAL('pressed()'), self.expandCollapseAll)

        self.searchInput = QLineEdit(self.toolBar)
        self.searchInput.setText(QCoreApplication.translate('DataStorageBrowser', 'Enter search text here'))
        self.searchInput.setToolTip(QCoreApplication.translate('DataStorageBrowser',
            'Enter search text using wildcards here, press ENTER again to go to next match!'))
        self.toolBarLayout.addWidget(self.searchInput, 100)
        self.connect(self.searchInput, SIGNAL('returnPressed()'), self.searchItem)

        self.helpButton = QToolButton(self.toolBar)
        self.helpButton.setText(QCoreApplication.translate('DataStorageBrowser', 'Help'))
        self.helpButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.help)))
        self.helpButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Show help for DataStorageBrowser'))
        self.toolBarLayout.addWidget(self.helpButton)
        self.connect(self.helpButton, SIGNAL('pressed()'), self.showHelp)

        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Vertical)
        self.treeView = QTreeView(self.splitter)
        self.treeView.setAlternatingRowColors(True)
        self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.setAutoExpandDelay(500)
        self.textBrowser = QTextBrowser(self.splitter)
        self.layout.addWidget(self.splitter)
        self.splitter.setStretchFactor(0, 60)
        self.splitter.setStretchFactor(1, 40)
        self.model = DSModel()
        self.treeView.setModel(self.model)
        self.treeView.setSortingEnabled(True)
        self.treeView.expandAll()
        self.connect(self.treeView.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.showItem)
        self.connect(self.treeView, SIGNAL("doubleClicked(QModelIndex)"), self.itemAction)
        self.connect(self.treeView, SIGNAL("customContextMenuRequested(QPoint)"), self.showContextMenu)
        self.selectedNode = None
        self.selectedMI = None
        self.searchText = ''
        self.searchResults = []
        self.collExpand = SimuVis4.Misc.Switcher()


    def loadDatabase(self, dn=None):
        """load a database"""
        if not dn:
            dn = QFileDialog.getExistingDirectory(self, QCoreApplication.translate('DataStorageBrowser',
                "Select a folder containing a datastorage database"), SimuVis4.Globals.defaultFolder)
            if not dn.isEmpty():
                dn = unicode(dn)
                SimuVis4.Globals.defaultFolder = dn
            else:
                return
        self.model.addDatabase(dn)
        self.treeView.expandToDepth(1)
        self.treeView.resizeColumnToContents(0)


    def showHelp(self):
        SimuVis4.HelpBrowser.showHelp('/plugin/DataStorageBrowser/index.html')


    def showItem(self, mi, pr):
        """show the item at model index mi"""
        t, n = self.model.dsNode(mi)
        txt = ""
        if t == 'R':
            # FIXME: no metadata?
            txt = rootInfo.substitute(name=n.name, title=escape(n.title), folder=n.h5dir,
                projects=len(n)) + formatMetaData(n)
        elif t == 'P':
            txt = projectInfo.substitute(name=n.name, path=escape(n.parent.name),
                title=escape(n.title), groups=len(n)) + formatMetaData(n)
        elif t == 'G':
            txt = groupInfo.substitute(name=n.name,  path='/'.join(n.path.split('/')[:-1]),
                title=escape(n.title), sensors=len(n), charts=len(n.charts)) + formatMetaData(n)
        elif t == 'S':
            txt = sensorInfo.substitute(name=n.name,  path='/'.join(n.path.split('/')[:-1]),
                title=escape(n.title), start=n.timegrid.start, stop=n.timegrid.stop,
                step=n.timegrid.step, length=n.datalen()) + formatMetaData(n)
        elif t == 'C':
            txt = chartInfo.substitute(name=n.name, path=n.sensorgroup.path)
        self.textBrowser.setText(txt)


    def searchItem(self):
        """execute the search and highlight the (next) result"""
        txt = str(self.searchInput.text())
        if txt != self.searchText:
            self.searchText = txt
            tmp = self.model.findItems(txt,
                Qt.MatchFixedString | Qt.MatchContains | Qt.MatchWildcard | Qt.MatchRecursive)
            self.searchList = [i.index() for i in tmp]
        if self.searchList:
            mi = self.searchList.pop()
            self.treeView.setCurrentIndex(mi)
            self.treeView.expand(mi)
            self.treeView.scrollTo(mi)
        else:
            QMessageBox.information(self, QCoreApplication.translate('DataStorageBrowser',
                'No (more) matches!'), QCoreApplication.translate('DataStorageBrowser',
                'No (more) matches found! Change you search criteria and try again!'))
            self.searchText = ''


    def expandCollapseAll(self):
        if self.collExpand():
            self.treeView.collapseAll()
        else:
            self.treeView.expandAll()

    def itemAction(self, mi):
        """default action (on doubleclick) for item at model index mi"""
        t, n = self.model.dsNode(mi)
        if t == 'R':
            pass
        elif t == 'P':
            pass
        elif t == 'G':
            pass
        elif t == 'S':
            self.showQwtPlot(n)
        elif t == 'C':
            self.showMplChart(n)


    def showContextMenu(self, pos):
        """show context menu for item at pos"""
        mi = self.treeView.indexAt(pos)
        t, n = self.model.dsNode(mi)
        self.selectedNode = n
        self.selectedMI = mi
        m = QMenu()
        if t == 'R':
            m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Close'), self.closeDatabase)
        elif t == 'P':
            pass
        elif t == 'G':
            nCharts = len(n.charts)
            if nCharts > 0:
                txt = str(QCoreApplication.translate('DataStorageBrowser', 'Show all Charts (%d)')) % nCharts
                m.addAction(txt, self.showAllCharts)
            m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Add Chart'), self.newChart)
        elif t == 'S':
            m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Plot (Qwt)'), self.showQwtPlot)
        elif t == 'C':
            m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Show'), self.showMplChart)
            m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Delete'), self.deleteItem)
        if t in 'RPGS':
            m.addSeparator()
            m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Edit metadata'), self.editMetadata)
        a = m.exec_(self.treeView.mapToGlobal(pos))


    def showMplChart(self, node=None):
        if node is None:
            node = self.selectedNode
        showChartMplWindow(node, maximized=showChartMaximized)


    def showAllCharts(self, node=None):
        if node is None:
            node = self.selectedNode
        for chart in node.charts.values():
            showChartMplWindow(chart, maximized=showChartMaximized)


    def showQwtPlot(self, node=None):
        if node is None:
            node = self.selectedNode
        showQwtPlotWindow(node, maximized=showChartMaximized)


    def editMetadata(self, node=None):
        if node is None:
            node = self.selectedNode
        editMetadata(node)


    def closeDatabase(self, mi=None):
        if mi is None:
            mi = self.selectedMI
        self.model.closeDatabase(mi)


    def newChart(self, mi=None):
        """add a chart to sensorgroup at mi using the wizard"""
        if mi is None:
            mi = self.selectedMI
        showNewChartWizard(self.model, mi)


    def deleteItem(self, mi=None):
        """delete the item at mi"""
        if mi is None:
            mi = self.selectedMI
        self.model.deleteItem(mi)
class DSBrowser(QWidget):
    """browser for datastorage databases
       Nodes are identified by a string, containing fields separated by '|'.
       - first filed is a capital letter:
       'R'oot, 'P'roject, sensor'G'roup, 'S'ensor and 'C'hart
       - second field is the database folder
       - third filed is the path of the node in the database
       - for charts the fourth field is the chart name (third is path of sensorgroup in this case)
    """

    def __init__(self):
        QWidget.__init__(self)
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setMargin(0)

        self.toolBar = QFrame(self)
        self.toolBarLayout = QHBoxLayout(self.toolBar)
        self.toolBarLayout.setMargin(2)
        self.toolBarLayout.setSpacing(2)
        self.layout.addWidget(self.toolBar)

        self.loadButton = QToolButton(self.toolBar)
        self.loadButton.setText(QCoreApplication.translate("DataStorageBrowser", "Open..."))
        self.loadButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.fileOpen)))
        self.loadButton.setToolTip(QCoreApplication.translate("DataStorageBrowser", "Open a datastorage database"))
        self.toolBarLayout.addWidget(self.loadButton)
        self.connect(self.loadButton, SIGNAL("pressed()"), self.loadDatabase)

        self.expandButton = QToolButton(self.toolBar)
        self.expandButton.setText("Expand/Collapse")
        self.expandButton.setIcon(QIcon(QPixmap(Icons.exp_col)))
        self.expandButton.setToolTip(
            QCoreApplication.translate("DataStorageBrowser", "Expand or collapse the whole tree")
        )
        self.toolBarLayout.addWidget(self.expandButton)
        self.connect(self.expandButton, SIGNAL("pressed()"), self.expandCollapseAll)

        self.searchInput = MyLineEdit(self.toolBar)
        self.searchInput.setText(QCoreApplication.translate("DataStorageBrowser", "Enter search text here"))
        self.searchInput.setToolTip(
            QCoreApplication.translate(
                "DataStorageBrowser", "Enter search text using wildcards here, press ENTER again to go to next match!"
            )
        )
        self.toolBarLayout.addWidget(self.searchInput, 100)
        self.connect(self.searchInput, SIGNAL("returnPressed()"), self.searchItem)

        self.helpButton = QToolButton(self.toolBar)
        self.helpButton.setText(QCoreApplication.translate("DataStorageBrowser", "Help"))
        self.helpButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.help)))
        self.helpButton.setToolTip(QCoreApplication.translate("DataStorageBrowser", "Show help for DataStorageBrowser"))
        self.toolBarLayout.addWidget(self.helpButton)
        self.connect(self.helpButton, SIGNAL("pressed()"), self.showHelp)

        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Vertical)
        self.treeView = QTreeView(self.splitter)
        self.treeView.setAlternatingRowColors(True)
        self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.setAutoExpandDelay(500)
        self.textBrowser = QTextBrowser(self.splitter)
        self.layout.addWidget(self.splitter)
        self.splitter.setStretchFactor(0, 60)
        self.splitter.setStretchFactor(1, 40)
        self.model = DSModel()
        self.treeView.setModel(self.model)
        self.treeView.setSortingEnabled(True)
        self.treeView.expandAll()
        self.connect(self.treeView.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.showItem)
        self.connect(self.treeView, SIGNAL("doubleClicked(QModelIndex)"), self.itemAction)
        self.connect(self.treeView, SIGNAL("customContextMenuRequested(QPoint)"), self.showContextMenu)
        self.selectedNode = None
        self.selectedMI = None
        self.searchText = ""
        self.searchResults = []
        self.collExpand = SimuVis4.Misc.Switcher()

        self.statusBar = SimuVis4.Globals.mainWin.statusBar()

    def loadDatabase(self, dn=None):
        """load a database"""
        if not dn:
            dn = QFileDialog.getExistingDirectory(
                self,
                QCoreApplication.translate("DataStorageBrowser", "Select a folder containing a datastorage database"),
                SimuVis4.Globals.defaultFolder,
            )
            if not dn.isEmpty():
                dn = unicode(dn)
                SimuVis4.Globals.defaultFolder = dn
            else:
                return
        self.model.addDatabase(dn)
        self.treeView.collapseAll()
        self.treeView.expandToDepth(SimuVis4.Globals.config.getint("datastoragebrowser", "expand_tree_depth"))
        self.treeView.resizeColumnToContents(0)

    def showHelp(self):
        SimuVis4.HelpBrowser.showHelp("/plugin/DataStorageBrowser/index.html")

    def showItem(self, mi, pr):
        """show the item at model index mi"""
        t, n = self.model.dsNode(mi)
        txt = ""
        if t == "R":
            txt = rootInfo.substitute(
                name=n.name, title=escape(n.title), folder=n.h5dir, projects=len(n)
            ) + formatMetaData(n)
        elif t == "P":
            txt = projectInfo.substitute(
                name=n.name, path=escape(n.parent.name), title=escape(n.title), groups=len(n)
            ) + formatMetaData(n)
        elif t == "G":
            txt = groupInfo.substitute(
                name=n.name,
                path="/".join(n.path.split("/")[:-1]),
                title=escape(n.title),
                sensors=len(n),
                charts=len(n.getCharts()),
                start=formatTime(n.timegrid.start),
                stop=formatTime(n.timegrid.stop),
                step=n.timegrid.step,
                timezone=n.timegrid.timezone,
            ) + formatMetaData(n)
        elif t == "S":
            txt = sensorInfo.substitute(
                name=n.name,
                path="/".join(n.path.split("/")[:-1]),
                title=escape(n.title),
                start=formatTime(n.timegrid.start),
                stop=formatTime(n.timegrid.stop),
                step=n.timegrid.step,
                length=n.datalen(),
                timezone=n.timegrid.timezone,
            ) + formatMetaData(n)
        elif t == "C":
            txt = chartInfo.substitute(name=n.name, path=n.sensorgroup.path)
        self.textBrowser.setText(txt)
        msg = ": ".join(str(self.model.itemFromIndex(mi).data().toString()).split("|")[1:])
        self.statusBar.showMessage(msg, 5000)

    def searchItem(self):
        """execute the search and highlight the (next) result"""
        txt = str(self.searchInput.text())
        if txt != self.searchText:
            self.searchText = txt
            tmp = self.model.findItems(
                txt, Qt.MatchFixedString | Qt.MatchContains | Qt.MatchWildcard | Qt.MatchRecursive
            )
            self.searchList = [i.index() for i in tmp]
        if self.searchList:
            mi = self.searchList.pop()
            self.treeView.setCurrentIndex(mi)
            self.treeView.expand(mi)
            self.treeView.scrollTo(mi)
        else:
            QMessageBox.information(
                self,
                QCoreApplication.translate("DataStorageBrowser", "No (more) matches!"),
                QCoreApplication.translate(
                    "DataStorageBrowser", "No (more) matches found! Change you search text and try again!"
                ),
            )
            self.searchText = ""

    def expandCollapseAll(self):
        if self.collExpand():
            self.treeView.collapseAll()
        else:
            self.treeView.expandAll()

    def itemAction(self, mi):
        """default action (on doubleclick) for item at model index mi"""
        t, n = self.model.dsNode(mi)
        if t == "S":
            if qwtPlotWindowActive():
                self.addToQwtPlot(n)
            else:
                self.showQwtPlot(n)
        elif t == "C":
            self.showChart(n)

    def showContextMenu(self, pos):
        """show context menu for item at pos"""
        mi = self.treeView.indexAt(pos)
        t, n = self.model.dsNode(mi)
        self.selectedNode = n
        self.selectedMI = mi
        m = QMenu()
        if t == "R":
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Close"), self.closeDatabase)
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Reload"), self.reloadDatabase)
        elif t == "P":
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "New sensorgroup"), self.newSensorGroup)
        elif t == "G":
            nCharts = len(n.getCharts())
            if nCharts > 0:
                txt = str(QCoreApplication.translate("DataStorageBrowser", "Show all charts (%d)")) % nCharts
                m.addAction(txt, self.showAllCharts)
                m.addAction(
                    QCoreApplication.translate("DataStorageBrowser", "Save all charts as images"),
                    self.saveAllChartImages,
                )
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Add chart"), self.newChart)
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Add/update data"), self.importFiles)
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Export data"), self.exportSensors)
        elif t == "S":
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "New plot (Qwt)"), self.showQwtPlot)
            if qwtPlotWindowActive():
                m.addAction(QCoreApplication.translate("DataStorageBrowser", "Add to plot (Qwt)"), self.addToQwtPlot)
        elif t == "C":
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Show"), self.showChart)
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Delete"), self.deleteItem)
        if t in "RPGS":
            m.addSeparator()
            m.addAction(QCoreApplication.translate("DataStorageBrowser", "Edit metadata"), self.editMetadata)
        a = m.exec_(self.treeView.mapToGlobal(pos))

    def newSensorGroup(self, mi=None):
        if mi is None:
            mi = self.selectedMI
        newSensorGroup(self.model, mi)

    def showChart(self, ch=None):
        if ch is None:
            ch = self.selectedNode
        showChartWindow(ch, maximized=showChartMaximized)

    def importFiles(self, mi=None):
        if mi is None:
            mi = self.selectedMI
        importFiles(self.model, mi)

    def showAllCharts(self, sg=None):
        if sg is None:
            sg = self.selectedNode
        showAllChartWindows(sg, maximized=showChartMaximized)

    def saveAllChartImages(self, sg=None):
        if sg is None:
            sg = self.selectedNode
        saveAllChartImages(sg)

    def exportSensors(self, sg=None):
        if sg is None:
            sg = self.selectedNode
        exportSensors(sg)

    def showQwtPlot(self, se=None):
        if se is None:
            se = self.selectedNode
        showQwtPlotWindow(se, maximized=showChartMaximized)

    def addToQwtPlot(self, se=None):
        if se is None:
            se = self.selectedNode
        addToQwtPlotWindow(se)

    def editMetadata(self, node=None):
        if node is None:
            node = self.selectedNode
        editMetadata(node)

    def closeDatabase(self, mi=None):
        if mi is None:
            mi = self.selectedMI
        self.model.closeDatabase(mi)

    def reloadDatabase(self, mi=None):
        if mi is None:
            mi = self.selectedMI
        dbPath = self.model.dsFolder(mi)
        self.model.closeDatabase(mi)
        self.loadDatabase(dbPath)

    def newChart(self, mi=None):
        """add a chart to sensorgroup at mi using the wizard"""
        if mi is None:
            mi = self.selectedMI
        showNewChartWizard(self.model, mi, self)

    def deleteItem(self, mi=None):
        """delete the item at mi"""
        if mi is None:
            mi = self.selectedMI
        self.model.deleteItem(mi)
Example #3
0
class DetailsTreeView(DetailsDBHandler, DetailsDockWidget):
    def __init__(self, iface, spatial_unit_dock):

        """
        The method initializes the dockwidget.
        :param iface: QGIS user interface class
        :type class qgis.utils.iface
        :param plugin: The STDM plugin
        :type class
        :return: None
        """
        from stdm.ui.entity_browser import _EntityDocumentViewerHandler
        DetailsDockWidget.__init__(self, iface, spatial_unit_dock)

        DetailsDBHandler.__init__(self)

        self.spatial_unit_dock = spatial_unit_dock

        self.view = QTreeView()
        self.view.setSelectionBehavior(
            QAbstractItemView.SelectRows
        )
        #self.feature_ids = []
        self.layer_table = None
        self.entity = None
        self.feature_models = {}
        self.party_models = {}
        self.STR_models = {}
        self.feature_STR_model = {}
        self.removed_feature = None
        self.selected_root = None
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.view.setUniformRowHeights(True)
        self.view.setRootIsDecorated(True)
        self.view.setAlternatingRowColors(True)
        self.view.setWordWrap(True)
        self.view.setHeaderHidden(True)
        self.view.setEditTriggers(
            QAbstractItemView.NoEditTriggers
        )
        self.current_profile = current_profile()
        self.social_tenure = self.current_profile.social_tenure
        self.spatial_unit = self.social_tenure.spatial_unit
        self.party = self.social_tenure.party
        self.view.setMinimumWidth(250)
        self.doc_viewer_title = QApplication.translate(
            'EntityBrowser',
            'Document Viewer'
        )
        self.doc_viewer = _EntityDocumentViewerHandler(
            self.doc_viewer_title, self.iface.mainWindow()
        )

    def set_layer_entity(self):
        self.layer_table = self.get_layer_source(
            self.iface.activeLayer()
        )
        if self.layer_table in spatial_tables():
            self.entity = self.current_profile.entity_by_name(
                self.layer_table
            )

        else:
            self.treeview_error('The layer is not a spatial entity layer. ')

    def activate_feature_details(self, button_clicked=True):
        """
        Action for showing feature details.
        :return:
        """
        # Get the active layer.
        active_layer = self.iface.activeLayer()
        # TODO fix feature_details_btn is deleted error.
        if active_layer is not None and \
                self.spatial_unit_dock.feature_details_btn.isChecked():
            # if feature detail dock is not defined or hidden, create empty dock.
            if self is None or self.isHidden():
                # if the selected layer is not a feature layer, show not
                # feature layer. (implicitly included in the if statement).
                if not self.feature_layer(active_layer):
                    self.spatial_unit_dock.feature_details_btn.setChecked(False)
                    return
                # If the selected layer is feature layer, get data and
                # display treeview in a dock widget
                else:
                    select_feature = QApplication.translate(
                        "STDMQGISLoader",
                        "Please select a feature to view their details."
                    )
                    self.init_dock()
                    self.add_tree_view()
                    self.model.clear()
                    self.treeview_error(select_feature)

                    # enable the select tool
                    self.activate_select_tool()
                    # set entity from active layer in the child class
                    self.set_layer_entity()
                    # set entity for the super class DetailModel
                    self.set_entity(self.entity)
                    # Registery column widget
                    self.set_formatter()
                    #set formatter for social tenure relationship.
                    self.set_formatter(self.social_tenure)
                    self.set_formatter(self.party)
                    # pull data, show treeview
                    active_layer.selectionChanged.connect(
                        self.show_tree
                    )
                    self.steam_signals(self.entity)

            # if feature_detail dock is open, toggle close
            else:
                self.close_dock(
                    self.spatial_unit_dock.feature_details_btn
                )
                self.feature_details = None
        # if no active layer, show error message and uncheck the feature tool
        else:
            if button_clicked:
                self.active_layer_check()
            self.spatial_unit_dock.feature_details_btn.setChecked(False)

    def add_tree_view(self):
        """
        Adds tree view to the dock widget and sets style.
        :return: None
        """
        self.tree_scrollArea.setWidget(self.view)

    def clear_feature_models(self):
        self.feature_models.clear()

    def reset_tree_view(self, no_feature=False):
        #clear feature_ids list, model and highlight
        self.model.clear()

        self.clear_sel_highlight() # remove sel_highlight
        self.disable_buttons(no_feature)
        if self.removed_feature is None:
            self.STR_models.clear()
            self.feature_models.clear()
        else:
            self.removed_feature = None
        features = self.selected_features()
        # if the selected feature is over 1,
        # activate multi_select_highlight
        if not features is None:
            self.view.clicked.connect(
                self.multi_select_highlight
            )
        # if there is at least one selected feature
        if len(features) > 0:
            self.add_tree_view()
            #self.feature_ids = features

    def disable_buttons(self, bool):
        self.edit_btn.setDisabled(bool)
        self.delete_btn.setDisabled(bool)

    def show_tree(self):
        selected_features = self.selected_features()
        if len(selected_features) < 1:
            self.reset_tree_view(True)
            return
        if not self.entity is None:
            self.reset_tree_view()
            if len(selected_features) < 1:
                self.disable_buttons(True)
                return

            layer_icon = QIcon(':/plugins/stdm/images/icons/layer.gif')
            roots = self.add_parent_tree(
                layer_icon, format_name(self.entity.short_name)
            )
            if roots is None:
                return

            for id, root in roots.iteritems():
                db_model = entity_id_to_model(self.entity, id)

                self.add_roots(db_model, root, id)

    def add_parent_tree(self, icon, title):
        roots = OrderedDict()
        for feature_id in self.selected_features():
            root = QStandardItem(icon, title)
            root.setData(feature_id)
            self.set_bold(root)
            self.model.appendRow(root)
            roots[feature_id] = root
        return roots

    def add_roots(self, model, parent, feature_id):
        self.feature_models[feature_id] = model
        if model is None:
            return
        self.column_widget_registry(model, self.entity)

        for i, (col, row) in enumerate(self.formatted_record.iteritems()):
            child = QStandardItem('{}: {}'.format(col, row))
            child.setSelectable(False)
            parent.appendRow([child])

            # Add Social Tenure Relationship steam as a last child
            if i == len(self.formatted_record)-1:
                self.add_STR_child(parent, feature_id)
        self.expand_node(parent)

    def add_STR_steam(self, parent, STR_id):
        str_icon = QIcon(
            ':/plugins/stdm/images/icons/social_tenure.png'
        )
        title = 'Social Tenure Relationship'
        str_root = QStandardItem(str_icon, title)
        str_root.setData(STR_id)
        self.set_bold(str_root)
        parent.appendRow([str_root])
        return str_root
    def add_no_STR_steam(self, parent):
        if self.entity.name == self.spatial_unit.name:
            no_str_icon = QIcon(
                ':/plugins/stdm/images/icons/remove.png'
            )
            title = 'No STR Defined'
            no_str_root = QStandardItem(no_str_icon, title)
            self.set_bold(no_str_root)
            parent.appendRow([no_str_root])

    def add_STR_child(self, parent, feature_id):
        if len(self.feature_STR_link(feature_id)) < 1:
            self.add_no_STR_steam(parent)
            return
        for record in self.feature_STR_link(feature_id):
            self.STR_models[record.id] = record
            str_root = self.add_STR_steam(parent, record.id)
            # add STR children
            self.column_widget_registry(record, self.social_tenure)
            for i, (col, row) in enumerate(
                    self.formatted_record.iteritems()
            ):
                STR_child = QStandardItem(
                    '{}: {}'.format(col, row)
                )
                STR_child.setSelectable(False)
                str_root.appendRow([STR_child])
                if i == len(self.formatted_record)-1:
                    self.add_party_child(
                        str_root, record.party_id
                    )
        self.feature_STR_model[feature_id] = self.STR_models.keys()

    def add_party_steam(self, parent, party_id):
        party_icon = QIcon(
            ':/plugins/stdm/images/icons/table.png'
        )
        title = format_name(self.party.short_name)
        party_root = QStandardItem(party_icon, title)
        party_root.setData(party_id)
        self.set_bold(party_root)

        parent.appendRow([party_root])
        party_root.setEditable(False)
        return party_root

    def add_party_child(self, parent, party_id):

        db_model = entity_id_to_model(self.party, party_id)
        self.party_models[party_id] = db_model
        party_root = self.add_party_steam(parent, party_id)
        # add STR children
        self.column_widget_registry(db_model, self.party)
        for col, row in self.formatted_record.iteritems():
            party_child = QStandardItem('{}: {}'.format(col, row))
            party_child.setSelectable(False)
            party_root.appendRow([party_child])

    def set_bold(self, standard_item):
        """
        Make a text of Qstandaritem to bold.
        :param standard_item: Qstandaritem
        :type: Qstandaritem
        :return: None
        """
        font = standard_item.font()
        font.setBold(True)
        standard_item.setFont(font)

    def treeview_error(self, message, icon=None):
        """
        Displays error message in feature details treeview.
        :param title: the title of the treeview.
        :type: String
        :param message: The message to be displayed.
        :type: String
        :param icon: The icon of the item.
        :type: Resource string
        :return: None
        """
        not_feature_ft_msg = QApplication.translate(
            'FeatureDetails', message
        )
        if icon== None:
            root = QStandardItem(not_feature_ft_msg)
        else:
            root = QStandardItem(icon, not_feature_ft_msg)

        self.view.setRootIsDecorated(False)
        self.model.appendRow(root)
        self.view.setRootIsDecorated(True)

    def expand_node(self, parent):
        """
        Make the last tree node expand.
        :param parent: The parent to expand
        :type QStandardItem
        :return:None
        """
        index = self.model.indexFromItem(parent)
        self.view.expand(index)


    def multi_select_highlight(self, index):
        """
        Highlights a feature with rubberBald class when selecting
        features are more than one.
        :param index: Selected QTreeView item index
        :type Integer
        :return: None
        """
        map = self.iface.mapCanvas()
        try:

            # Get the selected item text using the index
            selected_item = self.model.itemFromIndex(index)
            # Use mutli-select only when more than 1 items are selected.
            if self.layer.selectedFeatures() < 2:
                return
            self.selected_root = selected_item
            # Split the text to get the key and value.
            selected_item_text = selected_item.text()

            selected_value = selected_item.data()
            # If the first word is feature, expand & highlight.
            if selected_item_text == format_name(self.spatial_unit.short_name):
                self.view.expand(index)  # expand the item
                # Clear any existing highlight
                self.clear_sel_highlight()
                # Insert highlight
                # Create expression to target the selected feature
                expression = QgsExpression(
                    "\"id\"='" + str(selected_value) + "'"
                )
                # Get feature iteration based on the expression
                ft_iteration = self.layer.getFeatures(
                    QgsFeatureRequest(expression)
                )

                # Retrieve geometry and attributes
                for feature in ft_iteration:
                    # Fetch geometry
                    geom = feature.geometry()
                    self.sel_highlight = QgsHighlight(map, geom, self.layer)

                    self.sel_highlight.setFillColor(selection_color())
                    self.sel_highlight.setWidth(4)
                    self.sel_highlight.setColor(QColor(212,95,0, 255))
                    self.sel_highlight.show()
                    break
        except AttributeError:
            # pass attribute error on child items such as party
            pass
        except IndexError:
            pass

    def steam_signals(self, entity):
        self.edit_btn.clicked.connect(
            lambda : self.edit_selected_steam(
                entity
            )
        )
        self.delete_btn.clicked.connect(
            self.delete_selected_item
        )
        self.view_document_btn.clicked.connect(
            lambda : self.view_steam_document(
                entity
            )
        )

    def steam_data(self, mode):
        item = None
        # if self.view.currentIndex().text() == format_name(self.party):
        #     return None, None
        # One item is selected and number of feature is also 1
        if len(self.layer.selectedFeatures()) == 1 and \
                        len(self.view.selectedIndexes()) == 1:
            index = self.view.selectedIndexes()[0]
            item = self.model.itemFromIndex(index)
            result = item.data()

        # One item is selected on the map but not on the treeview
        elif len(self.layer.selectedFeatures()) == 1 and \
                        len(self.view.selectedIndexes()) == 0:
            item = self.model.item(0, 0)
            result = item.data()

        # multiple features are selected but one treeview item is selected
        elif len(self.layer.selectedFeatures()) > 1 and \
                        len(self.view.selectedIndexes()) == 1:
            item = self.selected_root
            result = self.selected_root.data()
        # multiple features are selected but no treeview item is selected
        elif len(self.layer.selectedFeatures()) > 1 and \
             len(self.view.selectedIndexes()) == 0:
            result = 'Please, select an item to {}.'.format(mode)
        else:
            result = 'Please, select at least one feature to {}.'.format(mode)
        if result is None:

            if item is None:
                item = self.model.item(0, 0)
                result = item.data()
            else:
                result = item.parent().data()
        return result, item

    def edit_selected_steam(self, entity):
        id, item = self.steam_data('edit')

        feature_edit = True
        if id is None:
            return
        if isinstance(id, str):
            data_error = QApplication.translate('DetailsTreeView', id)
            QMessageBox.warning(
                self.iface.mainWindow(), "Edit Error", data_error
            )
            return

        if item.text() == 'Social Tenure Relationship':
            model = self.STR_models[id]

            feature_edit = False
            ##TODO add STR wizard edit mode here.
        elif item.text() == format_name(self.party.short_name):
            feature_edit = False

            model = self.party_models[id]
            editor = EntityEditorDialog(
                self.party, model, self.iface.mainWindow()
            )
            editor.exec_()
        else:
            model = self.feature_models[id]

            editor = EntityEditorDialog(
                entity, model, self.iface.mainWindow()
            )
            editor.exec_()
        #root = self.find_root(entity, id)
        self.view.expand(item.index())
        if feature_edit:
            self.update_edited_steam(entity, id)
        else:
            self.update_edited_steam(self.social_tenure, id)

    def delete_selected_item(self):
        str_edit = False
        id, item = self.steam_data('delete')

        if isinstance(id, str):
            data_error = QApplication.translate(
                'DetailsTreeView', id
            )
            QMessageBox.warning(
                self.iface.mainWindow(),
                'Delete Error',
                data_error
            )
            return
        if item.text() == 'Social Tenure Relationship':
            str_edit = True
            db_model = self.STR_models[id]

        elif item.text() == format_name(self.spatial_unit.short_name) and \
            id not in self.feature_STR_model.keys():
            db_model = self.feature_models[id]

        # if spatial unit is linked to STR, don't allow delete
        elif item.text() == format_name(self.spatial_unit.short_name) and \
                        id in self.feature_STR_model.keys():


            delete_warning = QApplication.translate(
                'DetailsTreeView',
                'You have to first delete the social tenure \n'
                'relationship to delete the {} record.'.format(
                    item.text()
                )

            )
            QMessageBox.warning(
                self.iface.mainWindow(),
                'Delete Error',
                delete_warning
            )
            return
        # If it is party node, STR exists and don't allow delete.
        elif item.text() == format_name(self.party.short_name):
            delete_warning = QApplication.translate(
                'DetailsTreeView',
                'You have to first delete the social tenure \n'
                'relationship to delete the {} record.'.format(
                    item.text()
                )
            )
            QMessageBox.warning(
                self.iface.mainWindow(),
                'Delete Error',
                delete_warning
            )
            return
        else:
            return
        delete_warning = QApplication.translate(
            'DetailsTreeView',
            'Are you sure you want to delete '
            'the selected record(s)?\n'
            'This action cannot be undone.'
        )

        delete_question = QMessageBox.warning(
            self.iface.mainWindow(),
            "Delete Warning",
            delete_warning,
            QMessageBox.Yes | QMessageBox.No
        )
        if delete_question == QMessageBox.Yes:
            db_model.delete()

            if str_edit:
                del self.STR_models[id]
            else:
                self.removed_feature = id
                del self.feature_models[id]

            self.updated_removed_steam(str_edit, item)
        else:
            return

    def update_edited_steam(self, entity, feature_id):

        # remove rows before adding the updated ones.
        self.layer.setSelectedFeatures(
            self.feature_models.keys()
        )
        root = self.find_root(entity, feature_id)
        if root is None:
            return
        self.view.selectionModel().select(
            root.index(), self.view.selectionModel().Select
        )
        self.expand_node(root)
        self.multi_select_highlight(root.index())

    def find_root(self, entity, feature_id):
        all_roots = self.model.findItems(
            format_name(entity.short_name)
        )
        root = None
        for item in all_roots:
            if item.data() == feature_id:
                root = item
                break
        return root

    def updated_removed_steam(self, STR_edit, item):
        if not STR_edit:
            if len(self.feature_models) > 1:
                self.refresh_layers()
            feature_ids = self.feature_models.keys()
            self.layer.setSelectedFeatures(
                feature_ids
            )
        else:
            item.removeRows(0, 2)
            item.setText('No STR Definded')
            no_str_icon = QIcon(
                ':/plugins/stdm/images/icons/remove.png'
            )
            item.setIcon(no_str_icon)

    def view_steam_document(self, entity):
        # Slot raised to show the document viewer for the selected entity

        id, item = self.steam_data('edit')

        if id is None:
            return
        if isinstance(id, str):
            data_error = QApplication.translate('DetailsTreeView', id)
            QMessageBox.warning(
                self.iface.mainWindow(), "Edit Error", data_error
            )
            return
        if item.text() == 'Social Tenure Relationship':
            db_model = self.STR_models[id]
        else:
            db_model = self.feature_models[id]

        if not db_model is None:
            docs = db_model.documents
            # Notify there are no documents for the selected doc
            if len(docs) == 0:
                msg = QApplication.translate(
                    'EntityBrowser',
                    'There are no supporting documents '
                    'for the selected record.'
                )

                QMessageBox.warning(
                    self,
                    self.doc_viewer_title,
                    msg
                )
            else:
                self.doc_viewer.load(docs)