Exemplo n.º 1
0
class MultipleInputDialog(BASE, WIDGET):
    def __init__(self, options, selectedoptions=None, datatype=None):
        super(MultipleInputDialog, self).__init__(None)
        self.setupUi(self)
        self.datatype = datatype
        self.model = None

        self.options = []
        for i, option in enumerate(options):
            if option is None or isinstance(option, str):
                self.options.append((i, option))
            else:
                self.options.append((option[0], option[1]))

        self.selectedoptions = selectedoptions or []

        # Additional buttons
        self.btnSelectAll = QPushButton(self.tr('Select All'))
        self.buttonBox.addButton(self.btnSelectAll,
                                 QDialogButtonBox.ActionRole)
        self.btnClearSelection = QPushButton(self.tr('Clear Selection'))
        self.buttonBox.addButton(self.btnClearSelection,
                                 QDialogButtonBox.ActionRole)
        self.btnToggleSelection = QPushButton(self.tr('Toggle Selection'))
        self.buttonBox.addButton(self.btnToggleSelection,
                                 QDialogButtonBox.ActionRole)
        if self.datatype is not None:
            btnAddFile = QPushButton(
                QCoreApplication.translate("MultipleInputDialog",
                                           'Add File(s)…'))
            btnAddFile.clicked.connect(self.addFiles)
            self.buttonBox.addButton(btnAddFile, QDialogButtonBox.ActionRole)

        self.btnSelectAll.clicked.connect(lambda: self.selectAll(True))
        self.btnClearSelection.clicked.connect(lambda: self.selectAll(False))
        self.btnToggleSelection.clicked.connect(self.toggleSelection)

        self.settings = QgsSettings()
        self.restoreGeometry(
            self.settings.value("/Processing/multipleInputDialogGeometry",
                                QByteArray()))

        self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.lstLayers.setDragDropMode(QAbstractItemView.InternalMove)

        self.populateList()
        self.finished.connect(self.saveWindowGeometry)

    def saveWindowGeometry(self):
        self.settings.setValue("/Processing/multipleInputDialogGeometry",
                               self.saveGeometry())

    def populateList(self):
        self.model = QStandardItemModel()
        for value, text in self.options:
            item = QStandardItem(text)
            item.setData(value, Qt.UserRole)
            item.setCheckState(Qt.Checked if value in
                               self.selectedoptions else Qt.Unchecked)
            item.setCheckable(True)
            item.setDropEnabled(False)
            self.model.appendRow(item)

        # add extra options (e.g. manually added layers)
        for t in [o for o in self.selectedoptions if not isinstance(o, int)]:
            if isinstance(t, QgsProcessingModelChildParameterSource):
                item = QStandardItem(t.staticValue())
            else:
                item = QStandardItem(t)
            item.setData(item.text(), Qt.UserRole)
            item.setCheckState(Qt.Checked)
            item.setCheckable(True)
            item.setDropEnabled(False)
            self.model.appendRow(item)

        self.lstLayers.setModel(self.model)

    def accept(self):
        self.selectedoptions = []
        model = self.lstLayers.model()
        for i in range(model.rowCount()):
            item = model.item(i)
            if item.checkState() == Qt.Checked:
                self.selectedoptions.append(item.data(Qt.UserRole))
        QDialog.accept(self)

    def reject(self):
        self.selectedoptions = None
        QDialog.reject(self)

    def getItemsToModify(self):
        items = []
        if len(self.lstLayers.selectedIndexes()) > 1:
            for i in self.lstLayers.selectedIndexes():
                items.append(self.model.itemFromIndex(i))
        else:
            for i in range(self.model.rowCount()):
                items.append(self.model.item(i))
        return items

    def selectAll(self, value):
        for item in self.getItemsToModify():
            item.setCheckState(Qt.Checked if value else Qt.Unchecked)

    def toggleSelection(self):
        for item in self.getItemsToModify():
            checked = item.checkState() == Qt.Checked
            item.setCheckState(Qt.Unchecked if checked else Qt.Checked)

    def getFileFilter(self, datatype):
        """
        Returns a suitable file filter pattern for the specified parameter definition
        :param param:
        :return:
        """
        if datatype == QgsProcessing.TypeRaster:
            return QgsProviderRegistry.instance().fileRasterFilters()
        elif datatype == QgsProcessing.TypeFile:
            return self.tr('All files (*.*)')
        else:
            exts = QgsVectorFileWriter.supportedFormatExtensions()
            for i in range(len(exts)):
                exts[i] = self.tr('{0} files (*.{1})').format(
                    exts[i].upper(), exts[i].lower())
            return self.tr('All files (*.*)') + ';;' + ';;'.join(exts)

    def addFiles(self):
        filter = self.getFileFilter(self.datatype)

        settings = QgsSettings()
        path = str(settings.value('/Processing/LastInputPath'))

        ret, selected_filter = QFileDialog.getOpenFileNames(
            self, self.tr('Select File(s)'), path, filter)
        if ret:
            files = list(ret)
            settings.setValue('/Processing/LastInputPath',
                              os.path.dirname(str(files[0])))
            for filename in files:
                item = QStandardItem(filename)
                item.setData(filename, Qt.UserRole)
                item.setCheckState(Qt.Checked)
                item.setCheckable(True)
                item.setDropEnabled(False)
                self.model.appendRow(item)
class ResourceSharingDialog(QDialog, FORM_CLASS):
    TAB_ALL = 0
    TAB_INSTALLED = 1
    TAB_SETTINGS = 2

    def __init__(self, parent=None, iface=None):
        """Constructor.

        :param parent: Optional widget to use as parent
        :type parent: QWidget

        :param iface: An instance of QGisInterface
        :type iface: QGisInterface
        """
        super(ResourceSharingDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface

        # Reconfigure UI
        self.setModal(True)
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)
        self.button_install.setEnabled(False)
        self.button_open.setEnabled(False)
        self.button_uninstall.setEnabled(False)

        # Set up the "main menu" - QListWidgetItem
        # All collections
        icon_all = QIcon()
        icon_all.addFile(str(resources_path('img', 'plugin.svg')), QSize(),
                         QIcon.Normal, QIcon.Off)
        item_all = QListWidgetItem()
        item_all.setIcon(icon_all)
        item_all.setText(self.tr('All collections'))
        # Installed collections
        icon_installed = QIcon()
        icon_installed.addFile(
            str(resources_path('img', 'plugin-installed.svg')), QSize(),
            QIcon.Normal, QIcon.Off)
        item_installed = QListWidgetItem()
        item_installed.setIcon(icon_installed)
        item_installed.setText(self.tr('Installed collections'))
        item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        # Settings / repositories
        icon_settings = QIcon()
        icon_settings.addFile(str(resources_path('img', 'settings.svg')),
                              QSize(), QIcon.Normal, QIcon.Off)
        item_settings = QListWidgetItem()
        item_settings.setIcon(icon_settings)
        item_settings.setText(self.tr('Settings'))
        item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

        # Add the items to the list widget
        self.menu_list_widget.addItem(item_all)
        self.menu_list_widget.addItem(item_installed)
        self.menu_list_widget.addItem(item_settings)

        # Init the message bar
        self.message_bar = QgsMessageBar(self)
        self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.vlayoutRightColumn.insertWidget(0, self.message_bar)

        # Progress dialog for long running processes
        self.progress_dialog = None

        # Init the repository manager dialog
        self.repository_manager = RepositoryManager()
        self.collection_manager = CollectionManager()
        # Collections list view
        self.collections_model = QStandardItemModel(0, 1)
        self.collections_model.sort(0, Qt.AscendingOrder)
        self.collection_proxy = CustomSortFilterProxyModel(self)
        self.collection_proxy.setSourceModel(self.collections_model)
        self.list_view_collections.setModel(self.collection_proxy)
        # Active selected collection
        self._selected_collection_id = None

        # Slots
        self.button_add.clicked.connect(self.add_repository)
        self.button_edit.clicked.connect(self.edit_repository)
        self.button_delete.clicked.connect(self.delete_repository)
        self.button_reload.clicked.connect(self.reload_repositories)
        self.menu_list_widget.currentRowChanged.connect(self.set_current_tab)
        self.list_view_collections.selectionModel().currentChanged.connect(
            self.on_list_view_collections_clicked)
        self.line_edit_filter.textChanged.connect(self.filter_collections)
        self.button_install.clicked.connect(self.install_collection)
        self.button_open.clicked.connect(self.open_collection)
        self.button_uninstall.clicked.connect(self.uninstall_collection)
        self.button_box.button(QDialogButtonBox.Help).clicked.connect(
            self.open_help)

        # Populate the repositories widget and collections list view
        self.populate_repositories_widget()
        self.reload_collections_model()

    def set_current_tab(self, index):
        """Set stacked widget based on the active tab.

        :param index: The index of the active widget (in the list widget).
        :type index: int
        """
        # Clear message bar
        self.message_bar.clearWidgets()
        if index == (self.menu_list_widget.count() - 1):
            # Last menu entry - Settings
            self.stacked_menu_widget.setCurrentIndex(1)
        else:
            # Not settings, must be Collections (all or installed)
            if index == 1:
                # Installed collections
                self.collection_proxy.accepted_status = \
                    COLLECTION_INSTALLED_STATUS
                # Set the web view
                title = self.tr('Installed Collections')
                description = self.tr(
                    'On the left you see the list of all the '
                    'installed collections.')
            else:
                # All collections (0)
                self.collection_proxy.accepted_status = COLLECTION_ALL_STATUS
                # Set the web view
                title = self.tr('All Collections')
                description = self.tr(
                    'On the left you see a list of all the collections '
                    'that are available from the registered repositories.<br> '
                    'Installed collections are emphasized (in <b>bold</b>).')

            context = {
                'resources_path': str(resources_path()),
                'title': title,
                'description': description
            }
            self.web_view_details.setHtml(
                render_template('tab_description.html', context))
            self.stacked_menu_widget.setCurrentIndex(0)

    def add_repository(self):
        """Open add repository dialog."""
        dlg = ManageRepositoryDialog(self)
        if not dlg.exec_():
            return

        for repoName, repo in self.repository_manager.directories.items():
            if dlg.line_edit_url.text().strip() == repo['url']:
                self.message_bar.pushMessage(
                    self.tr(
                        'Unable to add another repository with the same URL!'),
                    Qgis.Warning, 5)
                return
            if dlg.line_edit_name.text().strip() == repoName:
                self.message_bar.pushMessage(
                    self.tr('Repositories must have unique names!'),
                    Qgis.Warning, 5)
                return

        repo_name = dlg.line_edit_name.text()
        repo_url = dlg.line_edit_url.text().strip()
        repo_auth_cfg = dlg.line_edit_auth_id.text().strip()
        if repo_name in self.repository_manager.directories:
            repo_name += '(2)'

        # Show progress dialog
        self.show_progress_dialog("Fetching repository's metadata")

        # Add repository
        try:
            status, adderror = self.repository_manager.add_directory(
                repo_name, repo_url, repo_auth_cfg)
            if status:
                self.message_bar.pushMessage(
                    self.tr('Repository was successfully added'), Qgis.Success,
                    5)
            else:
                self.message_bar.pushMessage(
                    self.tr('Unable to add repository: %s') % adderror,
                    Qgis.Warning, 5)
        except Exception as e:
            self.message_bar.pushMessage(self.tr('%s') % e, Qgis.Warning, 5)
        finally:
            self.progress_dialog.hide()

        # Reload data and widget
        self.reload_data_and_widget()

        # Deactivate edit and delete button
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)

    def edit_repository(self):
        """Open edit repository dialog."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item:
            repo_name = selected_item.text(0)

        if not repo_name:
            return

        # Check if it is among the officially approved QGIS repositories
        settings = QgsSettings()
        settings.beginGroup(repo_settings_group())
        if settings.value(repo_name + '/url') in \
                self.repository_manager._online_directories.values():
            self.message_bar.pushMessage(
                self.tr('You can not edit the official repositories!'),
                Qgis.Warning, 5)
            return

        dlg = ManageRepositoryDialog(self)
        dlg.line_edit_name.setText(repo_name)
        dlg.line_edit_url.setText(
            self.repository_manager.directories[repo_name]['url'])
        dlg.line_edit_auth_id.setText(
            self.repository_manager.directories[repo_name]['auth_cfg'])

        if not dlg.exec_():
            return

        # Check if the changed URL is already present and that
        # the new repository name is unique
        new_url = dlg.line_edit_url.text().strip()
        old_url = self.repository_manager.directories[repo_name]['url']
        new_name = dlg.line_edit_name.text().strip()
        for repoName, repo in self.repository_manager.directories.items():
            if new_url == repo['url'] and (old_url != new_url):
                self.message_bar.pushMessage(
                    self.tr('Unable to add another repository with the same '
                            'URL!'), Qgis.Warning, 5)
                return
            if new_name == repoName and (repo_name != new_name):
                self.message_bar.pushMessage(
                    self.tr('Repositories must have unique names!'),
                    Qgis.Warning, 5)
                return

        # Redundant
        if (new_name in self.repository_manager.directories) and (new_name !=
                                                                  repo_name):
            new_name += '(2)'

        new_auth_cfg = dlg.line_edit_auth_id.text()

        # Show progress dialog
        self.show_progress_dialog("Fetching repository's metadata")

        # Edit repository
        try:
            status, editerror = self.repository_manager.edit_directory(
                repo_name, new_name, old_url, new_url, new_auth_cfg)
            if status:
                self.message_bar.pushMessage(
                    self.tr('Repository is successfully updated'),
                    Qgis.Success, 5)
            else:
                self.message_bar.pushMessage(
                    self.tr('Unable to edit repository: %s') % editerror,
                    Qgis.Warning, 5)
        except Exception as e:
            self.message_bar.pushMessage(self.tr('%s') % e, Qgis.Warning, 5)
        finally:
            self.progress_dialog.hide()

        # Reload data and widget
        self.reload_data_and_widget()

        # Deactivate the edit and delete buttons
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)

    def delete_repository(self):
        """Delete a repository in the tree widget."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item:
            repo_name = selected_item.text(0)

        if not repo_name:
            return
        # Check if it is among the offical repositories
        repo_url = self.repository_manager.directories[repo_name]['url']
        if repo_url in self.repository_manager._online_directories.values():
            self.message_bar.pushMessage(
                self.tr('You can not remove official repositories!'),
                Qgis.Warning, 5)
            return

        warning = self.tr('Are you sure you want to remove the following '
                          'repository?') + '\n' + repo_name
        if QMessageBox.warning(self, self.tr('QGIS Resource Sharing'), warning,
                               QMessageBox.Yes,
                               QMessageBox.No) == QMessageBox.No:
            return

        # Remove repository
        installed_collections = \
            self.collection_manager.get_installed_collections(repo_url)
        if installed_collections:
            message = ('You have installed collections from this '
                       'repository. Please uninstall them first!')
            self.message_bar.pushMessage(message, Qgis.Warning, 5)
        else:
            self.repository_manager.remove_directory(repo_name)
            # Reload data and widget
            self.reload_data_and_widget()
            # Deactivate the edit and delete buttons
            self.button_edit.setEnabled(False)
            self.button_delete.setEnabled(False)

    def reload_repositories(self):
        """Slot for when user clicks reload repositories button."""
        # Show progress dialog
        self.show_progress_dialog('Reloading all repositories')

        for repo_name in self.repository_manager.directories:
            directory = self.repository_manager.directories[repo_name]
            url = directory['url']
            auth_cfg = directory['auth_cfg']
            try:
                status, reloaderror = self.repository_manager.reload_directory(
                    repo_name, url, auth_cfg)
                if status:
                    self.message_bar.pushMessage(
                        self.tr('Repository %s is successfully reloaded') %
                        repo_name, Qgis.Info, 5)
                else:
                    self.message_bar.pushMessage(
                        self.tr('Unable to reload %s: %s') %
                        (repo_name, reloaderror), Qgis.Warning, 5)
            except Exception as e:
                self.message_bar.pushMessage(
                    self.tr('%s') % e, Qgis.Warning, 5)

        self.progress_dialog.hide()
        # Reload data and widget
        self.reload_data_and_widget()

    def install_collection(self):
        """Slot for when the user clicks the install/reinstall button."""
        # Save the current index to enable selection after installation
        self.current_index = self.list_view_collections.currentIndex()
        self.show_progress_dialog('Starting installation...')
        self.progress_dialog.canceled.connect(self.install_canceled)

        self.installer_thread = QThread()
        self.installer_worker = CollectionInstaller(
            self.collection_manager, self._selected_collection_id)
        self.installer_worker.moveToThread(self.installer_thread)
        self.installer_worker.finished.connect(self.install_finished)
        self.installer_worker.aborted.connect(self.install_aborted)
        self.installer_worker.progress.connect(self.install_progress)
        self.installer_thread.started.connect(self.installer_worker.run)
        self.installer_thread.start()

    def install_finished(self):
        # Process the result
        self.progress_dialog.hide()
        installStatus = self.installer_worker.install_status
        if not installStatus:
            message = self.installer_worker.error_message
        # Clean up the worker and thread
        self.installer_worker.deleteLater()
        self.installer_thread.quit()
        self.installer_thread.wait()
        self.installer_thread.deleteLater()

        if installStatus:
            self.reload_collections_model()
            # Report what has been installed
            message = '<b>%s</b> was successfully installed, containing:\n<ul>' % (
                config.COLLECTIONS[self._selected_collection_id]['name'])
            number = 0
            if 'style' in config.COLLECTIONS[
                    self._selected_collection_id].keys():
                number = config.COLLECTIONS[
                    self._selected_collection_id]['style']
                message = message + '\n<li> ' + str(
                    number) + ' Layer style (QML) file'
                if number > 1:
                    message = message + 's'
            if 'symbol' in config.COLLECTIONS[
                    self._selected_collection_id].keys():
                number = config.COLLECTIONS[
                    self._selected_collection_id]['symbol']
                message = message + '\n<li> ' + str(
                    number) + ' XML symbol file'
                if number > 1:
                    message = message + 's'
            if 'svg' in config.COLLECTIONS[
                    self._selected_collection_id].keys():
                number = config.COLLECTIONS[
                    self._selected_collection_id]['svg']
                message = message + '\n<li> ' + str(number) + ' SVG file'
                if number > 1:
                    message = message + 's'
            if 'models' in config.COLLECTIONS[
                    self._selected_collection_id].keys():
                number = config.COLLECTIONS[
                    self._selected_collection_id]['models']
                message = message + '\n<li> ' + str(number) + ' model'
                if number > 1:
                    message = message + 's'
            if 'expressions' in config.COLLECTIONS[
                    self._selected_collection_id].keys():
                number = config.COLLECTIONS[
                    self._selected_collection_id]['expressions']
                message = message + '\n<li> ' + str(
                    number) + ' expression file'
                if number > 1:
                    message = message + 's'
            if 'processing' in config.COLLECTIONS[
                    self._selected_collection_id].keys():
                number = config.COLLECTIONS[
                    self._selected_collection_id]['processing']
                message = message + '\n<li> ' + str(
                    number) + ' processing script'
                if number > 1:
                    message = message + 's'
            if 'rscripts' in config.COLLECTIONS[
                    self._selected_collection_id].keys():
                number = config.COLLECTIONS[
                    self._selected_collection_id]['rscripts']
                message = message + '\n<li> ' + str(number) + ' R script'
                if number > 1:
                    message = message + 's'
            message = message + '\n</ul>'
        QMessageBox.information(self, 'Resource Sharing', message)
        self.populate_repositories_widget()
        # Set the selection
        oldRow = self.current_index.row()
        newIndex = self.collections_model.createIndex(oldRow, 0)
        selection_model = self.list_view_collections.selectionModel()
        selection_model.setCurrentIndex(newIndex,
                                        selection_model.ClearAndSelect)
        selection_model.select(newIndex, selection_model.ClearAndSelect)
        # Update the buttons
        self.button_install.setEnabled(True)
        self.button_install.setText('Reinstall')
        self.button_open.setEnabled(True)
        self.button_uninstall.setEnabled(True)

        self.show_collection_metadata(self._selected_collection_id)

    def install_canceled(self):
        self.progress_dialog.hide()
        self.show_progress_dialog('Cancelling installation...')
        self.installer_worker.abort()

    def install_aborted(self):
        if self.installer_thread.isRunning():
            self.installer_thread.quit()
        self.installer_thread.finished.connect(self.progress_dialog.hide)

    def install_progress(self, text):
        self.progress_dialog.setLabelText(text)

    def uninstall_collection(self):
        """Slot called when user clicks the uninstall button."""
        # get the QModelIndex for the item to be uninstalled
        uninstall_index = self.list_view_collections.currentIndex()
        coll_id = self._selected_collection_id
        try:
            self.collection_manager.uninstall(coll_id)
        except Exception as e:
            LOGGER.error('Could not uninstall collection ' +
                         config.COLLECTIONS[coll_id]['name'] + ':\n' + str(e))
        else:
            QMessageBox.information(
                self, 'Resource Sharing',
                'The collection was successfully uninstalled!')
            self.reload_collections_model()
            # Fix the GUI
            currentMenuRow = self.menu_list_widget.currentRow()
            self.set_current_tab(currentMenuRow)
            self.populate_repositories_widget()

            rowCount = self.collection_proxy.rowCount()
            if rowCount > 0:
                # Set the current (and selected) row in the listview
                newRow = uninstall_index.row()
                # Check if this was the last element
                rowCount = self.collection_proxy.rowCount()
                if newRow == rowCount:
                    newRow = newRow - 1
                # Select the new current element
                newIndex = self.collections_model.createIndex(newRow, 0)
                selection_model = self.list_view_collections.selectionModel()
                selection_model.setCurrentIndex(newIndex,
                                                selection_model.ClearAndSelect)
                # Get the id of the current collection
                proxyModel = self.list_view_collections.model()
                proxyIndex = proxyModel.index(newRow, 0)
                current_coll_id = proxyIndex.data(COLLECTION_ID_ROLE)
                self._selected_collection_id = current_coll_id
                # Update buttons
                status = config.COLLECTIONS[current_coll_id]['status']
                if status == COLLECTION_INSTALLED_STATUS:
                    self.button_install.setEnabled(True)
                    self.button_install.setText('Reinstall')
                    self.button_open.setEnabled(True)
                    self.button_uninstall.setEnabled(True)
                else:
                    self.button_install.setEnabled(True)
                    self.button_install.setText('Install')
                    self.button_open.setEnabled(False)
                    self.button_uninstall.setEnabled(False)
                # Update the web_view_details frame
                self.show_collection_metadata(current_coll_id)
            else:
                self.button_install.setEnabled(False)
                self.button_install.setText('Install')
                self.button_open.setEnabled(False)
                self.button_uninstall.setEnabled(False)

    def open_collection(self):
        """Slot for when user clicks 'Open' button."""
        collection_path = local_collection_path(self._selected_collection_id)
        directory_url = QUrl.fromLocalFile(str(collection_path))
        QDesktopServices.openUrl(directory_url)

    def reload_data_and_widget(self):
        """Reload repositories and collections and update widgets related."""
        self.reload_repositories_widget()
        self.reload_collections_model()

    def reload_repositories_widget(self):
        """Refresh tree repositories using new repositories data."""
        self.repository_manager.load_directories()
        self.populate_repositories_widget()

    def populate_repositories_widget(self):
        """Populate the current dictionary repositories to the tree widget."""
        # Clear the current tree widget
        self.tree_repositories.clear()
        installed_collections = \
            self.collection_manager.get_installed_collections()
        # Export the updated ones from the repository manager
        for repo_name in self.repository_manager.directories:
            url = self.repository_manager.directories[repo_name]['url']
            item = QTreeWidgetItem(self.tree_repositories, REPOSITORY_ITEM)
            item.setText(0, repo_name)
            item.setText(1, url)
            for coll_id in config.COLLECTIONS:
                if ('repository_name' in config.COLLECTIONS[coll_id].keys()
                        and config.COLLECTIONS[coll_id]['repository_name']
                        == repo_name):
                    coll_name = config.COLLECTIONS[coll_id]['name']
                    coll_tags = config.COLLECTIONS[coll_id]['tags']
                    collectionItem = QTreeWidgetItem(item, COLLECTION_ITEM)
                    collitemtext = coll_name
                    if installed_collections and coll_id in installed_collections.keys(
                    ):
                        collitemtext = coll_name + ' (installed)'
                        collectionFont = QFont()
                        collectionFont.setWeight(60)
                        collectionItem.setFont(0, collectionFont)
                    collectionItem.setText(0, collitemtext)
                    collectionItem.setText(1, coll_tags)
        self.tree_repositories.resizeColumnToContents(0)
        self.tree_repositories.resizeColumnToContents(1)
        self.tree_repositories.sortItems(1, Qt.AscendingOrder)

    def reload_collections_model(self):
        """Reload the collections model with the current collections."""
        self.collections_model.clear()
        installed_collections = \
            self.collection_manager.get_installed_collections()
        for id in config.COLLECTIONS:
            collection_name = config.COLLECTIONS[id]['name']
            collection_author = config.COLLECTIONS[id]['author']
            collection_tags = config.COLLECTIONS[id]['tags']
            collection_description = config.COLLECTIONS[id]['description']
            collection_status = config.COLLECTIONS[id]['status']
            repository_name = ''
            if 'repository_name' in config.COLLECTIONS[id].keys():
                repository_name = config.COLLECTIONS[id]['repository_name']
            item = QStandardItem(collection_name + ' (' + repository_name +
                                 ')')
            item.setEditable(False)
            item.setData(id, COLLECTION_ID_ROLE)
            item.setData(collection_name, COLLECTION_NAME_ROLE)
            item.setData(collection_description, COLLECTION_DESCRIPTION_ROLE)
            item.setData(collection_author, COLLECTION_AUTHOR_ROLE)
            item.setData(collection_tags, COLLECTION_TAGS_ROLE)
            item.setData(collection_status, COLLECTION_STATUS_ROLE)
            # Make installed collections stand out
            if installed_collections and id in installed_collections.keys():
                collectionFont = QFont()
                collectionFont.setWeight(60)
                item.setFont(collectionFont)
            self.collections_model.appendRow(item)
        self.collections_model.sort(0, Qt.AscendingOrder)

    def on_tree_repositories_itemSelectionChanged(self):
        """Slot for the itemSelectionChanged signal of tree_repositories."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item and selected_item.type() == REPOSITORY_ITEM:
            if selected_item:
                repo_name = selected_item.text(0)
            if not repo_name:
                return
            if not repo_name in self.repository_manager.directories.keys():
                return
            repo_url = self.repository_manager.directories[repo_name]['url']
            # Disable the edit and delete buttons for "official" repositories
            if repo_url in self.repository_manager._online_directories.values(
            ):
                self.button_edit.setEnabled(False)
                self.button_delete.setEnabled(False)
            else:
                # Activate the edit and delete buttons
                self.button_edit.setEnabled(True)
                self.button_delete.setEnabled(True)
        elif selected_item and selected_item.type() == COLLECTION_ITEM:
            self.button_edit.setEnabled(False)
            self.button_delete.setEnabled(False)
        else:
            self.button_edit.setEnabled(False)
            self.button_delete.setEnabled(False)

    def on_list_view_collections_clicked(self, index):
        """Slot for when the list_view_collections is clicked."""
        real_index = self.collection_proxy.mapToSource(index)
        if real_index.row() != -1:
            collection_item = self.collections_model.itemFromIndex(real_index)
            collection_id = collection_item.data(COLLECTION_ID_ROLE)
            self._selected_collection_id = collection_id

            # Enable / disable buttons
            status = config.COLLECTIONS[self._selected_collection_id]['status']
            is_installed = status == COLLECTION_INSTALLED_STATUS
            if is_installed:
                self.button_install.setEnabled(True)
                self.button_install.setText('Reinstall')
                self.button_open.setEnabled(True)
                self.button_uninstall.setEnabled(True)
            else:
                self.button_install.setEnabled(True)
                self.button_install.setText('Install')
                self.button_open.setEnabled(False)
                self.button_uninstall.setEnabled(False)

            # Show  metadata
            self.show_collection_metadata(collection_id)

    @pyqtSlot(str)
    def filter_collections(self, text):
        search = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp)
        self.collection_proxy.setFilterRegExp(search)

    def show_collection_metadata(self, id):
        """Show the collection metadata given the ID."""
        html = self.collection_manager.get_html(id)
        self.web_view_details.setHtml(html)

    def reject(self):
        """Slot when the dialog is closed."""
        # Serialize collections to settings
        self.repository_manager.serialize_repositories()
        self.done(0)

    def open_help(self):
        """Open help."""
        doc_url = QUrl('http://qgis-contribution.github.io/' +
                       'QGIS-ResourceSharing/')
        QDesktopServices.openUrl(doc_url)

    def show_progress_dialog(self, text):
        """Show infinite progress dialog with given text.

        :param text: Text as the label of the progress dialog
        :type text: str
        """
        if self.progress_dialog is None:
            self.progress_dialog = QProgressDialog(self)
            self.progress_dialog.setWindowModality(Qt.WindowModal)
            self.progress_dialog.setAutoClose(False)
            title = self.tr('Resource Sharing')
            self.progress_dialog.setWindowTitle(title)
            # Just use an infinite progress bar here
            self.progress_dialog.setMaximum(0)
            self.progress_dialog.setMinimum(0)
            self.progress_dialog.setValue(0)
            self.progress_dialog.setLabelText(text)

        self.progress_dialog.show()
Exemplo n.º 3
0
class MultipleInputDialog(BASE, WIDGET):

    def __init__(self, options, selectedoptions=None, datatype=None):
        super(MultipleInputDialog, self).__init__(None)
        self.setupUi(self)
        self.datatype = datatype
        self.model = None

        self.options = []
        for i, option in enumerate(options):
            if option is None or isinstance(option, str):
                self.options.append((i, option))
            else:
                self.options.append((option[0], option[1]))

        self.selectedoptions = selectedoptions or []

        # Additional buttons
        self.btnSelectAll = QPushButton(self.tr('Select All'))
        self.buttonBox.addButton(self.btnSelectAll,
                                 QDialogButtonBox.ActionRole)
        self.btnClearSelection = QPushButton(self.tr('Clear Selection'))
        self.buttonBox.addButton(self.btnClearSelection,
                                 QDialogButtonBox.ActionRole)
        self.btnToggleSelection = QPushButton(self.tr('Toggle Selection'))
        self.buttonBox.addButton(self.btnToggleSelection,
                                 QDialogButtonBox.ActionRole)
        if self.datatype is not None:
            btnAddFile = QPushButton(QCoreApplication.translate("MultipleInputDialog", 'Add File(s)…'))
            btnAddFile.clicked.connect(self.addFiles)
            self.buttonBox.addButton(btnAddFile,
                                     QDialogButtonBox.ActionRole)

        self.btnSelectAll.clicked.connect(lambda: self.selectAll(True))
        self.btnClearSelection.clicked.connect(lambda: self.selectAll(False))
        self.btnToggleSelection.clicked.connect(self.toggleSelection)

        self.settings = QgsSettings()
        self.restoreGeometry(self.settings.value("/Processing/multipleInputDialogGeometry", QByteArray()))

        self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.lstLayers.setDragDropMode(QAbstractItemView.InternalMove)

        self.populateList()
        self.finished.connect(self.saveWindowGeometry)

    def saveWindowGeometry(self):
        self.settings.setValue("/Processing/multipleInputDialogGeometry", self.saveGeometry())

    def populateList(self):
        self.model = QStandardItemModel()
        for value, text in self.options:
            item = QStandardItem(text)
            item.setData(value, Qt.UserRole)
            item.setCheckState(Qt.Checked if value in self.selectedoptions else Qt.Unchecked)
            item.setCheckable(True)
            item.setDropEnabled(False)
            self.model.appendRow(item)

        # add extra options (e.g. manually added layers)
        for t in [o for o in self.selectedoptions if not isinstance(o, int)]:
            if isinstance(t, QgsProcessingModelChildParameterSource):
                item = QStandardItem(t.staticValue())
            else:
                item = QStandardItem(t)
            item.setData(item.text(), Qt.UserRole)
            item.setCheckState(Qt.Checked)
            item.setCheckable(True)
            item.setDropEnabled(False)
            self.model.appendRow(item)

        self.lstLayers.setModel(self.model)

    def accept(self):
        self.selectedoptions = []
        model = self.lstLayers.model()
        for i in range(model.rowCount()):
            item = model.item(i)
            if item.checkState() == Qt.Checked:
                self.selectedoptions.append(item.data(Qt.UserRole))
        QDialog.accept(self)

    def reject(self):
        self.selectedoptions = None
        QDialog.reject(self)

    def getItemsToModify(self):
        items = []
        if len(self.lstLayers.selectedIndexes()) > 1:
            for i in self.lstLayers.selectedIndexes():
                items.append(self.model.itemFromIndex(i))
        else:
            for i in range(self.model.rowCount()):
                items.append(self.model.item(i))
        return items

    def selectAll(self, value):
        for item in self.getItemsToModify():
            item.setCheckState(Qt.Checked if value else Qt.Unchecked)

    def toggleSelection(self):
        for item in self.getItemsToModify():
            checked = item.checkState() == Qt.Checked
            item.setCheckState(Qt.Unchecked if checked else Qt.Checked)

    def getFileFilter(self, datatype):
        """
        Returns a suitable file filter pattern for the specified parameter definition
        :param param:
        :return:
        """
        if datatype == QgsProcessing.TypeRaster:
            return QgsProviderRegistry.instance().fileRasterFilters()
        elif datatype == QgsProcessing.TypeFile:
            return self.tr('All files (*.*)')
        else:
            exts = QgsVectorFileWriter.supportedFormatExtensions()
            for i in range(len(exts)):
                exts[i] = self.tr('{0} files (*.{1})').format(exts[i].upper(), exts[i].lower())
            return self.tr('All files (*.*)') + ';;' + ';;'.join(exts)

    def addFiles(self):
        filter = self.getFileFilter(self.datatype)

        settings = QgsSettings()
        path = str(settings.value('/Processing/LastInputPath'))

        ret, selected_filter = QFileDialog.getOpenFileNames(self, self.tr('Select File(s)'),
                                                            path, filter)
        if ret:
            files = list(ret)
            settings.setValue('/Processing/LastInputPath',
                              os.path.dirname(str(files[0])))
            for filename in files:
                item = QStandardItem(filename)
                item.setData(filename, Qt.UserRole)
                item.setCheckState(Qt.Checked)
                item.setCheckable(True)
                item.setDropEnabled(False)
                self.model.appendRow(item)
Exemplo n.º 4
0
class contentAuthDlg(WIDGET, BASE):
    '''
    Content authorization dialog
    '''
    def __init__(self, plugin):
        QDialog.__init__(self, plugin.iface.mainWindow())
        self.setupUi(self)

        QgsGui.enableAutoGeometryRestore(self)

        # Initialize the dialog
        self.initGui()

        # Load users
        self.loadContent()

        # Load Roles
        self.loadRoles()

        # Reference to the currently selected STDM content item
        self.currentContent = None

    def initGui(self):
        '''
        Initialize GUI properties
        '''
        # Disable any action by the user in the roles view
        self.lstRoles.setEnabled(False)

        # Connect signals
        self.lstContent.activated.connect(self.onContentClicked)
        self.lstContent.clicked.connect(self.onContentClicked)
        self.lstRoles.activated.connect(self.onRoleSelected)
        self.lstRoles.clicked.connect(self.onRoleSelected)

    def loadContent(self):
        '''
        Loads STDM content items
        '''
        self.content = Content()
        # self.content=Table('content_base',Base.metadata,autoload=True,autoload_with=STDMDb.instance().engine)
        cntItems = self.content.queryObject().all()
        '''
        self.content=Table('content_base',Base.metadata,autoload=True,autoload_with=STDMDb.instance().engine)
        
        session= STDMDb.instance().session
        cntItems=session.query(self.content)
        '''
        cnts = [cntItem.name for cntItem in cntItems]
        self.contentModel = UsersRolesModel(cnts)
        self.lstContent.setModel(self.contentModel)

    def loadRoles(self, contentname=""):
        '''
        Loads the roles in the database cluster
        '''
        self.roleProvider = RoleProvider()
        sysRoles = self.roleProvider.GetAllRoles()
        roles = []

        # Load the corresponding roles for the specified content item
        cnt = Content()
        if contentname != "":
            self.currentContent = self.content.queryObject().filter(
                Content.name == contentname).first()
            if self.currentContent:
                roles = [rl.name for rl in self.currentContent.roles]

        # Initialize model
        self.roleMappingsModel = QStandardItemModel(self)
        self.roleMappingsModel.setColumnCount(1)

        # Add role items into the standard item model
        for r in range(len(sysRoles)):
            role = sysRoles[r]
            if role.name != "postgres":
                roleItem = self._createNewRoleItem(role.name)

                # Check if the db role is in the approved for the current content item
                roleIndex = getIndex(roles, role.name)
                if roleIndex != -1:
                    roleItem.setCheckState(Qt.Checked)

                self.roleMappingsModel.appendRow(roleItem)

        self.lstRoles.setModel(self.roleMappingsModel)

    def _createNewRoleItem(self, rolename):
        '''
        Creates a custom role item for use in a QStandardItemModel
        '''
        # Set icon
        icon = QIcon()
        icon.addPixmap(GuiUtils.get_icon_pixmap("roles.png"), QIcon.Normal,
                       QIcon.Off)

        roleItem = QStandardItem(icon, rolename)
        roleItem.setCheckable(True)
        roleItem.setCheckState(Qt.Unchecked)

        return roleItem

    def onContentClicked(self, index):
        '''
        Slot activated when a content item is selected to load the roles for the specified content items
        '''
        self.lstRoles.setEnabled(True)
        contentName = index.data()
        self.loadRoles(contentName)
        self.privilege_provider = SinglePrivilegeProvider(
            contentName, current_profile())

    def onRoleSelected(self, index):
        '''
        Slot which is called when a user checks/unchecks to add/remove a role for the
        specified content item.
        '''
        if self.currentContent != None:

            item = self.roleMappingsModel.itemFromIndex(index)
            rolename = item.text()

            self.privilege_provider.role = rolename

            # Get role object from role name
            role = Role()
            rl = role.queryObject().filter(Role.name == rolename).first()

            self.blockSignals(True)

            # Add role to the content item if the item is selected  or remove if it was previosuly checked
            if item.checkState() == Qt.Checked:
                self.currentContent.roles.append(rl)
                self.privilege_provider.grant_privilege()

            elif item.checkState() == Qt.Unchecked:
                self.currentContent.roles.remove(rl)
                self.privilege_provider.revoke_privilege()

            self.currentContent.update()

            self.blockSignals(False)
Exemplo n.º 5
0
class checkableMapLayerList(QWidget):

    # create a checkable list of the map layers

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        layerList = QgsProject.instance().layerTreeRoot().findLayers()
        self.iface = iface
        """for layer in layerList:
            print(layer.name())"""

        self.selectedLayers = []

        layout = QVBoxLayout()
        self.model = QStandardItemModel()

        self.select_all_cb = QCheckBox('Check All')
        self.select_all_cb.setChecked(True)
        self.select_all_cb.setStyleSheet('margin-left: 5px; font: bold')
        #self.select_all_cb.stateChanged.connect(lambda: selectAllCheckChanged(select_all_cb, model))
        layout.addWidget(self.select_all_cb)

        self.view = QListView()
        self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.view.setSelectionMode(QAbstractItemView.NoSelection)
        self.view.setSelectionRectVisible(False)

        for layer in layerList:
            item = QStandardItem(layer.name())
            # item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
            # item.setData(QVariant(Qt.Checked), Qt.CheckStateRole)
            item.setCheckable(True)
            item.setSelectable(False)
            item.setCheckState(QtCore.Qt.Checked)
            self.model.appendRow(item)
            self.selectedLayers.append(item)

        self.view.setModel(self.model)

        #view.clicked.connect(lambda: listviewCheckChanged(item, model, select_all_cb))

        layout.addWidget(self.view)

        self.setLayout(layout)
        """if parent:
            parent.setLayout(layout)
        else:
            window = QWidget()
            window.setLayout(layout)"""
        #window.show()

    def selectAllCheckChanged(self, select_all_cb, model):
        TOMsMessageLog.logMessage("IN selectAllCheckChanged", level=Qgis.Info)
        for index in range(model.rowCount()):
            item = model.item(index)
            if item.isCheckable():
                if select_all_cb.isChecked():
                    item.setCheckState(QtCore.Qt.Checked)
                    self.selectedLayers.append(item)
                else:
                    item.setCheckState(QtCore.Qt.Unchecked)
                    self.selectedLayers.remove(item)

        TOMsMessageLog.logMessage(
            "IN selectAllCheckChanged: len list {}".format(
                len(self.selectedLayers)),
            level=Qgis.Info)

    def listviewCheckChanged(self, model, select_all_cb):
        ''' updates the select all checkbox based on the listview '''
        # model = self.listview.model()
        TOMsMessageLog.logMessage("IN listviewCheckChanged", level=Qgis.Info)
        items = [model.item(index) for index in range(model.rowCount())]
        if all(item.checkState() == QtCore.Qt.Checked for item in items):
            select_all_cb.setTristate(False)
            select_all_cb.setCheckState(QtCore.Qt.Checked)
        elif any(item.checkState() == QtCore.Qt.Checked for item in items):
            select_all_cb.setTristate(True)
            select_all_cb.setCheckState(QtCore.Qt.PartiallyChecked)
        else:
            select_all_cb.setTristate(False)
            select_all_cb.setCheckState(QtCore.Qt.Unchecked)

    def updateSelectedLayers(self, index):
        #QMessageBox.information(self.iface.mainWindow(), "debug", "IN updateSelectedLayers: {}".format(self.model.itemFromIndex(index)))
        TOMsMessageLog.logMessage("IN updateSelectedLayers: {}".format(index),
                                  level=Qgis.Info)
        item = self.model.itemFromIndex(index)
        if item.checkState() == QtCore.Qt.Checked:
            self.selectedLayers.append(item)
        else:
            self.selectedLayers.remove(item)

    def getSelectedLayers(self):
        return self.selectedLayers
Exemplo n.º 6
0
class QVDistrictesBarris(QObject):

    __distBarrisCSV = r'Dades\DIST_BARRIS.csv'
    __zones = r'Dades\Zones.gpkg'

    def __init__(self):
        super().__init__()
        self.labels = []
        self.registre = {}

        # Model
        self.model = QStandardItemModel()
        self.llegirZonesGPKG()
        #self.llegirDistrictesBarrisCSV()

        # View
        self.view = QTreeView()

        self.view.setContextMenuPolicy(Qt.ActionsContextMenu)

        self.actExpand = QAction("Expandeix/Contreu Tot", self)
        self.actExpand.setStatusTip("Expand")
        self.actExpand.triggered.connect(self.expand_all)
        self.view.addAction(self.actExpand)

        self.view.setModel(self.model)
        self.iniView()

    def expand_all(self):
        """Expandir o contraer todo el arbol, dependiendo de si detecta que esta extendido o contraido
        """
        if self.view.isExpanded(self.model.index(0, 0, QModelIndex())):
            self.view.collapseAll()
        else:
            self.view.expandAll()

    def iniView(self):
        self.view.setHeaderHidden(True)
        for i in range(self.model.columnCount()):
            if i == 0:
                self.view.setColumnHidden(i, False)
                self.view.resizeColumnToContents(i)
                self.view.resizeColumnToContents(i)
            else:
                self.view.setColumnHidden(i, True)
        self.view.setEditTriggers(QTreeView.NoEditTriggers)
        self.expand_all()

    def llegirZonesGPKG(self):
        try:
            QvFuncions.setReadOnlyFile(self.__zones)
            pathDistrictes = self.__zones + '|layername=districtes'
            layerDistrictes = QgsVectorLayer(pathDistrictes, 'ogr')
            pathBarris = self.__zones + '|layername=barris'
            layerBarris = QgsVectorLayer(pathBarris, 'ogr')

            rowsDistrictes = layerDistrictes.getFeatures()
            llistaDistrictes = []
            for rowD in rowsDistrictes:
                #print(rowD.attributes())
                #zona = ""
                num_districte = rowD.attributes()[1]
                nom_districte = rowD.attributes()[2]
                num_barri = ""

                nom_barri = ""
                geometria = rowD.geometry().boundingBox()
                x_min = str(geometria.xMinimum())
                y_min = str(geometria.yMinimum())
                x_max = str(geometria.xMaximum())
                y_max = str(geometria.yMaximum())
                item = [
                    num_districte, nom_districte, num_barri, nom_barri, x_min,
                    y_min, x_max, y_max
                ]
                llistaDistrictes.append(item)

            def ordenaPerNumDistricte(elem):
                return elem[0]

            llistaDistrictes.sort(key=ordenaPerNumDistricte)
            #print(llistaDistrictes)

            rowsBarris = layerBarris.getFeatures()
            llistaBarris = []
            for rowB in rowsBarris:
                #print(rowB.attributes())
                #zona = ""
                num_districte = rowB.attributes()[3]
                nom_districte = llistaDistrictes[int(num_districte) - 1][1]
                num_barri = rowB.attributes()[1]
                nom_barri = rowB.attributes()[2]
                geometria = rowB.geometry().boundingBox()
                x_min = str(geometria.xMinimum())
                y_min = str(geometria.yMinimum())
                x_max = str(geometria.xMaximum())
                y_max = str(geometria.yMaximum())
                item = [
                    num_districte, nom_districte, num_barri, nom_barri, x_min,
                    y_min, x_max, y_max
                ]
                llistaBarris.append(item)

            def ordenaPerNumBarri(elem):
                return elem[2]

            llistaBarris.sort(key=ordenaPerNumBarri)
            #print(llistaBarris)

            self.labels = [
                "ZONA", "DISTRICTE", "NOM_DISTRICTE", "BARRI", "NOM_BARRI",
                "X_MIN", "Y_MIN", "X_MAX", "Y_MAX"
            ]
            root = self.model.invisibleRootItem()
            self.model.setColumnCount(len(self.labels))
            self.model.setHorizontalHeaderLabels(self.labels)

            #Afegir Barcelona com a arrel de l'arbre
            bcn_dades = [
                "00", "Barcelona", "00", "Barcelona", "419710.0553820258",
                "4573818.80776309", "436533.35", "4591775.02"
            ]
            bcn = [QStandardItem("Barcelona")]
            for item in bcn_dades:
                bcn.append(QStandardItem(item))
            root.appendRow(bcn)

            ultimaDistr = -1
            itDist = 0
            for b in llistaBarris:
                if ultimaDistr != int(b[0]):  #Afegir següent districte
                    dist = [QStandardItem(llistaDistrictes[itDist][1])]
                    for i in range(0, len(llistaDistrictes[itDist])):
                        dist.append(QStandardItem(llistaDistrictes[itDist][i]))
                    bcn[0].appendRow(dist)

                    itDist = itDist + 1
                    #Afegir següent Barri
                barri = [QStandardItem(b[3])]
                for item in b:
                    barri.append(QStandardItem(item))
                dist[0].appendRow(barri)
                ultimaDistr = int(b[0])
            return True
        except:
            print("Error en construcció de l'arbre de zones")
            return False

    # def llegirDistrictesBarrisCSV(self):
    #     try:
    #         first = True
    #         with open(self.__distBarrisCSV, newline='') as csvFile:
    #             reader = csv.DictReader(csvFile, delimiter=';')
    #             root = self.model.invisibleRootItem()
    #             for row in reader:
    #                 if first: # Primer registro
    #                     self.labels = ['ZONA']
    #                     for item in row:
    #                         self.labels.append(item)
    #                     self.model.setColumnCount(len(self.labels))
    #                     self.model.setHorizontalHeaderLabels(self.labels)
    #                     first = False
    #                 if row['BARRI'] == '': # Registro de distrito
    #                     dist = [QStandardItem(row['NOM_DISTRICTE'])]
    #                     for item in row.values():
    #                         dist.append(QStandardItem(item))
    #                     root.appendRow(dist)
    #                 else: # Registro de barrio
    #                     barri = [QStandardItem(row['NOM_BARRI'])]
    #                     for item in row.values():
    #                         barri.append(QStandardItem(item))
    #                     dist[0].appendRow(barri)
    #         return True
    #     except:
    #         print('QDistrictesBarris.llegirDistrictesBarrisCSV(): ', sys.exc_info()[0], sys.exc_info()[1])
    #         return False

    def llegirRegistre(self):
        try:
            click = self.view.currentIndex()
            #Controlarem si s'ha canviat d'índex o no
            if hasattr(self, 'ultimIndex') and self.ultimIndex == click:
                return self.registre
            self.ultimIndex = click
            self.registre = {}
            for i in range(self.model.columnCount()):
                index = click.sibling(click.row(), i)
                item = self.model.itemFromIndex(index)
                self.registre[self.labels[i]] = item.text()
            self.registre['RANG'] = QgsRectangle(float(self.registre['X_MIN']), \
                                                float(self.registre['Y_MIN']), \
                                                float(self.registre['X_MAX']), \
                                                float(self.registre['Y_MAX']))
        except:
            print('QDistrictesBarris.llegirRegistre(): ',
                  sys.exc_info()[0],
                  sys.exc_info()[1])
        finally:
            return self.registre

    def llegirRang(self):
        return self.llegirRegistre()['RANG']

    def esDistricte(self):
        return self.llegirRegistre()['BARRI'] == ''

    def esBarri(self):
        return not self.esDistricte()

    def llegirNom(self):
        if self.esDistricte():
            distr = self.llegirRegistre()["NOM_DISTRICTE"]
            distr_d = distr + "_d"
            return distr_d
        else:
            barri = self.llegirRegistre()["NOM_BARRI"]
            if barri == "Barcelona":
                return barri
            else:
                barri_b = barri + "_b"
                return barri_b

    def llegirID(self):
        if self.esDistricte():
            return self.llegirRegistre()['DISTRICTE']
        return self.llegirRegistre()['BARRI']
Exemplo n.º 7
0
class SPAQLunicornDialog(QtWidgets.QDialog, FORM_CLASS):
    ## The triple store configuration file
    triplestoreconf = None
    ## Prefix map
    prefixes = None

    enrichtab = None

    interlinktab = None

    conceptList = None

    completerClassList = None

    columnvars = {}

    def __init__(self,
                 triplestoreconf={},
                 prefixes=[],
                 addVocabConf={},
                 autocomplete={},
                 prefixstore={
                     "normal": {},
                     "reversed": {}
                 },
                 savedQueriesJSON={},
                 maindlg=None,
                 parent=None):
        """Constructor."""
        super(SPAQLunicornDialog, self).__init__(parent)
        self.setupUi(self)
        self.prefixes = prefixes
        self.maindlg = maindlg
        self.savedQueriesJSON = savedQueriesJSON
        self.enrichtab = EnrichmentTab(self)
        self.interlinktab = InterlinkingTab(self)
        self.addVocabConf = addVocabConf
        self.autocomplete = autocomplete
        self.prefixstore = prefixstore
        self.triplestoreconf = triplestoreconf
        self.searchTripleStoreDialog = TripleStoreDialog(
            self.triplestoreconf, self.prefixes, self.prefixstore,
            self.comboBox)
        self.geoClassList.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.geoClassList.setAlternatingRowColors(True)
        self.geoClassList.setViewMode(QListView.ListMode)
        self.geoClassList.setContextMenuPolicy(Qt.CustomContextMenu)
        self.geoClassList.customContextMenuRequested.connect(self.onContext)
        self.geoClassListModel = QStandardItemModel()
        self.proxyModel = QSortFilterProxyModel(self)
        self.proxyModel.sort(0)
        self.proxyModel.setSourceModel(self.geoClassListModel)
        self.geoClassList.setModel(self.proxyModel)
        self.geoClassListModel.clear()
        self.queryLimit.setValidator(QRegExpValidator(QRegExp("[0-9]*")))
        self.filterConcepts.textChanged.connect(self.setFilterFromText)
        self.inp_sparql2 = ToolTipPlainText(self.tab, self.triplestoreconf,
                                            self.comboBox, self.columnvars,
                                            self.prefixes, self.autocomplete)
        self.inp_sparql2.move(10, 130)
        self.inp_sparql2.setMinimumSize(780, 401)
        self.inp_sparql2.document().defaultFont().setPointSize(16)
        self.inp_sparql2.setPlainText(
            "SELECT ?item ?lat ?lon WHERE {\n ?item ?b ?c .\n ?item <http://www.wikidata.org/prop:P123> ?def .\n}"
        )
        self.inp_sparql2.columnvars = {}
        self.inp_sparql2.textChanged.connect(self.validateSPARQL)
        self.sparqlhighlight = SPARQLHighlighter(self.inp_sparql2)
        self.areaconcepts.hide()
        self.areas.hide()
        self.label_8.hide()
        self.label_9.hide()
        self.savedQueries.hide()
        self.loadQuery.hide()
        self.saveQueryButton.hide()
        self.saveQueryName.hide()
        self.savedQueryLabel.hide()
        self.saveQueryName_2.hide()
        self.enrichTableResult.hide()
        self.queryTemplates.currentIndexChanged.connect(self.viewselectaction)
        self.bboxButton.clicked.connect(self.getPointFromCanvas)
        self.interlinkTable.cellClicked.connect(
            self.createInterlinkSearchDialog)
        self.enrichTable.cellClicked.connect(self.createEnrichSearchDialog)
        self.chooseLayerInterlink.clear()
        self.searchClass.clicked.connect(self.createInterlinkSearchDialog)
        urlregex = QRegExp(
            "http[s]?://(?:[a-zA-Z#]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
        )
        urlvalidator = QRegExpValidator(urlregex, self)
        self.interlinkNameSpace.setValidator(urlvalidator)
        self.interlinkNameSpace.textChanged.connect(self.check_state3)
        self.interlinkNameSpace.textChanged.emit(
            self.interlinkNameSpace.text())
        self.addEnrichedLayerButton.clicked.connect(
            self.enrichtab.addEnrichedLayer)
        self.startEnrichment.clicked.connect(self.enrichtab.enrichLayerProcess)
        self.exportInterlink.clicked.connect(
            self.enrichtab.exportEnrichedLayer)
        self.loadQuery.clicked.connect(self.loadQueryFunc)
        self.saveQueryButton.clicked.connect(self.saveQueryFunc)
        self.exportMappingButton.clicked.connect(
            self.interlinktab.exportMapping)
        self.importMappingButton.clicked.connect(self.interlinktab.loadMapping)
        self.loadLayerInterlink.clicked.connect(self.loadLayerForInterlink)
        self.loadLayerEnrich.clicked.connect(self.loadLayerForEnrichment)
        self.addEnrichedLayerRowButton.clicked.connect(self.addEnrichRow)
        self.geoClassList.selectionModel().selectionChanged.connect(
            self.viewselectaction)
        self.loadFileButton.clicked.connect(self.buildLoadGraphDialog)
        self.refreshLayersInterlink.clicked.connect(self.loadUnicornLayers)
        self.btn_loadunicornlayers.clicked.connect(self.loadUnicornLayers)
        self.whattoenrich.clicked.connect(self.createWhatToEnrich)
        self.quickAddTripleStore.clicked.connect(self.buildQuickAddTripleStore)
        self.loadTripleStoreButton.clicked.connect(
            self.buildCustomTripleStoreDialog)
        self.loadUnicornLayers()

    def loadQueryFunc(self):
        if self.triplestoreconf[self.comboBox.currentIndex(
        )]["endpoint"] in self.savedQueriesJSON:
            self.inp_sparql2.setPlainText(self.savedQueriesJSON[
                self.triplestoreconf[self.comboBox.currentIndex()]
                ["endpoint"]][self.savedQueries.currentIndex()]["query"])

    def saveQueryFunc(self):
        queryName = self.saveQueryName.text()
        if queryName != None and queryName != "":
            __location__ = os.path.realpath(
                os.path.join(os.getcwd(), os.path.dirname(__file__)))
            self.savedQueriesJSON[self.triplestoreconf[
                self.comboBox.currentIndex()]["endpoint"]].append({
                    "label":
                    queryName,
                    "query":
                    self.inp_sparql2.toPlainText()
                })
            self.savedQueries.addItem(queryName)
            f = open(os.path.join(__location__, 'savedqueries.json'), "w")
            f.write(json.dumps(self.savedQueriesJSON))
            f.close()

    def onContext(self):
        menu = QMenu("Menu", self.geoClassList)
        action = QAction("Open in Webbrowser")
        menu.addAction(action)
        action.triggered.connect(self.openURL)

    def openURL(self):
        curindex = self.proxyModel.mapToSource(
            self.geoClassList.selectionModel().currentIndex())
        concept = self.geoClassListModel.itemFromIndex(curindex).data(1)
        url = QUrl(concept)
        QDesktopServices.openUrl(url)

    def setFilterFromText(self):
        self.proxyModel.setFilterRegExp(self.filterConcepts.text())

    ##
    #  @brief Creates a What To Enrich dialog with parameters given.
    #
    #  @param self The object pointer
    def buildLoadGraphDialog(self):
        self.searchTripleStoreDialog = LoadGraphDialog(self.triplestoreconf,
                                                       self.maindlg, self)
        self.searchTripleStoreDialog.setWindowTitle("Load Graph")
        self.searchTripleStoreDialog.exec_()

    ##
    #  @brief Creates a What To Enrich dialog with parameters given.
    #
    #  @param self The object pointer
    def buildQuickAddTripleStore(self):
        self.searchTripleStoreDialog = TripleStoreQuickAddDialog(
            self.triplestoreconf, self.prefixes, self.prefixstore,
            self.comboBox)
        self.searchTripleStoreDialog.setMinimumSize(580, 186)
        self.searchTripleStoreDialog.setWindowTitle(
            "Configure Own Triple Store")
        self.searchTripleStoreDialog.exec_()

    ##
    #  @brief Creates a What To Enrich dialog with parameters given.
    #
    #  @param self The object pointer
    def buildCustomTripleStoreDialog(self):
        self.searchTripleStoreDialog = TripleStoreDialog(
            self.triplestoreconf, self.prefixes, self.prefixstore,
            self.comboBox)
        self.searchTripleStoreDialog.setMinimumSize(700, 500)
        self.searchTripleStoreDialog.setWindowTitle(
            "Configure Own Triple Store")
        self.searchTripleStoreDialog.exec_()

    ##
    #  @brief Creates a What To Enrich dialog with parameters given.
    #
    #  @param self The object pointer
    def createWhatToEnrich(self):
        if self.enrichTable.rowCount() == 0:
            return
        layers = QgsProject.instance().layerTreeRoot().children()
        selectedLayerIndex = self.chooseLayerEnrich.currentIndex()
        layer = layers[selectedLayerIndex].layer()
        self.searchTripleStoreDialog = EnrichmentDialog(
            self.triplestoreconf, self.prefixes, self.enrichTable, layer, None,
            None)
        self.searchTripleStoreDialog.setMinimumSize(700, 500)
        self.searchTripleStoreDialog.setWindowTitle("Enrichment Search")
        self.searchTripleStoreDialog.exec_()

    def check_state3(self):
        self.searchTripleStoreDialog.check_state(self.interlinkNameSpace)

    def createEnrichSearchDialog(self, row=-1, column=-1):
        if column == 1:
            self.buildSearchDialog(row, column, False, self.enrichTable, False,
                                   False, None, self.addVocabConf)
        if column == 6:
            self.buildSearchDialog(row, column, False, self.enrichTable, False,
                                   False, None, self.addVocabConf)

    def createEnrichSearchDialogProp(self, row=-1, column=-1):
        self.buildSearchDialog(row, column, False, self.findIDPropertyEdit,
                               True, False, None, self.addVocabConf)

    ##
    #  @brief Creates a search dialog with parameters for interlinking.
    #
    #  @param self The object pointer
    #  @param row The row of the table for which to map the search result
    #  @param column The column of the table for which to map the search result
    def createInterlinkSearchDialog(self, row=-1, column=-1):
        if column > 3 and column < 7:
            self.buildSearchDialog(row, column, True, self.interlinkTable,
                                   True, False, None, self.addVocabConf)
        elif column >= 7:
            layers = QgsProject.instance().layerTreeRoot().children()
            selectedLayerIndex = self.chooseLayerInterlink.currentIndex()
            layer = layers[selectedLayerIndex].layer()
            self.buildValueMappingDialog(row, column, True,
                                         self.interlinkTable, layer)
        elif column == -1:
            self.buildSearchDialog(row, column, -1,
                                   self.interlinkOwlClassInput, False, False,
                                   None, self.addVocabConf)

    ##
    #  @brief Shows the configuration table after creating an enrichment result.
    #
    #  @param  self The object pointer
    #
    def showConfigTable(self):
        self.enrichTableResult.hide()
        self.enrichTable.show()
        self.startEnrichment.setText("Start Enrichment")
        self.startEnrichment.clicked.disconnect()
        self.startEnrichment.clicked.connect(self.enrichtab.enrichLayerProcess)

    ##
    #  @brief Executes a GUI event when a new SPARQL endpoint is selected.
    #  Usually loads the list of concepts related to the SPARQL endpoint
    #  @param  send The sender of the request
    #
    def viewselectaction(self):
        endpointIndex = self.comboBox.currentIndex()
        if endpointIndex == 0:
            self.justloadingfromfile = False
            return
        concept = ""
        curindex = self.proxyModel.mapToSource(
            self.geoClassList.selectionModel().currentIndex())
        if self.geoClassList.selectionModel().currentIndex(
        ) != None and self.geoClassListModel.itemFromIndex(
                curindex) != None and re.match(
                    r'.*Q[0-9]+.*',
                    self.geoClassListModel.itemFromIndex(curindex).text()
                ) and not self.geoClassListModel.itemFromIndex(
                    curindex).text().startswith("http"):
            self.inp_label.setText(
                self.geoClassListModel.itemFromIndex(curindex).text().split(
                    "(")[0].lower().replace(" ", "_"))
            concept = "Q" + self.geoClassListModel.itemFromIndex(
                curindex).text().split("Q")[1].replace(")", "")
        elif self.geoClassListModel.itemFromIndex(curindex) != None:
            concept = self.geoClassListModel.itemFromIndex(curindex).data(1)
        if "querytemplate" in self.triplestoreconf[endpointIndex]:
            if "wd:Q%%concept%% ." in self.triplestoreconf[endpointIndex][
                    "querytemplate"][
                        self.queryTemplates.currentIndex()]["query"]:
                querytext = ""
                if concept != None and concept.startswith("http"):
                    querytext = self.triplestoreconf[endpointIndex][
                        "querytemplate"][self.queryTemplates.currentIndex(
                        )]["query"].replace(
                            "wd:Q%%concept%% .",
                            "wd:" + concept[concept.rfind('/') + 1:] + " .")
                elif concept != None:
                    querytext = self.triplestoreconf[endpointIndex][
                        "querytemplate"][self.queryTemplates.currentIndex(
                        )]["query"].replace("wd:Q%%concept%% .",
                                            "wd:" + concept + " .")
            else:
                querytext = self.triplestoreconf[endpointIndex][
                    "querytemplate"][
                        self.queryTemplates.currentIndex()]["query"].replace(
                            "%%concept%%", concept)
            if self.queryLimit.text().isnumeric(
            ) and querytext.rfind("LIMIT") != -1:
                querytext = querytext[0:querytext.rfind(
                    "LIMIT")] + "LIMIT " + self.queryLimit.text()
            elif self.queryLimit.text().isnumeric() and querytext.rfind(
                    "LIMIT") == -1:
                querytext = querytext + " LIMIT " + self.queryLimit.text()
            self.inp_sparql2.setPlainText(querytext)
            self.inp_sparql2.columnvars = {}
        if self.geoClassList.selectionModel().currentIndex(
        ) != None and self.geoClassListModel.itemFromIndex(
                curindex
        ) != None and "#" in self.geoClassListModel.itemFromIndex(
                curindex).text():
            self.inp_label.setText(
                self.geoClassListModel.itemFromIndex(curindex).text()
                [self.geoClassListModel.itemFromIndex(curindex).text().
                 rfind('#') + 1:].lower().replace(" ", "_"))
        elif self.geoClassList.selectionModel().currentIndex(
        ) != None and self.geoClassListModel.itemFromIndex(curindex) != None:
            self.inp_label.setText(
                self.geoClassListModel.itemFromIndex(curindex).text()
                [self.geoClassListModel.itemFromIndex(curindex).text().
                 rfind('/') + 1:].lower().replace(" ", "_"))

    def itemModelToMap(self, model):
        resdict = {}
        for row in range(model.rowCount()):
            index = model.index(row, 0, self)
            resdict[model.itemFromIndex(index).text()] = model.itemFromIndex(
                index).data(1)
        return resdict

    ##
    #  @brief Deletes a row from the table in the enrichment dialog.
    #
    #  @param  send The sender of the request
    #
    def deleteEnrichRow(send):
        w = send.sender().parent()
        row = self.enrichTable.indexAt(w.pos()).row()
        self.enrichTable.removeRow(row)
        self.enrichTable.setCurrentCell(0, 0)

    ##
    #  @brief Adds a new row to the table in the enrichment dialog.
    #
    #  @param  self The object pointer
    #
    def addEnrichRow(self):
        layers = QgsProject.instance().layerTreeRoot().children()
        selectedLayerIndex = self.chooseLayerEnrich.currentIndex()
        layer = layers[selectedLayerIndex].layer()
        self.enrichTableResult.hide()
        fieldnames = [field.name() for field in layer.fields()]
        item = QTableWidgetItem("new_column")
        #item.setFlags(QtCore.Qt.ItemIsEnabled)
        row = self.enrichTable.rowCount()
        self.enrichTable.insertRow(row)
        self.enrichTable.setItem(row, 0, item)
        cbox = QComboBox()
        cbox.addItem("Get Remote")
        cbox.addItem("No Enrichment")
        cbox.addItem("Exclude")
        self.enrichTable.setCellWidget(row, 3, cbox)
        cbox = QComboBox()
        cbox.addItem("Enrich Value")
        cbox.addItem("Enrich URI")
        cbox.addItem("Enrich Both")
        self.enrichTable.setCellWidget(row, 4, cbox)
        cbox = QComboBox()
        for fieldd in fieldnames:
            cbox.addItem(fieldd)
        self.enrichTable.setCellWidget(row, 5, cbox)
        itemm = QTableWidgetItem("http://www.w3.org/2000/01/rdf-schema#label")
        self.enrichTable.setItem(row, 6, itemm)
        itemm = QTableWidgetItem("")
        self.enrichTable.setItem(row, 7, itemm)
        itemm = QTableWidgetItem("")
        self.enrichTable.setItem(row, 8, itemm)

    ## Validates the SPARQL query in the input field and outputs errors in a label.
    #  @param self The object pointer.
    def validateSPARQL(self):
        if self.prefixes != None and self.comboBox != None and self.comboBox.currentIndex(
        ) != None and self.prefixes[self.comboBox.currentIndex(
        )] != None and self.inp_sparql2.toPlainText(
        ) != None and self.inp_sparql2.toPlainText() != "":
            try:
                if self.prefixes[self.comboBox.currentIndex()] != "":
                    prepareQuery(
                        "".join(self.prefixes[self.comboBox.currentIndex()]) +
                        "\n" + self.inp_sparql2.toPlainText())
                self.errorLabel.setText("Valid Query")
                self.errorline = -1
                self.sparqlhighlight.errorhighlightline = self.errorline
                self.sparqlhighlight.currentline = 0
                self.inp_sparql2.errorline = None
            except Exception as e:
                match = re.search(r'line:([0-9]+),', str(e))
                match2 = re.search(r'col:([0-9]+),', str(e))
                start = int(match.group(1)) - len(self.triplestoreconf[
                    self.comboBox.currentIndex()]["prefixes"]) - 1
                self.errorLabel.setText(
                    re.sub("line:([0-9]+),", "line: " + str(start) + ",",
                           str(e)))
                self.inp_sparql2.errorline = start - 1
                if "line" in str(e):
                    ex = str(e)
                    start = ex.find('line:') + 5
                    end = ex.find(',', start)
                    start2 = ex.find('col:') + 4
                    end2 = ex.find(')', start2)
                    self.errorline = ex[start:end]
                    self.sparqlhighlight.errorhighlightcol = ex[start2:end2]
                    self.sparqlhighlight.errorhighlightline = self.errorline
                    self.sparqlhighlight.currentline = 0

    ##
    #  @brief Builds the search dialog to search for a concept or class.
    #  @param  self The object pointer
    #  @param  row the row to insert the result
    #  @param  column the column to insert the result
    #  @param  interlinkOrEnrich indicates if the dialog is meant for interlinking or enrichment
    #  @param  table the GUI element to display the result
    def buildSearchDialog(self,
                          row,
                          column,
                          interlinkOrEnrich,
                          table,
                          propOrClass,
                          bothOptions=False,
                          currentprefixes=None,
                          addVocabConf=None):
        self.currentcol = column
        self.currentrow = row
        self.interlinkdialog = SearchDialog(column, row, self.triplestoreconf,
                                            self.prefixes, interlinkOrEnrich,
                                            table, propOrClass, bothOptions,
                                            currentprefixes, addVocabConf)
        self.interlinkdialog.setMinimumSize(650, 400)
        self.interlinkdialog.setWindowTitle("Search Interlink Concept")
        self.interlinkdialog.exec_()

    ##
    #  @brief Builds a boundingbox dialog allows to pick a bounding box for a SPARQL query.
    #
    #  @param self The object pointer
    def getPointFromCanvas(self):
        self.d = BBOXDialog(self.inp_sparql2, self.triplestoreconf,
                            self.comboBox.currentIndex())
        self.d.setWindowTitle("Choose BoundingBox")
        self.d.exec_()

    ##
    #  @brief Builds a value mapping dialog window for ther interlinking dialog.
    #
    #  @param self The object pointer
    #  @param row The row of the table for which to map the value
    #  @param column The column of the table for which to map the value
    #  @param table The table in which to save the value mapping result
    #  @param layer The layer which is concerned by the enrichment oder interlinking
    def buildValueMappingDialog(self, row, column, interlinkOrEnrich, table,
                                layer):
        self.currentcol = column
        self.currentrow = row
        valuemap = None
        if table.item(row, column) != None and table.item(row,
                                                          column).text() != "":
            valuemap = table.item(row, column).data(1)
        self.interlinkdialog = ValueMappingDialog(column, row,
                                                  self.triplestoreconf,
                                                  interlinkOrEnrich, table,
                                                  table.item(row, 3).text(),
                                                  layer, valuemap)
        self.interlinkdialog.setMinimumSize(650, 400)
        self.interlinkdialog.setWindowTitle("Get Value Mappings for column " +
                                            table.item(row, 3).text())
        self.interlinkdialog.exec_()

    ##
    #  @brief Loads a QGIS layer for interlinking into the interlinking dialog.
    #
    #  @param self The object pointer
    def loadLayerForInterlink(self):
        layers = QgsProject.instance().layerTreeRoot().children()
        selectedLayerIndex = self.chooseLayerInterlink.currentIndex()
        if len(layers) == 0:
            return
        layer = layers[selectedLayerIndex].layer()
        fieldnames = [field.name() for field in layer.fields()]
        while self.interlinkTable.rowCount() > 0:
            self.interlinkTable.removeRow(0)
        row = 0
        self.interlinkTable.setHorizontalHeaderLabels([
            "Export?", "IDColumn?", "GeoColumn?", "Column", "ColumnProperty",
            "PropertyType", "ColumnConcept", "ValueConcepts"
        ])
        self.interlinkTable.setColumnCount(8)
        for field in fieldnames:
            item = QTableWidgetItem(field)
            item.setFlags(QtCore.Qt.ItemIsEnabled)
            item2 = QTableWidgetItem()
            item2.setCheckState(True)
            item3 = QTableWidgetItem()
            item3.setCheckState(False)
            item4 = QTableWidgetItem()
            item4.setCheckState(False)
            self.interlinkTable.insertRow(row)
            self.interlinkTable.setItem(row, 3, item)
            self.interlinkTable.setItem(row, 0, item2)
            self.interlinkTable.setItem(row, 1, item3)
            self.interlinkTable.setItem(row, 2, item4)
            cbox = QComboBox()
            cbox.addItem("Automatic")
            cbox.addItem("AnnotationProperty")
            cbox.addItem("DataProperty")
            cbox.addItem("ObjectProperty")
            cbox.addItem("SubClass")
            self.interlinkTable.setCellWidget(row, 5, cbox)
            currentRowCount = self.interlinkTable.rowCount()
            row += 1

    ##
    #  @brief Loads a QGIS layer for enrichment into the enrichment dialog.
    #
    #  @param self The object pointer
    def loadLayerForEnrichment(self):
        layers = QgsProject.instance().layerTreeRoot().children()
        selectedLayerIndex = self.chooseLayerEnrich.currentIndex()
        if len(layers) == 0:
            return
        layer = layers[selectedLayerIndex].layer()
        self.enrichTableResult.hide()
        while self.enrichTableResult.rowCount() > 0:
            self.enrichTableResult.removeRow(0)
        self.enrichTable.show()
        self.addEnrichedLayerRowButton.setEnabled(True)
        fieldnames = [field.name() for field in layer.fields()]
        while self.enrichTable.rowCount() > 0:
            self.enrichTable.removeRow(0)
        row = 0
        self.enrichTable.setColumnCount(9)
        self.enrichTable.setHorizontalHeaderLabels([
            "Column", "EnrichmentConcept", "TripleStore", "Strategy",
            "content", "ID Column", "ID Property", "ID Domain", "Language"
        ])
        for field in fieldnames:
            item = QTableWidgetItem(field)
            item.setFlags(QtCore.Qt.ItemIsEnabled)
            currentRowCount = self.enrichTable.rowCount()
            self.enrichTable.insertRow(row)
            self.enrichTable.setItem(row, 0, item)
            cbox = QComboBox()
            cbox.addItem("No Enrichment")
            cbox.addItem("Keep Local")
            cbox.addItem("Keep Remote")
            cbox.addItem("Replace Local")
            cbox.addItem("Merge")
            cbox.addItem("Ask User")
            cbox.addItem("Exclude")
            self.enrichTable.setCellWidget(row, 3, cbox)
            cbox = QComboBox()
            cbox.addItem("Enrich Value")
            cbox.addItem("Enrich URI")
            cbox.addItem("Enrich Both")
            self.enrichTable.setCellWidget(row, 4, cbox)
            cbox = QComboBox()
            for fieldd in fieldnames:
                cbox.addItem(fieldd)
            self.enrichTable.setCellWidget(row, 5, cbox)
            itemm = QTableWidgetItem(
                "http://www.w3.org/2000/01/rdf-schema#label")
            self.enrichTable.setItem(row, 6, itemm)
            itemm = QTableWidgetItem("")
            self.enrichTable.setItem(row, 7, itemm)
            itemm = QTableWidgetItem("")
            self.enrichTable.setItem(row, 8, itemm)
            celllayout = QHBoxLayout()
            upbutton = QPushButton("Up")
            removebutton = QPushButton("Remove", self)
            removebutton.clicked.connect(self.deleteEnrichRow)
            downbutton = QPushButton("Down")
            celllayout.addWidget(upbutton)
            celllayout.addWidget(downbutton)
            celllayout.addWidget(removebutton)
            w = QWidget()
            w.setLayout(celllayout)
            optitem = QTableWidgetItem()
            #self.enrichTable.setCellWidget(row,4,w)
            #self.enrichTable.setItem(row,3,cbox)
            row += 1
        self.originalRowCount = row

    ## Fetch the currently loaded layers.
    #  @param self The object pointer.
    def loadUnicornLayers(self):
        layers = QgsProject.instance().layerTreeRoot().children()
        # Populate the comboBox with names of all the loaded unicorn layers
        self.loadedLayers.clear()
        self.chooseLayerInterlink.clear()
        self.chooseLayerEnrich.clear()
        for layer in layers:
            ucl = layer.name()
            #if type(layer) == QgsMapLayer.VectorLayer:
            self.loadedLayers.addItem(layer.name())
            self.chooseLayerInterlink.addItem(layer.name())
            self.chooseLayerEnrich.addItem(layer.name())
Exemplo n.º 8
0
class Dialog(QDialog, Ui_attr2BUGS):
    def __init__(self, iface, ml):
        """Constructor for the dialog.
        
        Args:
          iface: QgsInterface instance.
        """

        QDialog.__init__(self, iface.mainWindow())

        self.setupUi(self)
        self.setWindowTitle('Attributes to BUGS')

        self.ml = ml

        self.ids = []
        self.polynum = self.ml.featureCount()

        self.model = QStandardItemModel(0, 1)
        self.listView.setModel(self.model)
        self.model.setHeaderData(0, Qt.Horizontal, 'Field')

        provider = self.ml.dataProvider()
        fields = provider.fields()
        for f in fields:
            item = QStandardItem(f.name())
            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
            item.setData(Qt.Unchecked, Qt.CheckStateRole)
            self.model.appendRow(item)

        self.control()

        self.pushButton.clicked.connect(self.konv)
        self.pushButton_2.clicked.connect(self.close)
        self.pushButton_3.clicked.connect(self.save)

    def konv(self):
        QApplication.setOverrideCursor(Qt.WaitCursor)

        self.plainTextEdit.clear()

        mod = min(self.ids)

        provider = self.ml.dataProvider()
        fields = provider.fields()
        lst = '#data\nlist('

        for row in range(0, self.model.rowCount()):
            it = self.model.itemFromIndex(self.model.index(row, 0))
            if it.checkState() == 2:
                fld = str(self.model.itemData(self.model.index(row, 0))[0])
                var = fld + '=c('
                ft = fields[row].type()
                if ft == 10:
                    for ne in range(mod, self.polynum + mod):
                        feat = QgsFeature()
                        fiter = self.ml.getFeatures(QgsFeatureRequest(ne))
                        if fiter.nextFeature(feat):
                            u = feat.attribute(fld)
                            var += "'%s'," % u
                    var = var[:-2] + "')"
                else:
                    for ne in range(mod, self.polynum + mod):
                        feat = QgsFeature()
                        fiter = self.ml.getFeatures(QgsFeatureRequest(ne))
                        if fiter.nextFeature(feat):
                            u = feat.attribute(fld)
                            var += "%s," % u
                    var = var[:-1] + ')'
                lst += var + ', '
        lst += ')'
        lst = lst.replace('), )', '))')
        self.plainTextEdit.appendPlainText(lst)

        QApplication.restoreOverrideCursor()

    def control(self):
        feat = QgsFeature()
        provider = self.ml.dataProvider()
        feats = provider.getFeatures()
        #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0)
        #self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, self.polynum))
        ne = 0
        while feats.nextFeature(feat):
            ne += 1
            #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne)
            self.ids.append(feat.id())

    def save(self):
        fileName, _ = QFileDialog.getSaveFileName(self, caption='Save As...')
        try:
            file = QFile(fileName + '.txt')
            file.open(QIODevice.WriteOnly | QIODevice.Text)
            out = QTextStream(file)
            out << self.plainTextEdit.toPlainText()
            out.flush()
            file.close()
            self.close()
            return True
        except IOError:
            return False
Exemplo n.º 9
0
class ResourceSharingDialog(QDialog, FORM_CLASS):
    TAB_ALL = 0
    TAB_INSTALLED = 1
    TAB_SETTINGS = 2

    def __init__(self, parent=None, iface=None):
        """Constructor.

        :param parent: Optional widget to use as parent
        :type parent: QWidget

        :param iface: An instance of QGisInterface
        :type iface: QGisInterface
        """
        super(ResourceSharingDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        # Reconfigure UI
        self.setModal(True)
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)
        self.button_install.setEnabled(False)
        self.button_open.setEnabled(False)
        self.button_uninstall.setEnabled(False)
        # Set up the "main menu" - QListWidgetItem
        # All collections
        icon_all = QIcon()
        icon_all.addFile(str(resources_path("img", "plugin.svg")), QSize(),
                         QIcon.Normal, QIcon.Off)
        item_all = QListWidgetItem()
        item_all.setIcon(icon_all)
        item_all.setText(self.tr("All collections"))
        # Installed collections
        icon_installed = QIcon()
        icon_installed.addFile(
            str(resources_path("img", "plugin-installed.svg")),
            QSize(),
            QIcon.Normal,
            QIcon.Off,
        )
        item_installed = QListWidgetItem()
        item_installed.setIcon(icon_installed)
        item_installed.setText(self.tr("Installed collections"))
        item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        # Settings / repositories
        icon_settings = QIcon()
        icon_settings.addFile(str(resources_path("img", "settings.svg")),
                              QSize(), QIcon.Normal, QIcon.Off)
        item_settings = QListWidgetItem()
        item_settings.setIcon(icon_settings)
        item_settings.setText(self.tr("Settings"))
        item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        # Add the items to the list widget
        self.menu_list_widget.addItem(item_all)
        self.menu_list_widget.addItem(item_installed)
        self.menu_list_widget.addItem(item_settings)
        # Init the message bar
        self.message_bar = QgsMessageBar(self)
        self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.vlayoutRightColumn.insertWidget(0, self.message_bar)
        # Progress dialog for long running processes
        self.progress_dialog = None
        # Init the repository manager dialog
        self.repository_manager = RepositoryManager()
        self.collection_manager = CollectionManager()
        # Collections list view
        self.collections_model = QStandardItemModel(0, 1)
        self.collections_model.sort(0, Qt.AscendingOrder)
        self.collection_proxy = CustomSortFilterProxyModel(self)
        self.collection_proxy.setSourceModel(self.collections_model)
        self.list_view_collections.setModel(self.collection_proxy)
        # Active selected collection
        self._sel_coll_id = None
        # Slots
        self.button_add.clicked.connect(self.add_repository)
        self.button_edit.clicked.connect(self.edit_repository)
        self.button_delete.clicked.connect(self.delete_repository)
        self.button_reload.clicked.connect(self.reload_repositories)
        self.button_reload_dir.clicked.connect(self.reload_off_res_directory)
        self.menu_list_widget.currentRowChanged.connect(self.set_current_tab)
        self.list_view_collections.selectionModel().currentChanged.connect(
            self.on_list_view_collections_clicked)
        self.line_edit_filter.textChanged.connect(self.filter_collections)
        self.button_install.clicked.connect(self.install_collection)
        self.button_open.clicked.connect(self.open_collection)
        self.button_uninstall.clicked.connect(self.uninstall_collection)
        self.button_box.button(QDialogButtonBox.Help).clicked.connect(
            self.open_help)
        # Populate the repositories widget and collections list view
        self.populate_repositories_widget()
        self.reload_collections_model()

    def set_current_tab(self, index):
        """Set stacked widget based on the active tab.

        :param index: The index of the active widget (in the list widget).
        :type index: int
        """
        # Clear message bar
        self.message_bar.clearWidgets()
        if index == (self.menu_list_widget.count() - 1):
            # Last menu entry - Settings
            self.stacked_menu_widget.setCurrentIndex(1)
        else:
            # Not settings, must be Collections (all or installed)
            if index == 1:
                # Installed collections
                self.collection_proxy.accepted_status = COLLECTION_INSTALLED_STATUS
                # Set the web view
                title = self.tr("Installed Collections")
                description = self.tr(
                    "On the left you see the list of all the "
                    "installed collections.")
            else:
                # All collections (0)
                self.collection_proxy.accepted_status = COLLECTION_ALL_STATUS
                # Set the web view
                title = self.tr("All Collections")
                description = self.tr(
                    "On the left you see a list of all the collections "
                    "that are available from the registered repositories.<br> "
                    "Installed collections are emphasized (in <b>bold</b>).")

            context = {
                "resources_path": str(resources_path()),
                "title": title,
                "description": description,
            }
            self.web_view_details.setHtml(
                render_template("tab_description.html", context))
            self.stacked_menu_widget.setCurrentIndex(0)

    def add_repository(self):
        """Open add repository dialog."""
        dlg = ManageRepositoryDialog(self)
        if not dlg.exec_():
            return
        for repoName, repo in self.repository_manager.directories.items():
            if dlg.line_edit_url.text().strip() == repo["url"]:
                self.message_bar.pushMessage(
                    self.tr(
                        "Unable to add another repository with the same URL!"),
                    Qgis.Warning,
                    5,
                )
                return
            if dlg.line_edit_name.text().strip() == repoName:
                self.message_bar.pushMessage(
                    self.tr("Repositories must have unique names!"),
                    Qgis.Warning, 5)
                return
        repo_name = dlg.line_edit_name.text()
        repo_url = dlg.line_edit_url.text().strip()
        repo_auth_cfg = dlg.line_edit_auth_id.text().strip()
        if repo_name in self.repository_manager.directories:
            repo_name += "(2)"
        # Show progress dialog
        self.show_progress_dialog("Fetching repository's metadata")
        # Add repository
        try:
            status, adderror = self.repository_manager.add_directory(
                repo_name, repo_url, repo_auth_cfg)
            if status:
                self.message_bar.pushMessage(
                    self.tr("Repository was successfully added"), Qgis.Success,
                    5)
            else:
                self.message_bar.pushMessage(
                    self.tr("Unable to add repository: %s") % adderror,
                    Qgis.Warning, 5)
        except Exception as e:
            self.message_bar.pushMessage(self.tr("%s") % e, Qgis.Warning, 5)
        finally:
            self.progress_dialog.hide()
        # Reload data and widget
        self.reload_data_and_widget()
        # Deactivate edit and delete button
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)

    def edit_repository(self):
        """Open edit repository dialog."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item:
            repo_name = selected_item.text(0)
        if not repo_name:
            return
        # Check if it is among the officially approved QGIS repositories
        settings = QgsSettings()
        settings.beginGroup(repo_settings_group())
        if (settings.value(repo_name + "/url")
                in self.repository_manager._online_directories.values()):
            self.message_bar.pushMessage(
                self.tr("You can not edit the official repositories!"),
                Qgis.Warning, 5)
            return
        dlg = ManageRepositoryDialog(self)
        dlg.line_edit_name.setText(repo_name)
        dlg.line_edit_url.setText(
            self.repository_manager.directories[repo_name]["url"])
        dlg.line_edit_auth_id.setText(
            self.repository_manager.directories[repo_name]["auth_cfg"])
        if not dlg.exec_():
            return
        # Check if the changed URL is already present and that
        # the new repository name is unique
        new_url = dlg.line_edit_url.text().strip()
        old_url = self.repository_manager.directories[repo_name]["url"]
        new_name = dlg.line_edit_name.text().strip()
        for repoName, repo in self.repository_manager.directories.items():
            if new_url == repo["url"] and (old_url != new_url):
                self.message_bar.pushMessage(
                    self.tr("Unable to add another repository with the same "
                            "URL!"),
                    Qgis.Warning,
                    5,
                )
                return
            if new_name == repoName and (repo_name != new_name):
                self.message_bar.pushMessage(
                    self.tr("Repositories must have unique names!"),
                    Qgis.Warning, 5)
                return
        # Redundant
        if (new_name in self.repository_manager.directories) and (new_name !=
                                                                  repo_name):
            new_name += "(2)"
        new_auth_cfg = dlg.line_edit_auth_id.text()
        # Show progress dialog
        self.show_progress_dialog("Fetching repository's metadata")
        # Edit repository
        try:
            status, editerror = self.repository_manager.edit_directory(
                repo_name, new_name, old_url, new_url, new_auth_cfg)
            if status:
                self.message_bar.pushMessage(
                    self.tr("Repository is successfully updated"),
                    Qgis.Success, 5)
            else:
                self.message_bar.pushMessage(
                    self.tr("Unable to edit repository: %s") % editerror,
                    Qgis.Warning,
                    5,
                )
        except Exception as e:
            self.message_bar.pushMessage(self.tr("%s") % e, Qgis.Warning, 5)
        finally:
            self.progress_dialog.hide()
        # Reload data and widget
        self.reload_data_and_widget()
        # Deactivate the edit and delete buttons
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)

    def delete_repository(self):
        """Delete a repository in the tree widget."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item:
            repo_name = selected_item.text(0)
        if not repo_name:
            return
        # Check if it is among the offical repositories
        repo_url = self.repository_manager.directories[repo_name]["url"]
        if repo_url in self.repository_manager._online_directories.values():
            self.message_bar.pushMessage(
                self.tr("You can not remove official repositories!"),
                Qgis.Warning, 5)
            return
        warning = (self.tr("Are you sure you want to remove the following "
                           "repository?") + "\n" + repo_name)
        if (QMessageBox.warning(
                self,
                self.tr("QGIS Resource Sharing"),
                warning,
                QMessageBox.Yes,
                QMessageBox.No,
        ) == QMessageBox.No):
            return

        # Remove repository
        installed_collections = self.collection_manager.get_installed_collections(
            repo_url)
        if installed_collections:
            message = ("You have installed collections from this "
                       "repository. Please uninstall them first!")
            self.message_bar.pushMessage(message, Qgis.Warning, 5)
        else:
            self.repository_manager.remove_directory(repo_name)
            # Reload data and widget
            self.reload_data_and_widget()
            # Deactivate the edit and delete buttons
            self.button_edit.setEnabled(False)
            self.button_delete.setEnabled(False)

    def reload_off_res_directory(self):
        """Slot called when the user clicks the 'Reload directory'
        button."""
        # Show progress dialog
        self.show_progress_dialog("Reloading the official QGIS resource"
                                  " directory")
        self.repository_manager._online_directories = {}
        # Registered directories
        self.repository_manager._directories = {}
        self.repository_manager.fetch_online_directories()
        # Load directory of repositories from settings
        self.repository_manager.load_directories()
        self.message_bar.pushMessage("On-line directory reloaded", Qgis.Info,
                                     5)
        self.progress_dialog.hide()
        # Reload data and widget
        self.reload_data_and_widget()

    def reload_repositories(self):
        """Slot called when the user clicks the 'Reload repositories'
        button."""
        # Show progress dialog
        self.show_progress_dialog("Reloading all repositories")
        for repo_name in self.repository_manager.directories:
            directory = self.repository_manager.directories[repo_name]
            url = directory["url"]
            auth_cfg = directory["auth_cfg"]
            try:
                status, reloaderror = self.repository_manager.reload_directory(
                    repo_name, url, auth_cfg)
                if status:
                    self.message_bar.pushMessage(
                        self.tr("Repository %s is successfully reloaded") %
                        repo_name,
                        Qgis.Info,
                        5,
                    )
                else:
                    self.message_bar.pushMessage(
                        self.tr("Unable to reload %s: %s") %
                        (repo_name, reloaderror),
                        Qgis.Warning,
                        5,
                    )
            except Exception as e:
                self.message_bar.pushMessage(
                    self.tr("%s") % e, Qgis.Warning, 5)
        self.progress_dialog.hide()
        # Reload data and widget
        self.reload_data_and_widget()

    def install_collection(self):
        """Slot called when the user clicks the Install/Reinstall button."""
        # Save the current index to enable selection after installation
        self.current_index = self.list_view_collections.currentIndex()
        self.show_progress_dialog("Starting installation...")
        self.progress_dialog.canceled.connect(self.install_canceled)
        self.installer_thread = QThread()
        self.installer_worker = CollectionInstaller(self.collection_manager,
                                                    self._sel_coll_id)
        self.installer_worker.moveToThread(self.installer_thread)
        self.installer_worker.finished.connect(self.install_finished)
        self.installer_worker.aborted.connect(self.install_aborted)
        self.installer_worker.progress.connect(self.install_progress)
        self.installer_thread.started.connect(self.installer_worker.run)
        self.installer_thread.start()

    def install_finished(self):
        # Process the result
        self.progress_dialog.hide()
        installStatus = self.installer_worker.install_status
        if not installStatus:
            message = self.installer_worker.error_message
        # Clean up the worker and thread
        self.installer_worker.deleteLater()
        self.installer_thread.quit()
        self.installer_thread.wait()
        self.installer_thread.deleteLater()
        if installStatus:
            self.reload_collections_model()
            # Report what has been installed
            message = "<b>%s</b> was successfully installed, " "containing:\n<ul>" % (
                config.COLLECTIONS[self._sel_coll_id]["name"])
            number = 0
            for type_, description in SUPPORTED_RESOURCES_MAP.items():
                if type_ in config.COLLECTIONS[self._sel_coll_id].keys():
                    number = config.COLLECTIONS[self._sel_coll_id][type_]
                    message += (f"\n<li>{number} {description}"
                                f'{"s" if number > 1 else ""}'
                                f"</li>")
            message += "\n</ul>"
        QMessageBox.information(self, "Resource Sharing", message)
        self.populate_repositories_widget()
        # Set the selection
        oldRow = self.current_index.row()
        newIndex = self.collections_model.createIndex(oldRow, 0)
        selection_model = self.list_view_collections.selectionModel()
        selection_model.setCurrentIndex(newIndex,
                                        selection_model.ClearAndSelect)
        selection_model.select(newIndex, selection_model.ClearAndSelect)
        # Update the buttons
        self.button_install.setEnabled(True)
        self.button_install.setText("Reinstall")
        self.button_open.setEnabled(True)
        self.button_uninstall.setEnabled(True)
        self.show_collection_metadata(self._sel_coll_id)

    def install_canceled(self):
        self.progress_dialog.hide()
        self.show_progress_dialog("Cancelling installation...")
        self.installer_worker.abort()

    def install_aborted(self):
        if self.installer_thread.isRunning():
            self.installer_thread.quit()
        self.installer_thread.finished.connect(self.progress_dialog.hide)

    def install_progress(self, text):
        self.progress_dialog.setLabelText(text)

    def uninstall_collection(self):
        """Slot called when the user clicks the 'Uninstall' button."""
        # get the QModelIndex for the item to be uninstalled
        uninstall_index = self.list_view_collections.currentIndex()
        coll_id = self._sel_coll_id
        try:
            self.collection_manager.uninstall(coll_id)
        except Exception as e:
            LOGGER.error("Could not uninstall collection " +
                         config.COLLECTIONS[coll_id]["name"] + ":\n" + str(e))
        else:
            QMessageBox.information(
                self, "Resource Sharing",
                "The collection was successfully uninstalled!")
            self.reload_collections_model()
            # Fix the GUI
            currentMenuRow = self.menu_list_widget.currentRow()
            self.set_current_tab(currentMenuRow)
            self.populate_repositories_widget()
            rowCount = self.collection_proxy.rowCount()
            if rowCount > 0:
                # Set the current (and selected) row in the listview
                newRow = uninstall_index.row()
                # Check if this was the last element
                rowCount = self.collection_proxy.rowCount()
                if newRow == rowCount:
                    newRow = newRow - 1
                # Select the new current element
                newIndex = self.collections_model.createIndex(newRow, 0)
                selection_model = self.list_view_collections.selectionModel()
                selection_model.setCurrentIndex(newIndex,
                                                selection_model.ClearAndSelect)
                # Get the id of the current collection
                proxyModel = self.list_view_collections.model()
                proxyIndex = proxyModel.index(newRow, 0)
                current_coll_id = proxyIndex.data(COLLECTION_ID_ROLE)
                self._sel_coll_id = current_coll_id
                # Update buttons
                status = config.COLLECTIONS[current_coll_id]["status"]
                if status == COLLECTION_INSTALLED_STATUS:
                    self.button_install.setEnabled(True)
                    self.button_install.setText("Reinstall")
                    self.button_open.setEnabled(True)
                    self.button_uninstall.setEnabled(True)
                else:
                    self.button_install.setEnabled(True)
                    self.button_install.setText("Install")
                    self.button_open.setEnabled(False)
                    self.button_uninstall.setEnabled(False)
                # Update the web_view_details frame
                self.show_collection_metadata(current_coll_id)
            else:
                self.button_install.setEnabled(False)
                self.button_install.setText("Install")
                self.button_open.setEnabled(False)
                self.button_uninstall.setEnabled(False)

    def open_collection(self):
        """Slot called when the user clicks the 'Open' button."""
        collection_path = local_collection_path(self._sel_coll_id)
        directory_url = QUrl.fromLocalFile(str(collection_path))
        QDesktopServices.openUrl(directory_url)

    def reload_data_and_widget(self):
        """Reload repositories and collections and update widgets related."""
        self.reload_repositories_widget()
        self.reload_collections_model()

    def reload_repositories_widget(self):
        """Refresh tree repositories using new repositories data."""
        self.repository_manager.load_directories()
        self.populate_repositories_widget()

    def populate_repositories_widget(self):
        """Populate the current dictionary repositories to the tree widget."""
        # Clear the current tree widget
        self.tree_repositories.clear()
        installed_collections = self.collection_manager.get_installed_collections(
        )
        # Export the updated ones from the repository manager
        repo_Font = QFont()
        repo_with_installed_Font = QFont()
        repo_with_installed_Font.setWeight(60)
        collection_brush = QBrush(Qt.darkGray)
        installed_collection_brush = QBrush(QColor(60, 25, 10))
        for repo_name in self.repository_manager.directories:
            url = self.repository_manager.directories[repo_name]["url"]
            item = QTreeWidgetItem(self.tree_repositories, REPOSITORY_ITEM)
            # Is the repository in the QGIS resource directory?
            if url in self.repository_manager._online_directories.values():
                repo_with_installed_Font.setUnderline(True)
                repo_Font.setUnderline(True)
            else:
                repo_with_installed_Font.setUnderline(False)
                repo_Font.setUnderline(False)
            item.setText(0, repo_name)
            item.setText(1, url)
            item.setFont(0, repo_Font)
            for coll_id in config.COLLECTIONS:
                if ("repository_name" in config.COLLECTIONS[coll_id].keys()
                        and config.COLLECTIONS[coll_id]["repository_name"]
                        == repo_name):
                    coll_name = config.COLLECTIONS[coll_id]["name"]
                    coll_tags = config.COLLECTIONS[coll_id]["tags"]
                    collectionItem = QTreeWidgetItem(item, COLLECTION_ITEM)
                    brush = collection_brush
                    collectionFont = QFont()
                    collectionFont.setStyle(QFont.StyleItalic)
                    collitemtext = coll_name
                    if (installed_collections
                            and coll_id in installed_collections.keys()):
                        collitemtext = coll_name + " (installed)"
                        brush = installed_collection_brush
                        item.setFont(0, repo_with_installed_Font)
                        item.setForeground(0, brush)
                        item.setForeground(1, brush)
                    collectionItem.setFont(0, collectionFont)
                    collectionItem.setForeground(0, brush)
                    collectionItem.setText(0, collitemtext)
                    collectionItem.setFont(1, collectionFont)
                    collectionItem.setForeground(1, brush)
                    collectionItem.setText(1, coll_tags)
        self.tree_repositories.resizeColumnToContents(0)
        self.tree_repositories.resizeColumnToContents(1)
        self.tree_repositories.sortItems(1, Qt.AscendingOrder)

    def reload_collections_model(self):
        """Reload the collections model with the current collections."""
        self.collections_model.clear()
        installed_collections = self.collection_manager.get_installed_collections(
        )
        for id in config.COLLECTIONS:
            collection_name = config.COLLECTIONS[id]["name"]
            collection_author = config.COLLECTIONS[id]["author"]
            collection_tags = config.COLLECTIONS[id]["tags"]
            collection_description = config.COLLECTIONS[id]["description"]
            collection_status = config.COLLECTIONS[id]["status"]
            repository_name = ""
            if "repository_name" in config.COLLECTIONS[id].keys():
                repository_name = config.COLLECTIONS[id]["repository_name"]
            item = QStandardItem(collection_name + " (" + repository_name +
                                 ")")
            item.setEditable(False)
            item.setData(id, COLLECTION_ID_ROLE)
            item.setData(collection_name, COLLECTION_NAME_ROLE)
            item.setData(collection_description, COLLECTION_DESCRIPTION_ROLE)
            item.setData(collection_author, COLLECTION_AUTHOR_ROLE)
            item.setData(collection_tags, COLLECTION_TAGS_ROLE)
            item.setData(collection_status, COLLECTION_STATUS_ROLE)
            # Make installed collections stand out
            if installed_collections and id in installed_collections.keys():
                collectionFont = QFont()
                collectionFont.setWeight(60)
                item.setFont(collectionFont)
            self.collections_model.appendRow(item)
        self.collections_model.sort(0, Qt.AscendingOrder)

    def on_tree_repositories_itemSelectionChanged(self):
        """Slot for the itemSelectionChanged signal of tree_repositories."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item and selected_item.type() == REPOSITORY_ITEM:
            if selected_item:
                repo_name = selected_item.text(0)
            if not repo_name:
                return
            if repo_name not in self.repository_manager.directories.keys():
                return
            repo_url = self.repository_manager.directories[repo_name]["url"]
            # Disable the edit and delete buttons for "official" repositories
            if repo_url in self.repository_manager._online_directories.values(
            ):
                self.button_edit.setEnabled(False)
                self.button_delete.setEnabled(False)
            else:
                # Activate the edit and delete buttons
                self.button_edit.setEnabled(True)
                self.button_delete.setEnabled(True)
        elif selected_item and selected_item.type() == COLLECTION_ITEM:
            self.button_edit.setEnabled(False)
            self.button_delete.setEnabled(False)
        else:
            self.button_edit.setEnabled(False)
            self.button_delete.setEnabled(False)

    def on_list_view_collections_clicked(self, index):
        """Slot called when the user clicks an item in
        list_view_collections."""
        real_index = self.collection_proxy.mapToSource(index)
        if real_index.row() != -1:
            collection_item = self.collections_model.itemFromIndex(real_index)
            collection_id = collection_item.data(COLLECTION_ID_ROLE)
            self._sel_coll_id = collection_id
            # Enable / disable buttons
            status = config.COLLECTIONS[self._sel_coll_id]["status"]
            is_installed = status == COLLECTION_INSTALLED_STATUS
            if is_installed:
                self.button_install.setEnabled(True)
                self.button_install.setText("Reinstall")
                self.button_open.setEnabled(True)
                self.button_uninstall.setEnabled(True)
            else:
                self.button_install.setEnabled(True)
                self.button_install.setText("Install")
                self.button_open.setEnabled(False)
                self.button_uninstall.setEnabled(False)
            # Show  metadata
            self.show_collection_metadata(collection_id)

    @pyqtSlot(str)
    def filter_collections(self, text):
        search = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp)
        self.collection_proxy.setFilterRegExp(search)

    def show_collection_metadata(self, id):
        """Show the collection metadata given the ID."""
        html = self.collection_manager.get_html(id)
        self.web_view_details.setHtml(html)

    def reject(self):
        """Slot called when the dialog is closed."""
        # Serialize collections to settings
        self.repository_manager.serialize_repositories()
        self.done(0)

    def open_help(self):
        """Open help."""
        doc_url = QUrl("http://qgis-contribution.github.io/" +
                       "QGIS-ResourceSharing/")
        QDesktopServices.openUrl(doc_url)

    def show_progress_dialog(self, text):
        """Show infinite progress dialog with given text.

        :param text: Text as the label of the progress dialog
        :type text: str
        """
        if self.progress_dialog is None:
            self.progress_dialog = QProgressDialog(self)
            self.progress_dialog.setWindowModality(Qt.WindowModal)
            self.progress_dialog.setAutoClose(False)
            title = self.tr("Resource Sharing")
            self.progress_dialog.setWindowTitle(title)
            # Just use an infinite progress bar here
            self.progress_dialog.setMaximum(0)
            self.progress_dialog.setMinimum(0)
            self.progress_dialog.setValue(0)
            self.progress_dialog.setLabelText(text)
        self.progress_dialog.show()
Exemplo n.º 10
0
class QRAVEMetaWidget(QDockWidget, Ui_QRAVEMetaWidgetBase):
    def __init__(self, parent=None):
        """Constructor."""
        super(QRAVEMetaWidget, self).__init__(parent)
        self.setupUi(self)

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.customContextMenuRequested.connect(self.open_menu)
        self.treeView.doubleClicked.connect(self.default_tree_action)

        self.settings = Settings()
        self.model = QStandardItemModel()
        self.treeView.setModel(self.model)
        self.meta = None
        self.menu = QMenu()

        # Initialize our classes
        self.hide()

    @pyqtSlot(str, str, dict, bool)
    def load(self, label: str, meta_type: str, meta: dict, show: bool = False):
        # re-initialize our model
        self.model.clear()
        self.meta = meta
        root_item = self.model.invisibleRootItem()
        self.model.setColumnCount(2)
        self.model.setHorizontalHeaderLabels(['Meta Name', 'Meta Value'])

        if meta_type == MetaType.PROJECT:
            self.treeView.setHeaderHidden(False)
            self.setWindowTitle('Project MetaData: {}'.format(label))
            self.treeView.setEnabled(True)
            if meta is not None and len(meta.keys()) > 0:
                if 'project' in meta and len(meta['project'].keys()) > 0:
                    proj_meta = QStandardItem('Project Meta')
                    proj_meta_font = proj_meta.font()
                    proj_meta_font.setBold(True)
                    proj_meta.setFont(proj_meta_font)
                    for k, v in meta['project'].items():
                        proj_meta.appendRow(
                            [QStandardItem(k),
                             QStandardItem(v)])
                    root_item.appendRow(proj_meta)
                if 'warehouse' in meta and len(meta['warehouse'].keys()) > 0:
                    wh_meta = QStandardItem('Warehouse Meta')
                    wh_meta_font = proj_meta.font()
                    wh_meta_font.setBold(True)
                    wh_meta.setFont(wh_meta_font)
                    for k, v in meta['warehouse'].items():
                        wh_meta.appendRow([QStandardItem(k), QStandardItem(v)])
                    root_item.appendRow(wh_meta)

        elif meta_type == MetaType.FOLDER:
            self.setWindowTitle('Folder: {}'.format(label))
            self.treeView.setHeaderHidden(True)
            self.treeView.setEnabled(False)
            self.model.setColumnCount(1)
            self.model.setHorizontalHeaderLabels(['Meta Name'])
            no_item = QStandardItem('Folders have no MetaData')
            no_item.setTextAlignment(Qt.AlignCenter)
            no_f = no_item.font()
            no_f.setItalic(True)
            no_item.setFont(no_f)
            root_item.appendRow(no_item)

        elif meta_type == MetaType.LAYER:
            self.setWindowTitle('Layer MetaData: {}'.format(label))
            self.treeView.setEnabled(True)
            self.treeView.setHeaderHidden(False)
            if meta is not None and len(meta.keys()) > 0:
                for k, v in meta.items():
                    root_item.appendRow([QStandardItem(k), QStandardItem(v)])
            else:
                self.treeView.setHeaderHidden(True)
                self.treeView.setEnabled(False)
                self.model.setColumnCount(1)
                self.model.setHorizontalHeaderLabels(['Meta Name'])
                no_item = QStandardItem('This layer has no MetaData')
                no_item.setTextAlignment(Qt.AlignCenter)
                no_f = no_item.font()
                no_f.setItalic(True)
                no_item.setFont(no_f)
                root_item.appendRow(no_item)
        elif meta_type == MetaType.NONE:
            self.treeView.setHeaderHidden(True)
            self.treeView.setEnabled(False)
            self.model.setColumnCount(1)
            self.setWindowTitle('Riverscapes MetaData: {}'.format(label))
            no_item = QStandardItem('This item cannot have metadata')
            no_item.setTextAlignment(Qt.AlignCenter)
            no_f = no_item.font()
            no_f.setItalic(True)
            no_item.setFont(no_f)
            root_item.appendRow(no_item)
            return

        # self.tree.header().setDefaultSectionSize(180)

        # self._populateTree(self.tree, )
        # Finally expand all levels
        self.treeView.expandAll()
        if show is True:
            self.show()

    def closeEvent(self, event):
        self.hide()

    def default_tree_action(self, index):
        item = self.model.itemFromIndex(index)
        data = item.data(Qt.UserRole)

    def open_menu(self, position):

        indexes = self.treeView.selectedIndexes()
        if len(indexes) < 1 or self.meta is None or len(self.meta.keys()) == 0:
            return

        # No multiselect so there is only ever one item
        item_name = self.model.itemFromIndex(indexes[0])
        item_val = self.model.itemFromIndex(
            indexes[1]) if len(indexes) > 0 else None

        self.menu.clear()
        if item_val is not None:
            row_text = {item_name.text(): item_val.text()}
            self.menu.addAction('Copy Name to Clipboard',
                                lambda: self.copy(item_name.text()))
            self.menu.addAction('Copy Value to Clipboard',
                                lambda: self.copy(item_val.text()))
            self.menu.addAction(
                'Copy Row to Clipboard (json)', lambda: self.copy(
                    json.dumps(row_text, indent=4, sort_keys=True)))
        self.menu.addAction(
            'Copy All to Clipboard (json)',
            lambda: self.copy(json.dumps(self.meta, indent=4, sort_keys=True)))

        self.menu.exec_(self.treeView.viewport().mapToGlobal(position))

    def copy(self, data: str):
        cb = QGuiApplication.clipboard()
        cb.clear(mode=cb.Clipboard)
        cb.setText(data, mode=cb.Clipboard)
Exemplo n.º 11
0
class TemplateDocumentSelector(WIDGET, BASE):
    """
    Dialog for selecting a document template from the saved list.
    """
    def __init__(self,
                 parent=None,
                 selectMode=True,
                 filter_data_source='',
                 access_templates=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.notifBar = NotificationBar(self.vlNotification)

        self._mode = selectMode

        # Filter templates by the specified table name
        self._filter_data_source = filter_data_source

        # Document templates in current profile
        self._profile_templates = []

        self._current_profile = current_profile()

        # Load current profile templates
        self._load_current_profile_templates()

        self.access_templates = access_templates or []

        if selectMode:
            self.buttonBox.setVisible(True)
            self.manageButtonBox.setVisible(False)
            currHeight = self.size().height()
            self.resize(200, currHeight)

        else:
            self.buttonBox.setVisible(False)
            self.manageButtonBox.setVisible(True)
            self.setWindowTitle(
                QApplication.translate("TemplateDocumentSelector",
                                       "Template Manager"))

        # Configure manage buttons
        btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok)
        btnEdit.setText(
            QApplication.translate("TemplateDocumentSelector", "Edit..."))
        btnEdit.setIcon(GuiUtils.get_icon("edit.png"))

        btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save)
        btnDelete.setText(
            QApplication.translate("TemplateDocumentSelector", "Delete"))
        btnDelete.setIcon(GuiUtils.get_icon("delete.png"))

        # Connect signals
        self.buttonBox.accepted.connect(self.onAccept)
        btnEdit.clicked.connect(self.onEditTemplate)
        btnDelete.clicked.connect(self.onDeleteTemplate)

        # Get saved document templates then add to the model
        templates = documentTemplates()

        self._docItemModel = QStandardItemModel(parent)
        self._docItemModel.setColumnCount(2)

        # Append current profile templates to the model.
        for dt in self._profile_templates:

            if self._template_contains_filter_table(
                    dt):  # and dt.name in self.access_templates:
                doc_name_item = self._createDocNameItem(dt.name)
                file_path_item = QStandardItem(dt.path)
                self._docItemModel.appendRow([doc_name_item, file_path_item])

        self.lstDocs.setModel(self._docItemModel)

    def _load_current_profile_templates(self):
        # Loads only those templates that refer to tables in the current
        # profile.
        if self._current_profile is None:
            return

        # Get saved document templates then add to the model
        templates = documentTemplates()

        profile_tables = self._current_profile.table_names()

        # Get templates for the current profile
        for name, path in templates.items():
            doc_temp = DocumentTemplate.build_from_path(name, path)
            if doc_temp.data_source is None:
                continue

            # Assert data source is in the current profile
            if doc_temp.data_source.referenced_table_name in profile_tables or \
                    doc_temp.data_source.name() in user_non_profile_views():
                self._add_doc_temp(doc_temp)
                # self._profile_templates.append(doc_temp)

    def _add_doc_temp(self, doc_temp):
        found = False
        for template in self._profile_templates:
            if template.name == doc_temp.name:
                found = True
                break
        if not found:
            self._profile_templates.append(doc_temp)

    def _template_contains_filter_table(self, document_template):
        # Returns true if the template refers to the filter data source

        # If no filter data source defined then always return True

        if document_template.data_source._dataSourceName in user_non_profile_views(
        ):
            return True

        if not self._filter_data_source:
            return True

        referenced_table = document_template.referenced_table_name

        if referenced_table == self._filter_data_source:
            return True

        return False

    @property
    def mode(self):
        return self._mode

    @property
    def filter_data_source(self):
        return self._filter_data_source

    def _createDocNameItem(self, docName):
        """
        Create a template document standard item.
        """
        # Set icon
        icon = QIcon()
        icon.addPixmap(GuiUtils.get_icon_pixmap("document.png"), QIcon.Normal,
                       QIcon.Off)

        dnItem = QStandardItem(icon, docName)

        return dnItem

    def onEditTemplate(self):
        """
        Slot raised to edit document template.
        """
        self.notifBar.clear()

        if self.documentMapping() is None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document template to edit"))
            return

        templateName, filePath = self.documentMapping()

        docName, ok = QInputDialog.getText(self, \
                                           QApplication.translate("TemplateDocumentSelector", "Edit Template"), \
                                           QApplication.translate("TemplateDocumentSelector",
                                                                  "Please enter the new template name below"), \
                                           text=templateName)
        if ok and docName == "":
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Template name cannot be empty"))
            return

        elif docName == templateName:
            return

        elif ok and docName != "":
            result, newTemplatePath = self._editTemplate(filePath, docName)

            if result:
                # Update view
                mIndices = self._selectedMappings()

                docNameItem = self._docItemModel.itemFromIndex(mIndices[0])
                filePathItem = self._docItemModel.itemFromIndex(mIndices[1])

                docNameItem.setText(docName)
                filePathItem.setText(newTemplatePath)

                self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                               "'{0}' template has been successfully updated".format(
                                                                                   docName)))

            else:
                self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                             "Error: '{0}' template could not be updated".format(
                                                                                 templateName)))

    def onDeleteTemplate(self):
        """
        Slot raised to delete document template.
        """
        self.notifBar.clear()

        if self.documentMapping() == None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document template to delete"))
            return

        templateName, filePath = self.documentMapping()

        result = QMessageBox.warning(self, QApplication.translate("TemplateDocumentSelector", \
                                                                  "Confirm delete"),
                                     QApplication.translate("TemplateDocumentSelector", \
                                                            "Are you sure you want to delete '{0}' template?" \
                                                            "This action cannot be undone.\nClick Yes to proceed " \
                                                            "or No to cancel.".format(templateName)),
                                     QMessageBox.Yes | QMessageBox.No)

        if result == QMessageBox.No:
            return

        status = self._deleteDocument(filePath)

        if status:
            # Remove item from list using model index row number
            selectedDocNameIndices = self.lstDocs.selectionModel(
            ).selectedRows(0)
            row = selectedDocNameIndices[0].row()
            self._docItemModel.removeRow(row)
            self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                           "'{0}' template has been successfully removed".format(
                                                                               templateName)))

        else:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Error: '{0}' template could not be removed".format(
                                                                             templateName)))

    def onAccept(self):
        """
        Slot raised to close the dialog only when a selection has been made by the user.
        """
        self.notifBar.clear()

        if self.documentMapping() == None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document"))
            return

        self.accept()

    def _selectedMappings(self):
        """
        Returns the model indices for the selected row.
        """
        selectedDocNameIndices = self.lstDocs.selectionModel().selectedRows(0)
        selectedFilePathIndices = self.lstDocs.selectionModel().selectedRows(1)

        if len(selectedDocNameIndices) == 0:
            return None

        docNameIndex = selectedDocNameIndices[0]
        filePathIndex = selectedFilePathIndices[0]

        return (docNameIndex, filePathIndex)

    def documentMapping(self):
        """
        Returns a tuple containing the selected document name and the corresponding file name.
        """
        mIndices = self._selectedMappings()

        if mIndices == None:
            return None

        docNameItem = self._docItemModel.itemFromIndex(mIndices[0])
        filePathItem = self._docItemModel.itemFromIndex(mIndices[1])

        return (docNameItem.text(), filePathItem.text())

    def _editTemplate(self, templatePath, newName):
        """
        Updates the template document to use the new name.
        """
        templateFile = QFile(templatePath)

        if not templateFile.open(QIODevice.ReadOnly):
            QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Open Operation Error"), \
                                 "{0}\n{1}".format(
                                     QApplication.translate("TemplateDocumentSelector", "Cannot read template file."), \
                                     templateFile.errorString()
                                 ))
            return (False, "")

        templateDoc = QDomDocument()

        if templateDoc.setContent(templateFile):
            composerElement = templateDoc.documentElement()
            titleAttr = composerElement.attributeNode("_title")
            if not titleAttr.isNull():
                titleAttr.setValue(newName)

            # Try remove file
            status = templateFile.remove()

            if not status:
                return (False, "")

            # Create new file
            newTemplatePath = self._composerTemplatesPath(
            ) + "/" + newName + ".sdt"
            newTemplateFile = QFile(newTemplatePath)

            if not newTemplateFile.open(QIODevice.WriteOnly):
                QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Save Operation Error"), \
                                     "{0}\n{1}".format(QApplication.translate("TemplateDocumentSelector",
                                                                              "Could not save template file."), \
                                                       newTemplateFile.errorString()
                                                       ))
                return (False, "")

            if newTemplateFile.write(templateDoc.toByteArray()) == -1:
                QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Save Error"), \
                                     QApplication.translate("TemplateDocumentSelector",
                                                            "Could not save template file."))
                return (False, "")

            newTemplateFile.close()

            return (True, newTemplatePath)

    def _deleteDocument(self, templatePath):
        """
        Delete the document template from the file system.
        """
        docFile = QFile(templatePath)

        return docFile.remove()

    def _composerTemplatesPath(self):
        """
        Reads the path of composer templates in the registry.
        """
        regConfig = RegistryConfig()
        keyName = "ComposerTemplates"

        valueCollection = regConfig.read([keyName])

        if len(valueCollection) == 0:
            return None

        else:
            return valueCollection[keyName]
class ResourceSharingDialog(QDialog, FORM_CLASS):
    TAB_ALL = 0
    TAB_INSTALLED = 1
    TAB_SETTINGS = 2

    def __init__(self, parent=None, iface=None):
        """Constructor.

        :param parent: Optional widget to use as parent
        :type parent: QWidget

        :param iface: An instance of QGisInterface
        :type iface: QGisInterface
        """
        super(ResourceSharingDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface

        # Reconfigure UI
        self.setModal(True)
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)
        self.button_install.setEnabled(False)
        self.button_open.setEnabled(False)
        self.button_uninstall.setEnabled(False)

        # Set QListWidgetItem
        # All
        icon_all = QIcon()
        icon_all.addFile(
            resources_path('img', 'plugin.svg'),
            QSize(),
            QIcon.Normal,
            QIcon.Off)
        item_all = QListWidgetItem()
        item_all.setIcon(icon_all)
        item_all.setText(self.tr('All'))
        # Installed
        icon_installed = QIcon()
        icon_installed.addFile(
            resources_path('img', 'plugin-installed.svg'),
            QSize(),
            QIcon.Normal,
            QIcon.Off)
        item_installed = QListWidgetItem()
        item_installed.setIcon(icon_installed)
        item_installed.setText(self.tr('Installed'))
        item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        # Settings
        icon_settings = QIcon()
        icon_settings.addFile(
            resources_path('img', 'settings.svg'),
            QSize(),
            QIcon.Normal,
            QIcon.Off)
        item_settings = QListWidgetItem()
        item_settings.setIcon(icon_settings)
        item_settings.setText(self.tr('Settings'))
        item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

        # Add the list widget item to the widget
        self.menu_list_widget.addItem(item_all)
        self.menu_list_widget.addItem(item_installed)
        self.menu_list_widget.addItem(item_settings)

        # Init the message bar
        self.message_bar = QgsMessageBar(self)
        self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.vlayoutRightColumn.insertWidget(0, self.message_bar)

        # Progress dialog for any long running process
        self.progress_dialog = None

        # Init repository manager
        self.repository_manager = RepositoryManager()
        self.collection_manager = CollectionManager()
        # Collections list view
        self.collections_model = QStandardItemModel(0, 1)
        self.collections_model.sort(0, Qt.AscendingOrder)
        self.collection_proxy = CustomSortFilterProxyModel(self)
        self.collection_proxy.setSourceModel(self.collections_model)
        self.list_view_collections.setModel(self.collection_proxy)
        # Active selected collection
        self._selected_collection_id = None

        # Slots
        self.button_add.clicked.connect(self.add_repository)
        self.button_edit.clicked.connect(self.edit_repository)
        self.button_delete.clicked.connect(self.delete_repository)
        self.button_reload.clicked.connect(self.reload_repositories)
        self.menu_list_widget.currentRowChanged.connect(self.set_current_tab)
        self.list_view_collections.selectionModel().currentChanged.connect(
            self.on_list_view_collections_clicked)
        self.line_edit_filter.textChanged.connect(self.filter_collections)
        self.button_install.clicked.connect(self.install_collection)
        self.button_open.clicked.connect(self.open_collection)
        self.button_uninstall.clicked.connect(self.uninstall_collection)
        self.button_box.button(QDialogButtonBox.Help).clicked.connect(
            self.open_help)

        # Populate repositories widget and collections list view
        self.populate_repositories_widget()
        self.reload_collections_model()

    def set_current_tab(self, index):
        """Set stacked widget based on active tab.

        :param index: The index of the active list widget item.
        :type index: int
        """
        # Clear message bar first
        self.message_bar.clearWidgets()
        if index == (self.menu_list_widget.count() - 1):
            # Switch to settings tab
            self.stacked_menu_widget.setCurrentIndex(1)
        else:
            # Switch to plugins tab
            if index == 1:
                # Installed
                self.collection_proxy.accepted_status = \
                    COLLECTION_INSTALLED_STATUS
                # Set the web view
                title = self.tr('Installed Collections')
                description = self.tr(
                    'On the left you see the list of all collections '
                    'installed on your QGIS')
            else:
                # All
                self.collection_proxy.accepted_status = COLLECTION_ALL_STATUS
                # Set the web view
                title = self.tr('All Collections')
                description = self.tr(
                    'On the left you see the list of all collections '
                    'available from the repositories registered in the '
                    'settings.')

            context = {
                'resources_path': resources_path(),
                'title': title,
                'description': description
            }
            self.web_view_details.setHtml(
                render_template('tab_description.html', context))
            self.stacked_menu_widget.setCurrentIndex(0)

    def add_repository(self):
        """Open add repository dialog."""
        dlg = ManageRepositoryDialog(self)
        if not dlg.exec_():
            return

        for repo in self.repository_manager.directories.values():
            if dlg.line_edit_url.text().strip() == repo['url']:
                self.message_bar.pushMessage(
                    self.tr(
                        'Unable to add another repository with the same URL!'),
                    Qgis.Critical, 5)
                return

        repo_name = dlg.line_edit_name.text()
        repo_url = dlg.line_edit_url.text().strip()
        repo_auth_cfg = dlg.line_edit_auth_id.text().strip()
        if repo_name in self.repository_manager.directories:
            repo_name += '(2)'

        # Show progress dialog
        self.show_progress_dialog("Fetching repository's metadata")

        # Add repository
        try:
            status, description = self.repository_manager.add_directory(
                repo_name, repo_url, repo_auth_cfg)
            if status:
                self.message_bar.pushMessage(
                    self.tr(
                        'Repository is successfully added'),
                    Qgis.Success, 5)
            else:
                self.message_bar.pushMessage(
                    self.tr(
                        'Unable to add repository: %s') % description,
                    Qgis.Critical, 5)
        except Exception as e:
            self.message_bar.pushMessage(
                self.tr('%s') % e,
                Qgis.Critical, 5)
        finally:
            self.progress_dialog.hide()

        # Reload data and widget
        self.reload_data_and_widget()

        # Deactivate edit and delete button
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)

    def edit_repository(self):
        """Open edit repository dialog."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item:
            repo_name = selected_item.text(0)

        if not repo_name:
            return

        # Check if it's the approved online dir repository
        settings = QSettings()
        settings.beginGroup(repo_settings_group())
        if settings.value(repo_name + '/url') in \
                self.repository_manager._online_directories.values():
            self.message_bar.pushMessage(
                self.tr(
                    'You can not edit the official repositories!'),
                Qgis.Warning, 5)
            return

        dlg = ManageRepositoryDialog(self)
        dlg.line_edit_name.setText(repo_name)
        dlg.line_edit_url.setText(
            self.repository_manager.directories[repo_name]['url'])
        dlg.line_edit_auth_id.setText(
            self.repository_manager.directories[repo_name]['auth_cfg'])

        if not dlg.exec_():
            return

        # Check if the changed URL is already there in the repo
        new_url = dlg.line_edit_url.text().strip()
        old_url = self.repository_manager.directories[repo_name]['url']
        for repo in self.repository_manager.directories.values():
            if new_url == repo['url'] and (old_url != new_url):
                self.message_bar.pushMessage(
                    self.tr('Unable to add another repository with the same '
                            'URL!'),
                    Qgis.Critical, 5)
                return

        new_name = dlg.line_edit_name.text()
        if (new_name in self.repository_manager.directories) and (
                    new_name != repo_name):
            new_name += '(2)'

        new_auth_cfg = dlg.line_edit_auth_id.text()

        # Show progress dialog
        self.show_progress_dialog("Fetching repository's metadata")

        # Edit repository
        try:
            status, description = self.repository_manager.edit_directory(
                repo_name,
                new_name,
                old_url,
                new_url,
                new_auth_cfg
            )
            if status:
                self.message_bar.pushMessage(
                    self.tr('Repository is successfully updated'),
                    Qgis.Success, 5)
            else:
                self.message_bar.pushMessage(
                    self.tr('Unable to add repository: %s') % description,
                    Qgis.Critical, 5)
        except Exception as e:
            self.message_bar.pushMessage(
                self.tr('%s') % e, Qgis.Critical, 5)
        finally:
            self.progress_dialog.hide()

        # Reload data and widget
        self.reload_data_and_widget()

        # Deactivate edit and delete button
        self.button_edit.setEnabled(False)
        self.button_delete.setEnabled(False)

    def delete_repository(self):
        """Delete a repository in the tree widget."""
        selected_item = self.tree_repositories.currentItem()
        if selected_item:
            repo_name = selected_item.text(0)

        if not repo_name:
            return
        # Check if it's the approved online dir repository
        repo_url = self.repository_manager.directories[repo_name]['url']
        if repo_url in self.repository_manager._online_directories.values():
            self.message_bar.pushMessage(
                self.tr(
                    'You can not remove the official repositories!'),
                Qgis.Warning, 5)
            return

        warning = self.tr('Are you sure you want to remove the following '
                          'repository?') + '\n' + repo_name
        if QMessageBox.warning(
                self,
                self.tr('QGIS Resource Sharing'),
                warning,
                QMessageBox.Yes,
                QMessageBox.No) == QMessageBox.No:
            return

        # Remove repository
        installed_collections = \
            self.collection_manager.get_installed_collections(repo_url)
        if installed_collections:
            message = ('You have some installed collections from this '
                       'repository. Please uninstall them first!')
            self.message_bar.pushMessage(message, Qgis.Warning, 5)
        else:
            self.repository_manager.remove_directory(repo_name)
            # Reload data and widget
            self.reload_data_and_widget()
            # Deactivate edit and delete button
            self.button_edit.setEnabled(False)
            self.button_delete.setEnabled(False)

    def reload_repositories(self):
        """Slot for when user clicks reload repositories button."""
        # Show progress dialog
        self.show_progress_dialog('Reloading all repositories')

        for repo_name in self.repository_manager.directories:
            directory = self.repository_manager.directories[repo_name]
            url = directory['url']
            auth_cfg = directory['auth_cfg']
            try:
                status, description = self.repository_manager.reload_directory(
                    repo_name, url, auth_cfg)
                if status:
                    self.message_bar.pushMessage(
                        self.tr(
                            'Repository %s is successfully reloaded') %
                        repo_name, Qgis.Info, 5)
                else:
                    self.message_bar.pushMessage(
                        self.tr(
                            'Unable to reload %s: %s') % (
                            repo_name, description),
                        Qgis.Critical, 5)
            except Exception as e:
                self.message_bar.pushMessage(
                    self.tr('%s') % e,
                    Qgis.Critical, 5)

        self.progress_dialog.hide()
        # Reload data and widget
        self.reload_data_and_widget()

    def install_collection(self):
        """Slot for when user clicks download button."""
        self.show_progress_dialog('Starting installation process...')
        self.progress_dialog.canceled.connect(self.install_canceled)

        self.installer_thread = QThread()
        self.installer_worker = CollectionInstaller(
            self.collection_manager, self._selected_collection_id)
        self.installer_worker.moveToThread(self.installer_thread)
        self.installer_worker.finished.connect(self.install_finished)
        self.installer_worker.aborted.connect(self.install_aborted)
        self.installer_worker.progress.connect(self.install_progress)
        self.installer_thread.started.connect(self.installer_worker.run)
        self.installer_thread.start()

    def install_finished(self):
        # Process the result
        self.progress_dialog.hide()
        if self.installer_worker.install_status:
            self.reload_collections_model()
            message = '%s is installed successfully' % (
                config.COLLECTIONS[self._selected_collection_id]['name'])
        else:
            message = self.installer_worker.error_message
        QMessageBox.information(self, 'Resource Sharing', message)
        # Clean up the worker and thread
        self.installer_worker.deleteLater()
        self.installer_thread.quit()
        self.installer_thread.wait()
        self.installer_thread.deleteLater()

    def install_canceled(self):
        self.progress_dialog.hide()
        self.show_progress_dialog('Cancelling installation...')
        self.installer_worker.abort()

    def install_aborted(self):
        if self.installer_thread.isRunning():
            self.installer_thread.quit()
        self.installer_thread.finished.connect(self.progress_dialog.hide)

    def install_progress(self, text):
        self.progress_dialog.setLabelText(text)

    def uninstall_collection(self):
        """Slot called when user clicks uninstall button."""
        try:
            self.collection_manager.uninstall(self._selected_collection_id)
        except Exception as e:
            raise
        self.reload_collections_model()
        QMessageBox.information(
            self,
            'Resource Sharing',
            'The collection is uninstalled succesfully!')

    def open_collection(self):
        """Slot for when user clicks 'Open' button."""
        collection_path = local_collection_path(self._selected_collection_id)
        directory_url = QUrl.fromLocalFile(collection_path)
        QDesktopServices.openUrl(directory_url)

    def reload_data_and_widget(self):
        """Reload repositories and collections and update widgets related."""
        self.reload_repositories_widget()
        self.reload_collections_model()

    def reload_repositories_widget(self):
        """Refresh tree repositories using new repositories data."""
        self.repository_manager.load_directories()
        self.populate_repositories_widget()

    def populate_repositories_widget(self):
        """Populate the current dictionary repositories to the tree widget."""
        # Clear the current tree widget
        self.tree_repositories.clear()

        # Export the updated ones from the repository manager
        for repo_name in self.repository_manager.directories:
            url = self.repository_manager.directories[repo_name]['url']
            item = QTreeWidgetItem(self.tree_repositories)
            item.setText(0, repo_name)
            item.setText(1, url)
        self.tree_repositories.resizeColumnToContents(0)
        self.tree_repositories.resizeColumnToContents(1)
        self.tree_repositories.sortItems(1, Qt.AscendingOrder)

    def reload_collections_model(self):
        """Reload the collections model with the current collections."""
        self.collections_model.clear()
        for id in config.COLLECTIONS:
            collection_name = config.COLLECTIONS[id]['name']
            collection_author = config.COLLECTIONS[id]['author']
            collection_tags = config.COLLECTIONS[id]['tags']
            collection_description = config.COLLECTIONS[id]['description']
            collection_status = config.COLLECTIONS[id]['status']
            item = QStandardItem(collection_name)
            item.setEditable(False)
            item.setData(id, COLLECTION_ID_ROLE)
            item.setData(collection_name, COLLECTION_NAME_ROLE)
            item.setData(collection_description, COLLECTION_DESCRIPTION_ROLE)
            item.setData(collection_author, COLLECTION_AUTHOR_ROLE)
            item.setData(collection_tags, COLLECTION_TAGS_ROLE)
            item.setData(collection_status, COLLECTION_STATUS_ROLE)
            self.collections_model.appendRow(item)
        self.collections_model.sort(0, Qt.AscendingOrder)

    def on_tree_repositories_itemSelectionChanged(self):
        """Slot for when the itemSelectionChanged signal emitted."""
        # Activate edit and delete button
        self.button_edit.setEnabled(True)
        self.button_delete.setEnabled(True)

    def on_list_view_collections_clicked(self, index):
        """Slot for when the list_view_collections is clicked."""
        real_index = self.collection_proxy.mapToSource(index)
        if real_index.row() != -1:
            collection_item = self.collections_model.itemFromIndex(real_index)
            collection_id = collection_item.data(COLLECTION_ID_ROLE)
            self._selected_collection_id = collection_id

            # Enable/disable button
            status = config.COLLECTIONS[self._selected_collection_id]['status']
            is_installed = status == COLLECTION_INSTALLED_STATUS
            if is_installed:
                self.button_install.setEnabled(True)
                self.button_install.setText('Reinstall')
                self.button_open.setEnabled(True)
                self.button_uninstall.setEnabled(True)
            else:
                self.button_install.setEnabled(True)
                self.button_install.setText('Install')
                self.button_open.setEnabled(False)
                self.button_uninstall.setEnabled(False)

            # Show  metadata
            self.show_collection_metadata(collection_id)

    @pyqtSlot(str)
    def filter_collections(self, text):
        search = QRegExp(
            text,
            Qt.CaseInsensitive,
            QRegExp.RegExp)
        self.collection_proxy.setFilterRegExp(search)

    def show_collection_metadata(self, id):
        """Show the collection metadata given the id."""
        html = self.collection_manager.get_html(id)
        self.web_view_details.setHtml(html)

    def reject(self):
        """Slot when the dialog is closed."""
        # Serialize collections to settings
        self.repository_manager.serialize_repositories()
        self.done(0)

    def open_help(self):
        """Open help."""
        doc_url = QUrl('http://www.akbargumbira.com/qgis_resources_sharing')
        QDesktopServices.openUrl(doc_url)

    def show_progress_dialog(self, text):
        """Show infinite progress dialog with given text.

        :param text: Text as the label of the progress dialog
        :type text: str
        """
        if self.progress_dialog is None:
            self.progress_dialog = QProgressDialog(self)
            self.progress_dialog.setWindowModality(Qt.WindowModal)
            self.progress_dialog.setAutoClose(False)
            title = self.tr('Resource Sharing')
            self.progress_dialog.setWindowTitle(title)
            # Just use infinite progress bar here
            self.progress_dialog.setMaximum(0)
            self.progress_dialog.setMinimum(0)
            self.progress_dialog.setValue(0)
            self.progress_dialog.setLabelText(text)

        self.progress_dialog.show()
Exemplo n.º 13
0
class manageAccountsDlg(WIDGET, BASE):
    '''
    User- and Role-management dialog class
    '''

    def __init__(self, plugin):
        QDialog.__init__(self, plugin.iface.mainWindow())
        self.setupUi(self)

        QgsGui.enableAutoGeometryRestore(self)

        self.tbUserRole.setTabIcon(0, GuiUtils.get_icon('user.png'))
        self.tbUserRole.setTabIcon(1, GuiUtils.get_icon('roles.png'))
        self.tbUserRole.setTabIcon(2, GuiUtils.get_icon('user_mapping.png'))

        # Initialize the dialog
        self.initGui()

        # Load users
        self.loadUsers()

        # Load Roles
        self.loadRoles()

        # Load user mappings
        self.loadUserMappings()

        # Reference to the currently selected STDM role for which user mappings need to be defined
        self.currentRole = None

    def initGui(self):
        '''
        Set control properties
        '''
        # Set properties for 'Users' button box
        btnUserNew = self.btnManageUsers.button(QDialogButtonBox.Ok)
        btnUserNew.setText(QApplication.translate("manageAccountsDlg", "New") + "...")
        btnUserNew.clicked.connect(self.onNewUser)

        btnUserEdit = self.btnManageUsers.button(QDialogButtonBox.Save)
        btnUserEdit.setText(QApplication.translate("manageAccountsDlg", "Edit"))
        btnUserEdit.clicked.connect(self.onEditUser)

        btnUserDelete = self.btnManageUsers.button(QDialogButtonBox.Cancel)
        btnUserDelete.setText(QApplication.translate("manageAccountsDlg", "Delete"))
        btnUserDelete.clicked.connect(self.onDeleteUser)

        # Set properties for 'Roles' button box
        btnRoleNew = self.btnManageRoles.button(QDialogButtonBox.Ok)
        btnRoleNew.setText(QApplication.translate("manageAccountsDlg", "New") + "...")
        btnRoleNew.clicked.connect(self.onNewRole)

        btnRoleDelete = self.btnManageRoles.button(QDialogButtonBox.Cancel)
        btnRoleDelete.setText(QApplication.translate("manageAccountsDlg", "Delete"))
        btnRoleDelete.clicked.connect(self.onDeleteRole)

        btnRoleSync = self.btnManageRoles.button(QDialogButtonBox.Apply)
        btnRoleSync.setText(QApplication.translate("manageAccountsDlg", "Sync"))
        btnRoleSync.setToolTip(
            QApplication.translate("manageAccountsDlg", "Synchronize STDM roles with database roles"))
        btnRoleSync.clicked.connect(self.onSyncRoles)

        # Data view signals
        self.lstRoles.clicked.connect(self.onRoleSelected)
        self.lstRoles.activated.connect(self.onRoleSelected)
        self.lstMappingRoles.clicked.connect(self.onRoleMappingSelected)
        self.lstMappingRoles.activated.connect(self.onRoleMappingSelected)
        self.lstMappingUsers.clicked.connect(self.onUserMappingSelected)
        self.lstMappingUsers.activated.connect(self.onUserMappingSelected)

        # Disable any action by the user in the user roles mapping list view
        self.lstMappingUsers.setEnabled(False)

    def onNewUser(self):
        '''
        Slot activated when the user requests to create a new user
        '''
        frmNewUser = newUserDlg(self)
        result = frmNewUser.exec_()

        # Add new user to dependent models
        if result == QDialog.Accepted:
            user = frmNewUser.user

            # Add new user to Users view
            numUsers = self.usersModel.rowCount()
            self.usersModel.insertRows(numUsers, 1)
            index = self.usersModel.index(numUsers)
            self.usersModel.setData(index, user.UserName)

            # Add the user to the user mappings list
            userItem = self._createNewUserMapping(user.UserName)
            numUsers = self.UserMappingsModel.rowCount()
            self.UserMappingsModel.setItem(numUsers, userItem)

            # Sort users
            self.sortUsers()

    def loadUsers(self):
        '''
        Loads the names of existing users
        '''
        self.membership = Membership()
        users = self.membership.getAllUsers()
        self.usersModel = UsersRolesModel(users)
        self.sortUsers()
        self.lstUsers.setModel(self.usersModel)

    def loadRoles(self):
        '''
        Loads the roles of the database cluster
        '''
        self.roleProvider = RoleProvider()
        roles = self.roleProvider.GetAllRoles()
        roleNames = []
        # Remove the postgresql role if it exists
        for role in roles:
            if role.name != 'postgres':
                roleNames.append(role.name)
        self.rolesModel = UsersRolesModel(roleNames)
        self.sortRoles()
        self.lstRoles.setModel(self.rolesModel)

        # Load mapping roles view as well
        self.lstMappingRoles.setModel(self.rolesModel)

    def onEditUser(self):
        '''
        Slot activated for editing a selected user
        '''
        selItems = self.lstUsers.selectionModel().selectedRows()

        if len(selItems) == 0:
            msg = QApplication.translate("manageAccountsDlg", "Please select a user to edit.")
            QMessageBox.warning(self, QApplication.translate("manageAccountsDlg", "Select User"), msg)
            return

        # Get user from the first item in the selection
        username = selItems[0].data()
        user = self.membership.getUser(username)

        if user != None:
            frmUserUpdate = newUserDlg(self, user)
            frmUserUpdate.exec_()

    def onDeleteUser(self):
        '''
        Slot activated for deleting a selected user
        '''
        selItems = self.lstUsers.selectionModel().selectedRows()

        if len(selItems) == 0:
            msg = QApplication.translate("manageAccountsDlg", "Please select a user to delete.")
            QMessageBox.warning(self, QApplication.translate("manageAccountsDlg", "Select User"), msg)
            return

        # Get user from the first item in the selection
        userIndex = selItems[0]
        username = userIndex.data()

        msg = QApplication.translate("manageAccountsDlg",
                                     "Are you sure you want to delete '%s'?\nOnce deleted, this user account cannot be recovered." % (
                                         username,))
        result = QMessageBox.warning(self, QApplication.translate("manageAccountsDlg", "Delete User"), msg,
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if result == QMessageBox.Yes:
            # Delete the user
            self.membership.deleteUser(username)

            # Remove user from the list
            self.usersModel.removeRows(userIndex.row(), 1)

            # Remove corresponding item from the user mapping list view
            self.UserMappingsModel.removeRow(userIndex.row())

            # Sort users
            self.sortUsers()

    def onSyncRoles(self):
        '''
        Slot for synchronizing STDM roles with database cluster roles.
        This is very important especially on first time login by the superuser/'postgres' account
        '''
        self.roleProvider.syncSTDMRoles()

        # Update view
        self.loadRoles()

        # Reset role description label
        self.lblRoleDescription.setText("")

    def sortUsers(self):
        '''
        Sort users in ascending order
        '''
        self.usersModel.sort(0, Qt.AscendingOrder)

    def sortRoles(self):
        '''
        Sorts the roles in ascending order
        '''
        self.rolesModel.sort(0, Qt.AscendingOrder)

    def onNewRole(self):
        '''
        Slot for creating new role
        '''
        frmNewRole = newRoleDlg(self)
        result = frmNewRole.exec_()

        if result == QDialog.Accepted:
            role = frmNewRole.role

            # Add new role to roles view
            numRoles = self.rolesModel.rowCount()
            self.rolesModel.insertRows(numRoles, 1)
            index = self.rolesModel.index(numRoles)
            self.rolesModel.setData(index, role.name)

            # Sort model contents
            self.sortRoles()

    def onRoleSelected(self, index):
        '''
        Slot activated when a role item in the view is selected to load the description text.
        '''
        roleName = index.data()
        role = self.roleProvider.GetRole(roleName)
        if role != None:
            self.lblRoleDescription.setText(role.description)

    def onDeleteRole(self):
        '''
        Slot for deleting the selected role
        '''
        selItems = self.lstRoles.selectionModel().selectedRows()

        if len(selItems) == 0:
            msg = QApplication.translate("manageAccountsDlg", "Please select a role to delete.")
            QMessageBox.warning(self, QApplication.translate("manageAccountsDlg", "Select Role"), msg)
            return

        # Get role from the first item in the selection
        roleIndex = selItems[0]
        rolename = roleIndex.data()

        msg = QApplication.translate("manageAccountsDlg",
                                     "Are you sure you want to delete '%s'?\nOnce deleted, this role cannot be recovered." % (
                                         rolename,))
        result = QMessageBox.warning(self, QApplication.translate("manageAccountsDlg", "Delete Role"), msg,
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if result == QMessageBox.Yes:
            # Delete the role
            self.roleProvider.DeleteSTDMRole(rolename)
            self.roleProvider.DeleteRole(rolename)

            # Remove user from the list and role mappings view
            self.rolesModel.removeRows(roleIndex.row(), 1)

            self.lblRoleDescription.setText("")

            self.sortRoles()

    def loadUserMappings(self, rolename=""):
        '''
        Loads checked/unchecked users in the user mappings list based on whether they are non/members
        of the specified group.
        If rolename is empty, then just load all users with the check state set to 'Unchecked'
        '''
        roleUsers = []
        if rolename != "":
            roleUsers = self.roleProvider.GetUsersInRole(rolename)

        sysUsers = self.usersModel._users

        # Initialize model
        self.UserMappingsModel = QStandardItemModel(len(sysUsers), 1, self)

        # Create standard user items (checkable and with an icon) then add them to the list view
        for u in range(len(sysUsers)):
            user = sysUsers[u]
            userItem = self._createNewUserMapping(user)

            # If the user is in the given role then set checkstate to 'checked'
            sysIndex = getIndex(roleUsers, user)
            if sysIndex != -1:
                userItem.setCheckState(Qt.Checked)

            self.UserMappingsModel.setItem(u, userItem)

        self.lstMappingUsers.setModel(self.UserMappingsModel)

    def _createNewUserMapping(self, username):
        '''
        Adds a new user to the list of user mappings
        '''
        # Set icon
        icon = QIcon()
        icon.addPixmap(GuiUtils.get_icon_pixmap("user.png"), QIcon.Normal, QIcon.Off)

        userItem = QStandardItem(icon, username)
        userItem.setCheckable(True)
        userItem.setCheckState(Qt.Unchecked)

        return userItem

    def onRoleMappingSelected(self, index):
        '''
        Slot activated when a role item in the mapping view is selected to load the users
        in the specified role
        '''
        self.lstMappingUsers.setEnabled(True)
        roleName = index.data()
        self.currentRole = roleName
        self.loadUserMappings(roleName)

    def onUserMappingSelected(self, index):
        '''
        Slot which is called when a user item in the user mapping list has been clicked or selected to
        add it to the currently selected role
        '''
        if self.currentRole != None:

            item = self.UserMappingsModel.itemFromIndex(index)
            username = item.text()

            self.blockSignals(True)

            # Add user to role if the item is selected or remove if it was checked
            if item.checkState() == Qt.Checked:
                self.roleProvider.AddUsersToRoles([username], [self.currentRole])
            elif item.checkState() == Qt.Unchecked:
                self.roleProvider.RemoveUsersFromRoles([username], [self.currentRole])

            self.blockSignals(False)
Exemplo n.º 14
0
class QRiSDockWidget(QtWidgets.QDockWidget, FORM_CLASS):

    closingPlugin = pyqtSignal()

    def __init__(self, parent=None):
        """Constructor."""
        super(QRiSDockWidget, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://doc.qt.io/qt-5/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        self.settings = Settings()

        self.qris_project = None
        self.menu = ContextMenu()

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.treeView.customContextMenuRequested.connect(self.open_menu)
        # self.treeView.doubleClicked.connect(self.default_tree_action)
        # self.treeView.clicked.connect(self.item_change)
        # self.treeView.expanded.connect(self.expand_tree_item)

        self.model = QStandardItemModel()
        self.treeView.setModel(self.model)

    # Take this out of init so that nodes can be added as new data is added and imported;
    def build_tree_view(self, qris_project, new_item=None):
        """Builds items in the tree view based on dictionary values that are part of the project"""
        self.qris_project = qris_project

        self.model.clear()
        self.tree_state = {}
        rootNode = self.model.invisibleRootItem()

        # set the project root
        project_node = QStandardItem(self.qris_project.project_name)
        project_node.setIcon(QIcon(':/plugins/qris_toolbar/icon.png'))
        project_node.setData('project_root', item_code['item_type'])
        rootNode.appendRow(project_node)
        self.treeView.setExpanded(project_node.index(), True)

        # Add project extent layers to tree
        extent_folder = QStandardItem("Project Extents")
        extent_folder.setIcon(QIcon(':/plugins/qris_toolbar/test_folder.png'))
        extent_folder.setData('extent_folder', item_code['item_type'])
        project_node.appendRow(extent_folder)

        for extent in self.qris_project.project_extents.values():
            extent_node = QStandardItem(extent.display_name)
            extent_node.setIcon(
                QIcon(':/plugins/qris_toolbar/test_project_extent.png'))
            extent_node.setData('extent_node', item_code['item_type'])
            extent_node.setData(extent, item_code['INSTANCE'])
            extent_folder.appendRow(extent_node)

        # Add project layers node
        layers_folder = QStandardItem("Project Layers")
        layers_folder.setIcon(QIcon(':/plugins/qris_toolbar/test_folder.png'))
        layers_folder.setData('layers_folder', item_code['item_type'])
        project_node.appendRow(layers_folder)

        # TODO extend this for geometry types and raster layers
        for layer in self.qris_project.project_vector_layers.values():
            layer_node = QStandardItem(layer.display_name)
            # TODO change icon by type
            layer_node.setIcon(QIcon(':/plugins/qris_toolbar/test_layers.png'))
            layer_node.setData('layer_node', item_code['item_type'])
            layer_node.setData(layer, item_code['INSTANCE'])
            layers_folder.appendRow(layer_node)

        # # Add riverscape surfaces node
        # # TODO go through and add layers to the tree
        # riverscape_surfaces_node = QStandardItem("Riverscape Surfaces")
        # riverscape_surfaces_node.setIcon(QIcon(':/plugins/qris_toolbar/BrowseFolder.png'))
        # riverscape_surfaces_node.setData('riverscape_surfaces_folder', item_code['item_type'])
        # riverscape_surfaces_node.setData('group', item_code['item_layer'])
        # project_node.appendRow(riverscape_surfaces_node)

        # # Add riverscape segments node
        # # TODO go through and add layers to the tree
        # riverscape_segments_node = QStandardItem("Riverscape Segments")
        # riverscape_segments_node.setIcon(QIcon(':/plugins/qris_toolbar/BrowseFolder.png'))
        # riverscape_segments_node.setData('riverscape_segments_folder', item_code['item_type'])
        # riverscape_segments_node.setData('group', item_code['item_layer'])
        # project_node.appendRow(riverscape_segments_node)

        # # Add detrended rasters to tree
        # detrended_rasters = QStandardItem("Detrended Rasters")
        # detrended_rasters.setIcon(QIcon(':/plugins/qris_toolbar/BrowseFolder.png'))
        # detrended_rasters.setData("DetrendedRastersFolder", item_code['item_type'])
        # detrended_rasters.setData('group', item_code['item_layer'])
        # project_node.appendRow(detrended_rasters)

        # for raster in self.qris_project.detrended_rasters.values():
        #     detrended_raster = QStandardItem(raster.name)
        #     detrended_raster.setIcon(QIcon(':/plugins/qris_toolbar/qris_raster.png'))
        #     detrended_raster.setData('DetrendedRaster', item_code['item_type'])
        #     detrended_raster.setData(raster, item_code['INSTANCE'])
        #     detrended_raster.setData('raster_layer', item_code['item_layer'])
        #     detrended_rasters.appendRow(detrended_raster)

        #     if len(raster.surfaces.values()) > 0:
        #         item_surfaces = QStandardItem("Surfaces")
        #         item_surfaces.setIcon(QIcon(':/plugins/qris_toolbar/BrowseFolder.png'))
        #         item_surfaces.setData('group', item_code['item_layer'])
        #         detrended_raster.appendRow(item_surfaces)
        #         for surface in raster.surfaces.values():
        #             item_surface = QStandardItem(surface.name)
        #             item_surface.setIcon(QIcon(':/plugins/qris_toolbar/layers/Polygon.png'))
        #             item_surface.setData('DetrendedRasterSurface', item_code['item_type'])
        #             item_surface.setData('surface_layer', item_code['item_layer'])
        #             item_surface.setData(surface, item_code['INSTANCE'])
        #             item_surfaces.appendRow(item_surface)

        # # Add assessments to tree
        # assessments_parent_node = QStandardItem("Riverscape Assessments")
        # assessments_parent_node.setIcon(QIcon(':/plugins/qris_toolbar/BrowseFolder.png'))
        # assessments_parent_node.setData('assessments_folder', item_code['item_type'])
        # assessments_parent_node.setData('group', item_code['item_layer'])
        # project_node.appendRow(assessments_parent_node)

        # if self.qris_project.project_assessments:
        #     self.qris_project.assessments_path = os.path.join(self.qris_project.project_path, "Assessments.gpkg")
        #     assessments_layer = QgsVectorLayer(self.qris_project.assessments_path + "|layername=assessments", "assessments", "ogr")
        #     for assessment_feature in assessments_layer.getFeatures():
        #         assessment_node = QStandardItem(assessment_feature.attribute('assessment_date').toString('yyyy-MM-dd'))
        #         assessment_node.setIcon(QIcon(':/plugins/qris_toolbar/BrowseFolder.png'))
        #         assessment_node.setData('dam_assessment', item_code['item_type'])
        #         assessment_node.setData('group', item_code['item_layer'])
        #         assessment_node.setData(assessment_feature.attribute('fid'), item_code['feature_id'])
        #         assessments_parent_node.appendRow(assessment_node)

        # assessments_parent_node.sortChildren(Qt.AscendingOrder)

        # Add designs to tree
        design_folder = QStandardItem("Low-Tech Designs")
        design_folder.setIcon(QIcon(':/plugins/qris_toolbar/test_folder.png'))
        design_folder.setData('design_folder', item_code['item_type'])
        project_node.appendRow(design_folder)
        self.treeView.setExpanded(design_folder.index(), True)

        design_geopackage_path = self.qris_project.project_designs.geopackage_path(
            self.qris_project.project_path)
        designs_path = design_geopackage_path + '|layername=designs'
        if os.path.exists(design_geopackage_path):
            designs_layer = QgsVectorLayer(designs_path, "designs", "ogr")
            for design_feature in designs_layer.getFeatures():
                # If these data types stick this should be refactored into a create node function
                design_node = QStandardItem(design_feature.attribute('name'))
                design_node.setIcon(
                    QIcon(':/plugins/qris_toolbar/test_design.png'))
                design_node.setData('design', item_code['item_type'])
                design_node.setData(design_feature.attribute('fid'),
                                    item_code['feature_id'])
                design_folder.appendRow(design_node)

                # TODO add the structure, footprint, and zoi to the tree under each design

            # TODO This just doesn't work very well
            design_folder.sortChildren(Qt.AscendingOrder)

        # populate structure types
        structure_type_folder = QStandardItem("Structure Types")
        structure_type_folder.setIcon(
            QIcon(':/plugins/qris_toolbar/test_settings.png'))
        structure_type_folder.setData('structure_type_folder',
                                      item_code['item_type'])
        design_folder.appendRow(structure_type_folder)

        structure_type_path = design_geopackage_path + '|layername=structure_types'
        structure_type_layer = QgsVectorLayer(structure_type_path,
                                              "structure_types", "ogr")
        for structure_type in structure_type_layer.getFeatures():
            structure_type_node = QStandardItem(
                structure_type.attribute('name'))
            # TODO change the icon
            structure_type_node.setIcon(
                QIcon(':/plugins/qris_toolbar/test_structure.png'))
            structure_type_node.setData('structure_type',
                                        item_code['item_type'])
            structure_type_node.setData(structure_type.attribute('fid'),
                                        item_code['feature_id'])
            structure_type_folder.appendRow(structure_type_node)

        # populate design phases types
        phase_folder = QStandardItem("Implementation Phases")
        # TODO change icon
        phase_folder.setIcon(QIcon(':/plugins/qris_toolbar/test_settings.png'))
        phase_folder.setData('phase_folder', item_code['item_type'])
        design_folder.appendRow(phase_folder)

        phase_path = design_geopackage_path + '|layername=phases'
        phase_layer = QgsVectorLayer(phase_path, "phases", "ogr")
        for phase in phase_layer.getFeatures():
            phase_node = QStandardItem(phase.attribute('name'))
            # TODO change the icon
            phase_node.setIcon(QIcon(':/plugins/qris_toolbar/test_phase.png'))
            phase_node.setData('phase', item_code['item_type'])
            phase_node.setData(phase.attribute('fid'), item_code['feature_id'])
            phase_folder.appendRow(phase_node)

        # populate zoi types
        zoi_type_folder = QStandardItem("ZOI Types")
        zoi_type_folder.setIcon(
            QIcon(':/plugins/qris_toolbar/test_settings.png'))
        zoi_type_folder.setData('zoi_type_folder', item_code['item_type'])
        design_folder.appendRow(zoi_type_folder)

        zoi_type_path = design_geopackage_path + '|layername=zoi_types'
        zoi_type_layer = QgsVectorLayer(zoi_type_path, "zoi_types", "ogr")
        for zoi_type in zoi_type_layer.getFeatures():
            zoi_type_node = QStandardItem(zoi_type.attribute('name'))
            # TODO change the icon
            zoi_type_node.setIcon(
                QIcon(':/plugins/qris_toolbar/test_influence.png'))
            zoi_type_node.setData('zoi_type', item_code['item_type'])
            zoi_type_node.setData(zoi_type.attribute('fid'),
                                  item_code['feature_id'])
            zoi_type_folder.appendRow(zoi_type_node)

        # Add a placed for photos
        # photos_folder = QStandardItem("Project Photos")
        # photos_folder.setIcon(QIcon(':/plugins/qris_toolbar/BrowseFolder.png'))
        # photos_folder.setData('photos_folder', item_code['item_type'])
        # project_node.appendRow(photos_folder)

        # TODO for now we are expanding the map however need to remember expanded state or add new nodes as we add data
        # self.treeView.expandAll()

        # Check if new item is in the tree, if it is pass it to the add_to_map function
        # Adds a test comment
        if new_item is not None and new_item != '':
            selected_item = self._find_item_in_model(new_item)
            if selected_item is not None:
                add_to_map(self.qris_project, self.model, selected_item)

    def _find_item_in_model(self, name):
        """Looks in the tree for an item name passed from the dataChange method."""
        # TODO may want to pass this is a try except block and give an informative error message
        selected_item = self.model.findItems(name, Qt.MatchRecursive)[0]
        return selected_item

    def get_item_expanded_state(self):
        """Recursively records a list of the expanded state for items in the tree"""

    def closeEvent(self, event):
        self.qris_project = None
        self.closingPlugin.emit()
        event.accept()

    def open_menu(self, position):
        """Connects signals as context menus to items in the tree"""
        self.menu.clear()
        indexes = self.treeView.selectedIndexes()
        if len(indexes) < 1:
            return

        # No multiselect so there is only ever one item
        idx = indexes[0]
        if not idx.isValid():
            return

        model_item = self.model.itemFromIndex(indexes[0])
        item_type = model_item.data(item_code['item_type'])

        if item_type == 'project_root':
            self.menu.addAction('EXPAND_ALL', lambda: self.expand_tree())
            self.menu.addAction('COLLAPSE_ALL', lambda: self.collapse_tree())
            self.menu.addAction(
                'REFRESH_TREE',
                lambda: self.build_tree_view(self.qris_project, None))
        elif item_type == "extent_folder":
            self.menu.addAction('ADD_PROJECT_EXTENT_LAYER',
                                lambda: self.import_project_extent_layer())
            self.menu.addAction('CREATE_BLANK_PROJECT_EXTENT_LAYER',
                                lambda: self.create_blank_project_extent())
        elif item_type == "layers_folder":
            self.menu.addAction('IMPORT_PROJECT_LAYER',
                                lambda: self.import_project_layer())
        elif item_type == "layer_node":
            self.menu.addAction(
                'ADD_TO_MAP',
                lambda: add_to_map(self.qris_project, self.model, model_item))
        elif item_type in ['extent_node', 'Project_Extent']:
            # self.menu.addAction('UPDATE_PROJECT_EXTENT', lambda: self.update_project_extent(model_item))
            # self.menu.addAction('DELETE_PROJECT_EXTENT', lambda: self.delete_project_extent(model_item))
            self.menu.addAction(
                'ADD_TO_MAP',
                lambda: add_to_map(self.qris_project, self.model, model_item))
        elif item_type == "design_folder":
            self.menu.addAction('ADD_DESIGN', lambda: self.add_design())
        elif item_type == "design":
            self.menu.addAction(
                'ADD_TO_MAP_OR_UPDATE_SYMBOLOGY',
                lambda: add_to_map(self.qris_project, self.model, model_item))
        elif item_type == "structure_type_folder":
            self.menu.addAction('ADD_STRUCTURE_TYPE',
                                lambda: self.add_structure_type())
        elif item_type == "zoi_type_folder":
            self.menu.addAction('ADD_ZOI_TYPE', lambda: self.add_zoi_type())
        elif item_type == "phase_folder":
            self.menu.addAction('ADD_PHASE', lambda: self.add_phase())
        else:
            self.menu.clear()
        self.menu.exec_(self.treeView.viewport().mapToGlobal(position))

    def expand_tree(self):
        self.treeView.expandAll()
        return

    def collapse_tree(self):
        self.treeView.collapseAll()
        return

    def add_assessment(self):
        """Initiates adding a new assessment"""
        self.assessment_dialog = AssessmentDlg(self.qris_project)
        self.assessment_dialog.dateEdit_assessment_date.setDate(
            QDate.currentDate())
        self.assessment_dialog.dataChange.connect(self.build_tree_view)
        self.assessment_dialog.show()

    def add_design(self):
        """Initiates adding a new design"""
        self.design_dialog = DesignDlg(self.qris_project)
        # TODO remove this stuff about date
        self.design_dialog.dataChange.connect(self.build_tree_view)
        self.design_dialog.show()

    def add_structure_type(self):
        """Initiates adding a structure type and the structure type dialog"""
        # TODO First check if the path to the database exists
        design_geopackage_path = self.qris_project.project_designs.geopackage_path(
            self.qris_project.project_path)
        if os.path.exists(design_geopackage_path):
            self.structure_type_dialog = StructureTypeDlg(self.qris_project)
            self.structure_type_dialog.dataChange.connect(self.build_tree_view)
            self.structure_type_dialog.show()
        else:
            # TODO move the creation of the design data model so that this isn't necessary
            QMessageBox.information(
                self, "Structure Types",
                "Please create a new project design before adding structure types"
            )

    def add_zoi_type(self):
        """Initiates adding a zoi type and the zoi type dialog"""
        # TODO First check if the path to the database exists
        design_geopackage_path = self.qris_project.project_designs.geopackage_path(
            self.qris_project.project_path)
        if os.path.exists(design_geopackage_path):
            self.zoi_type_dialog = ZoiTypeDlg(self.qris_project)
            self.zoi_type_dialog.dataChange.connect(self.build_tree_view)
            self.zoi_type_dialog.show()
        else:
            # TODO move the creation of the design data model so that this isn't necessary
            QMessageBox.information(
                self, "Structure Types",
                "Please create a new project design before adding a new influence type"
            )

    def add_phase(self):
        """Initiates adding a new phase within the phase dialog"""
        # TODO First check if the path to the database exists
        design_geopackage_path = self.qris_project.project_designs.geopackage_path(
            self.qris_project.project_path)
        if os.path.exists(design_geopackage_path):
            self.phase_dialog = PhaseDlg(self.qris_project)
            self.phase_dialog.dataChange.connect(self.build_tree_view)
            self.phase_dialog.show()
        else:
            # TODO move the creation of the design data model so that this isn't necessary
            QMessageBox.information(
                self, "Structure Types",
                "Please create a new project design before adding phases")

    # This will kick off importing photos
    def import_photos(self):
        pass

    def add_detrended_raster(self):
        # last_browse_path = self.settings.getValue('lastBrowsePath')
        # last_dir = os.path.dirname(last_browse_path) if last_browse_path is not None else None
        dialog_return = QFileDialog.getOpenFileName(
            None, "Add Detrended Raster to QRiS project", None,
            self.tr("Raster Data Sources (*.tif)"))
        if dialog_return is not None and dialog_return[
                0] != "" and os.path.isfile(dialog_return[0]):
            self.addDetrendedDlg = AddDetrendedRasterDlg(
                None, dialog_return[0], self.qris_project)
            self.addDetrendedDlg.dataChange.connect(self.build_tree_view)
            self.addDetrendedDlg.exec()

    def import_project_extent_layer(self):
        """launches the dialog that supports import of a project extent layer polygon"""
        select_layer = QgsDataSourceSelectDialog()
        select_layer.exec()
        uri = select_layer.uri()
        if uri is not None and uri.isValid() and uri.wkbType == 3:
            self.project_extent_dialog = ProjectExtentDlg(
                uri, self.qris_project)
            self.project_extent_dialog.dataChange.connect(self.build_tree_view)
            self.project_extent_dialog.exec_()
        else:
            QMessageBox.critical(self, "Invalid Layer",
                                 "Please select a valid polygon layer")

    def create_blank_project_extent(self):
        """Adds a blank project extent that will be edited by the user"""
        self.project_extent_dialog = ProjectExtentDlg(None, self.qris_project)
        self.project_extent_dialog.dataChange.connect(self.build_tree_view)
        self.project_extent_dialog.exec_()

    def update_project_extent(self):
        """Renames the project extent layer"""
        pass

    # def delete_project_extent(self, selected_item):
    #     """Deletes a project extent layer"""
    #     display_name = selected_item.data(item_code['INSTANCE']).display_name
    #     feature_name = selected_item.data(item_code['INSTANCE']).feature_name
    #     geopackage_path = selected_item.data(item_code['INSTANCE']).geopackage_path(self.qris_project.project_path)

    #     delete_ok = QMessageBox.question(self, f"Delete extent", f"Are you f*****g sure you wanna delete the extent layer: {display_name}")
    #     if delete_ok == QMessageBox.Yes:
    #         # remove from the map if it's there
    #         # TODO consider doing this based on the path
    #         for layer in QgsProject.instance().mapLayers().values():
    #             if layer.name() == display_name:
    #                 QgsProject.instance().removeMapLayers([layer.id()])
    #                 iface.mapCanvas().refresh()

    #         # TODO be sure to test whether the table exists first
    #         gdal_delete = gdal.OpenEx(geopackage_path, gdal.OF_UPDATE, allowed_drivers=['GPKG'])
    #         error = gdal_delete.DeleteLayer(feature_name)
    #         gdal_delete.ExecuteSQL('VACUUM')
    #         # TODO remove this from the Extents dictionary that will also remove from promect xml
    #         del(self.qris_project.project_extents[feature_name])
    #         # refresh the project xml
    #         self.qris_project.write_project_xml()
    #         # refresh the tree
    #         self.build_tree_view(self.qris_project, None)
    #     else:
    #         QMessageBox.information(self, "Delete extent", "No layers were deleted")

    def import_project_layer(self):
        """launches a dialog that supports import of project layers that can be clipped to a project extent"""
        select_layer = QgsDataSourceSelectDialog()
        select_layer.exec()
        uri = select_layer.uri()
        if uri is not None and uri.isValid():  # and uri.wkbType == 3:
            self.project_layer_dialog = ProjectLayerDlg(uri, self.qris_project)
            self.project_layer_dialog.dataChange.connect(self.build_tree_view)
            self.project_layer_dialog.exec_()
        else:
            QMessageBox.critical(self, "Invalid Layer",
                                 "Please select a valid gis layer")

    def explore_elevations(self, selected_item):
        raster = selected_item.data(item_code['INSTANCE'])
        self.elevation_widget = ElevationDockWidget(raster, self.qris_project)
        self.settings.iface.addDockWidget(Qt.LeftDockWidgetArea,
                                          self.elevation_widget)
        self.elevation_widget.dataChange.connect(self.build_tree_view)
        self.elevation_widget.show()
Exemplo n.º 15
0
class QRAVEDockWidget(QDockWidget, Ui_QRAVEDockWidgetBase):

    closingPlugin = pyqtSignal()
    dataChange = pyqtSignal()
    showMeta = pyqtSignal()
    metaChange = pyqtSignal(str, str, dict, bool)

    def __init__(self, parent=None):
        """Constructor."""
        super(QRAVEDockWidget, self).__init__(parent)

        self.setupUi(self)
        self.menu = ContextMenu()
        self.qproject = QgsProject.instance()
        self.qproject.cleared.connect(self.close_all)

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.customContextMenuRequested.connect(self.open_menu)
        self.treeView.doubleClicked.connect(self.default_tree_action)
        self.treeView.clicked.connect(self.item_change)

        self.treeView.expanded.connect(self.expand_tree_item)

        self.settings = Settings()

        self.model = QStandardItemModel()

        self.loaded_projects: List(Project) = []

        # Initialize our classes
        self.basemaps = BaseMaps()
        self.treeView.setModel(self.model)

        self.dataChange.connect(self.reload_tree)
        self.reload_tree()

    def expand_tree_item(self, idx: QModelIndex):
        item = self.model.itemFromIndex(idx)
        item_data = item.data(Qt.UserRole)
        if item_data and item_data.data and isinstance(item_data.data, QRaveBaseMap):
            item_data.data.load_layers()

    def _get_project(self, xml_path):
        try:
            return next(iter(self.loaded_projects))
        except Exception:
            return None

    @pyqtSlot()
    def reload_tree(self):
        # re-initialize our model and reload the projects from file
        # Try not to do this too often if you can
        self.model.clear()
        self.loaded_projects = []
        qrave_projects = self.get_project_settings()

        for project_path in qrave_projects:
            project = Project(project_path)
            project.load()

            if project is not None and project.exists is True and project.qproject is not None:
                self.model.appendRow(project.qproject)
                self.expand_children_recursive(self.model.indexFromItem(project.qproject))
                self.loaded_projects.append(project)

        # Load the tree objects
        self.basemaps.load()

        # Now load the basemaps
        region = self.settings.getValue('basemapRegion')
        if self.settings.getValue('basemapsInclude') is True \
                and region is not None and len(region) > 0 \
                and region in self.basemaps.regions.keys():
            self.model.appendRow(self.basemaps.regions[region])
            self.expand_children_recursive(self.model.indexFromItem(self.basemaps.regions[region]))

    def get_project_settings(self):
        try:
            qrave_projects_raw, type_conversion_ok = self.qproject.readEntry(
                CONSTANTS['settingsCategory'],
                'qrave_projects'
            )

            if type_conversion_ok is False:
                qrave_projects = []
            else:
                qrave_projects = json.loads(qrave_projects_raw)

                if qrave_projects is None or not isinstance(qrave_projects, list):
                    qrave_projects = []

        except Exception as e:
            self.settings.log('Error loading project settings: {}'.format(e), Qgis.Warning)
            qrave_projects = []

        filtered = [pf for pf in qrave_projects if os.path.isfile(pf)]
        filtered.reverse()
        # We Treat this like a stack where the last project in goes on the top.
        # Element 0 should be the top item
        return filtered

    def set_project_settings(self, projects: List[str]):
        self.qproject.writeEntry(CONSTANTS['settingsCategory'], 'qrave_projects', json.dumps(projects))

    @pyqtSlot()
    def add_project(self, xml_path: str):
        qrave_projects = self.get_project_settings()

        # If this project is not already in
        if xml_path not in qrave_projects:
            qrave_projects.append(xml_path)
            self.set_project_settings(qrave_projects)
            self.reload_tree()

            new_project = self._get_project(xml_path)

            # If this is a fresh load and the setting is set we load the default view
            load_default_setting = self.settings.getValue('loadDefaultView')

            if load_default_setting is True \
                    and new_project.default_view is not None \
                    and new_project.default_view in new_project.views:
                self.add_children_to_map(new_project.qproject, new_project.views[new_project.default_view])

    def closeEvent(self, event):
        """ When the user clicks the "X" in the dockwidget titlebar
        """
        self.hide()
        self.qproject.removeEntry(CONSTANTS['settingsCategory'], 'enabled')
        self.closingPlugin.emit()
        event.accept()

    def expand_children_recursive(self, idx: QModelIndex = None, force=False):
        """Expand all the children of a QTreeView node. Do it recursively
        TODO: Recursion might not be the best for large trees here.

        Args:
            idx (QModelIndex, optional): [description]. Defaults to None.
            force: ignore the "collapsed" business logic attribute
        """
        if idx is None:
            idx = self.treeView.rootIndex()

        for idy in range(self.model.rowCount(idx)):
            child = self.model.index(idy, 0, idx)
            self.expand_children_recursive(child, force)

        item = self.model.itemFromIndex(idx)
        item_data = item.data(Qt.UserRole) if item is not None else None

        # NOTE: This is pretty verbose on purpose

        # This thing needs to have data or it defaults to being expanded
        if item_data is None or item_data.data is None:
            collapsed = False

        # Collapsed is an attribute set in the business logic
        # Never expand the QRaveBaseMap object becsause there's a network call involved
        elif isinstance(item_data.data, QRaveBaseMap) \
                or (isinstance(item_data.data, dict) and 'collapsed' in item_data.data and item_data.data['collapsed'] == 'true'):
            collapsed = True

        else:
            collapsed = False

        if not self.treeView.isExpanded(idx) and not collapsed:
            self.treeView.setExpanded(idx, True)

    def default_tree_action(self, idx: QModelIndex):
        if not idx.isValid():
            return

        item = self.model.itemFromIndex(idx)
        item_data: ProjectTreeData = item.data(Qt.UserRole)

        # This is the default action for all add-able layers including basemaps
        if isinstance(item_data.data, QRaveMapLayer):
            if item_data.data.layer_type in [QRaveMapLayer.LayerTypes.FILE, QRaveMapLayer.LayerTypes.REPORT]:
                self.file_system_open(item_data.data.layer_uri)
            else:
                QRaveMapLayer.add_layer_to_map(item)

        # Expand is the default option for wms because we might need to load the layers
        elif isinstance(item_data.data, QRaveBaseMap):
            if item_data.data.tile_type == 'wms':
                pass
            # All the XYZ layers can be added normally.
            else:
                QRaveMapLayer.add_layer_to_map(item)

        elif item_data.type in [QRaveTreeTypes.PROJECT_ROOT]:
            self.change_meta(item, item_data, True)

        # For folder-y types we want Expand and contract is already implemented as a default
        elif item_data.type in [
            QRaveTreeTypes.PROJECT_FOLDER,
            QRaveTreeTypes.PROJECT_REPEATER_FOLDER,
            QRaveTreeTypes.PROJECT_VIEW_FOLDER,
            QRaveTreeTypes.BASEMAP_ROOT,
            QRaveTreeTypes.BASEMAP_SUPER_FOLDER,
            QRaveTreeTypes.BASEMAP_SUB_FOLDER
        ]:
            # print("Default Folder Action")
            pass

        elif item_data.type == QRaveTreeTypes.PROJECT_VIEW:
            print("Default View Action")
            self.add_view_to_map(item_data)

    def item_change(self, pos):
        """Triggered when the user selects a new item in the tree

        Args:pos
            pos ([type]): [description]
        """
        indexes = self.treeView.selectedIndexes()

        # No multiselect so there is only ever one item
        item = self.model.itemFromIndex(indexes[0])
        data_item: ProjectTreeData = item.data(Qt.UserRole)

        if len(indexes) < 1 or data_item.project is None or not data_item.project.exists:
            return

        # Update the metadata if we need to
        self.change_meta(item, data_item)

    def change_meta(self, item: QStandardItem, item_data: ProjectTreeData, show=False):
        """Update the MetaData dock widget with new information

        Args:
            item (QStandardItem): [description]
            data ([type]): [description]
            show (bool, optional): [description]. Defaults to False.
        """
        data = item_data.data
        if isinstance(data, QRaveMapLayer):
            meta = data.meta if data.meta is not None else {}
            self.metaChange.emit(item.text(), MetaType.LAYER, meta, show)

        elif isinstance(data, QRaveBaseMap):
            self.metaChange.emit(item.text(), MetaType.NONE, {}, show)

        elif item_data.type == QRaveTreeTypes.PROJECT_ROOT:
            self.metaChange.emit(item.text(), MetaType.PROJECT, {
                'project': item_data.project.meta,
                'warehouse': item_data.project.warehouse_meta
            }, show)
        elif item_data.type in [
            QRaveTreeTypes.PROJECT_FOLDER,
            QRaveTreeTypes.PROJECT_REPEATER_FOLDER,
            QRaveTreeTypes.PROJECT_VIEW_FOLDER,
            QRaveTreeTypes.BASEMAP_ROOT,
            QRaveTreeTypes.BASEMAP_SUPER_FOLDER,
            QRaveTreeTypes.BASEMAP_SUB_FOLDER
        ]:
            self.metaChange.emit(item.text(), MetaType.FOLDER, data or {}, show)
        elif isinstance(data, dict):
            # this is just the generic case for any kind of metadata
            self.metaChange.emit(item.text(), MetaType.NONE, data or {}, show)
        else:
            # Do not  update the metadata if we have nothing to show
            self.metaChange.emit(item.text(), MetaType.NONE, {}, show)

    def get_warehouse_url(self, wh_meta: Dict[str, str]):

        if wh_meta is not None:

            if 'program' in wh_meta and 'id' in wh_meta:
                return '/'.join([CONSTANTS['warehouseUrl'], wh_meta['program'], wh_meta['id']])

            elif '_rs_wh_id' in wh_meta and '_rs_wh_program' in wh_meta:
                return '/'.join([CONSTANTS['warehouseUrl'], wh_meta['_rs_wh_program'], wh_meta['_rs_wh_id']])

        return None

    def project_warehouse_view(self, project: Project):
        """Open this project in the warehouse if the warehouse meta entries exist
        """
        url = self.get_warehouse_url(project.warehouse_meta)
        if url is not None:
            QDesktopServices.openUrl(QUrl(url))

    def layer_warehouse_view(self, data: QRaveMapLayer):
        """Open this project in the warehouse if the warehouse meta entries exist
        """
        url = self.get_warehouse_url(data.meta)
        if url is not None:
            QDesktopServices.openUrl(QUrl(url))

    def close_all(self):
        for p in range(len(self.loaded_projects)):
            self.close_project(self.loaded_projects[0])

    def close_project(self, project: Project):
        """ Close the project
        """
        try:
            qrave_projects_raw, type_conversion_ok = self.qproject.readEntry(
                CONSTANTS['settingsCategory'],
                'qrave_projects'
            )
            qrave_projects = json.loads(qrave_projects_raw)
            if not type_conversion_ok or qrave_projects is None:
                qrave_projects = []
        except Exception as e:
            self.settings.log('Error closing project: {}'.format(e), Qgis.Warning)
            qrave_projects = []

        # Filter out the project we want to close and reload the tree
        qrave_projects = [x.project_xml_path for x in self.loaded_projects if x != project]

        # Write the settings back to the project
        self.qproject.writeEntry(CONSTANTS['settingsCategory'], 'qrave_projects', json.dumps(qrave_projects))
        self.loaded_projects = qrave_projects
        self.reload_tree()

    def file_system_open(self, fpath: str):
        """Open a file on the operating system using the default action

        Args:
            fpath (str): [description]
        """
        qurl = QUrl.fromLocalFile(fpath)
        QDesktopServices.openUrl(QUrl(qurl))

    def file_system_locate(self, fpath: str):
        """This the OS-agnostic "show in Finder" or "show in explorer" equivalent
        It should open the folder of the item in question

        Args:
            fpath (str): [description]
        """
        final_path = os.path.dirname(fpath)
        while not os.path.isdir(final_path):
            final_path = os.path.dirname(final_path)

        qurl = QUrl.fromLocalFile(final_path)
        QDesktopServices.openUrl(qurl)

    def toggleSubtree(self, item: QStandardItem = None, expand=True):

        def _recurse(curritem):
            idx = self.model.indexFromItem(item)
            if expand is not self.treeView.isExpanded(idx):
                self.treeView.setExpanded(idx, expand)

            for row in range(curritem.rowCount()):
                _recurse(curritem.child(row))

        if item is None:
            if expand is True:
                self.treeView.expandAll()
            else:
                self.treeView.collapseAll()
        else:
            _recurse(item)

    def add_view_to_map(self, item_data: ProjectTreeData):
        """Add a view and all its layers to the map

        Args:
            item (QStandardItem): [description]
        """
        self.add_children_to_map(item_data.project.qproject, item_data.data)

    def add_children_to_map(self, item: QStandardItem, bl_ids: List[str] = None):
        """Iteratively add all children to the map

        Args:
            item (QStandardItem): [description]
            bl_ids (List[str], optional): List of ids to filter by so we don't load everything. this is used for loading views
        """

        for child in self._get_children(item):
            # Is this something we can add to the map?
            project_tree_data = child.data(Qt.UserRole)
            if project_tree_data is not None and isinstance(project_tree_data.data, QRaveMapLayer):
                data = project_tree_data.data
                loadme = False
                # If this layer matches the businesslogic id filter
                if bl_ids is not None and len(bl_ids) > 0:
                    if 'id' in data.bl_attr and data.bl_attr['id'] in bl_ids:
                        loadme = True
                else:
                    loadme = True

                if loadme is True:
                    data.add_layer_to_map(child)

    def _get_children(self, root_item: QStandardItem):
        """Recursion is going to kill us here so do an iterative solution instead
           https://stackoverflow.com/questions/41949370/collect-all-items-in-qtreeview-recursively

        Yields:
            [type]: [description]
        """
        stack = [root_item]
        while stack:
            parent = stack.pop(0)
            for row in range(parent.rowCount()):
                for column in range(parent.columnCount()):
                    child = parent.child(row, column)
                    yield child
                    if child.hasChildren():
                        stack.append(child)

    def _get_parents(self, start_item: QStandardItem):
        stack = []
        placeholder = start_item.parent()
        while placeholder is not None and placeholder != self.model.invisibleRootItem():
            stack.append(placeholder)
            placeholder = start_item.parent()

        return stack.reverse()

    def open_menu(self, position):

        indexes = self.treeView.selectedIndexes()
        if len(indexes) < 1:
            return

        # No multiselect so there is only ever one item
        idx = indexes[0]

        if not idx.isValid():
            return

        item = self.model.itemFromIndex(indexes[0])
        project_tree_data = item.data(Qt.UserRole)  # ProjectTreeData object
        data = project_tree_data.data  # Could be a QRaveBaseMap, a QRaveMapLayer or just some random data

        # This is the layer context menu
        if isinstance(data, QRaveMapLayer):
            if data.layer_type == QRaveMapLayer.LayerTypes.WEBTILE:
                self.basemap_context_menu(idx, item, project_tree_data)
            elif data.layer_type in [QRaveMapLayer.LayerTypes.FILE, QRaveMapLayer.LayerTypes.REPORT]:
                self.file_layer_context_menu(idx, item, project_tree_data)
            else:
                self.map_layer_context_menu(idx, item, project_tree_data)

        elif isinstance(data, QRaveBaseMap):
            # A WMS QARaveBaseMap is just a container for layers
            if data.tile_type == 'wms':
                self.folder_dumb_context_menu(idx, item, project_tree_data)
            # Every other kind of basemap is an add-able layer
            else:
                self.basemap_context_menu(idx, item, project_tree_data)

        elif project_tree_data.type == QRaveTreeTypes.PROJECT_ROOT:
            self.project_context_menu(idx, item, project_tree_data)

        elif project_tree_data.type in [
            QRaveTreeTypes.PROJECT_VIEW_FOLDER,
            QRaveTreeTypes.BASEMAP_ROOT,
            QRaveTreeTypes.BASEMAP_SUPER_FOLDER
        ]:
            self.folder_dumb_context_menu(idx, item, project_tree_data)

        elif project_tree_data.type in [
            QRaveTreeTypes.PROJECT_FOLDER,
            QRaveTreeTypes.PROJECT_REPEATER_FOLDER,
            QRaveTreeTypes.BASEMAP_SUB_FOLDER
        ]:
            self.folder_context_menu(idx, item, project_tree_data)

        elif project_tree_data.type == QRaveTreeTypes.PROJECT_VIEW:
            self.view_context_menu(idx, item, project_tree_data)

        self.menu.exec_(self.treeView.viewport().mapToGlobal(position))

    def map_layer_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData):
        self.menu.clear()
        self.menu.addAction('ADD_TO_MAP', lambda: QRaveMapLayer.add_layer_to_map(item), enabled=item_data.data.exists)
        self.menu.addAction('VIEW_LAYER_META', lambda: self.change_meta(item, item_data, True))

        if bool(self.get_warehouse_url(item_data.data.meta)):
            self.menu.addAction('VIEW_WEB_SOURCE', lambda: self.layer_warehouse_view(item_data))

        self.menu.addAction('BROWSE_FOLDER', lambda: self.file_system_locate(item_data.data.layer_uri))

    def file_layer_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData):
        self.menu.clear()
        self.menu.addAction('OPEN_FILE', lambda: self.file_system_open(item_data.data.layer_uri))
        self.menu.addAction('BROWSE_FOLDER', lambda: self.file_system_locate(item_data.data.layer_uri))

    # Basemap context items
    def basemap_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData):
        self.menu.clear()
        self.menu.addAction('ADD_TO_MAP', lambda: QRaveMapLayer.add_layer_to_map(item))

    # Folder-level context menu
    def folder_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData):
        self.menu.clear()
        self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_children_to_map(item))
        self.menu.addSeparator()
        self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(item, False))
        self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(item, True))

    # Some folders don't have the 'ADD_ALL_TO_MAP' functionality enabled
    def folder_dumb_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData):
        self.menu.clear()
        self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(item, False))
        self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(item, True))

    # View context items
    def view_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData):
        self.menu.clear()
        self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_view_to_map(item_data))

    # Project-level context menu
    def project_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData):
        self.menu.clear()
        self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(None, False))
        self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(None, True))

        self.menu.addSeparator()
        self.menu.addAction('BROWSE_PROJECT_FOLDER', lambda: self.file_system_locate(data.project.project_xml_path))
        self.menu.addAction('VIEW_PROJECT_META', lambda: self.change_meta(item, data, True))
        self.menu.addAction('WAREHOUSE_VIEW', lambda: self.project_warehouse_view(data.project), enabled=bool(self.get_warehouse_url(data.project.warehouse_meta)))
        self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_children_to_map(item))
        self.menu.addSeparator()
        self.menu.addAction('REFRESH_PROJECT_HIERARCHY', self.reload_tree)
        self.menu.addAction('CUSTOMIZE_PROJECT_HIERARCHY', enabled=False)
        self.menu.addSeparator()
        self.menu.addAction('CLOSE_PROJECT', lambda: self.close_project(data.project), enabled=bool(data.project))
Exemplo n.º 16
0
class LookupValueSelector(WIDGET, BASE):
    """
    A dialog that enables to select a value and code from a lookup.
    .. versionadded:: 1.5
    """
    def __init__(self, parent, lookup_entity_name, profile=None):
        """
        Initializes LookupValueSelector.
        :param parent: The parent of the dialog.
        :type parent: QWidget
        :param lookup_entity_name: The lookup entity name
        :type lookup_entity_name: String
        :param profile: The current profile object
        :type profile: Object
        """
        QDialog.__init__(self, parent,
                         Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
        self.setupUi(self)
        self.value_and_code = None
        if profile is None:
            self._profile = current_profile()
        else:
            self._profile = profile

        self.lookup_entity = self._profile.entity_by_name('{}_{}'.format(
            self._profile.prefix, lookup_entity_name))

        self.notice = NotificationBar(self.notice_bar)
        self._view_model = QStandardItemModel()
        self.value_list_box.setModel(self._view_model)
        header_item = QStandardItem(lookup_entity_name)
        self._view_model.setHorizontalHeaderItem(0, header_item)
        self.populate_value_list_view()

        self.selected_code = None
        self.selected_value_code = None

        self.value_list_box.clicked.connect(self.validate_selected_code)

    def populate_value_list_view(self):
        """
        Populates the lookup values and codes.
        """
        self.value_and_code = self.lookup_entity.values

        for value, code in self.value_and_code.items():
            u_value = str(value)
            code_value = self.lookup_entity.values[u_value]

            value_code = QStandardItem('{} ({})'.format(
                code_value.value, code.code))
            value_code.setData(code.code)
            self._view_model.appendRow(value_code)

    def validate_selected_code(self):
        """
        Validate the selected code for the presence of Code or not.
        """
        self.notice.clear()
        self.selected_code_value()
        if self.selected_code == '':
            notice = QApplication.tr(self, 'The selected value has no code.')
            self.notice.insertWarningNotification(notice)

    def selected_code_value(self):
        """
        Get the selected lookup value.
        """
        index = self.value_list_box.currentIndex()
        item = self._view_model.itemFromIndex(index)
        self.selected_code = item.data()
        self.selected_value_code = item.text()

    def accept(self):
        """
        Overridden QDialog accept method.
        """
        self.selected_code_value()
        self.done(1)

    def reject(self):
        """
        Overridden QDialog accept method.
        """
        self.selected_code = None
        self.selected_value_code = None
        self.done(0)