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()
def stop(self): self.mutex.lock() self.stopMe = 1 self.mutex.unlock() QThread.wait(self)
class ThreediDockWidget(QtWidgets.QDockWidget, FORM_CLASS): closingPlugin = pyqtSignal() def __init__(self, iface, plugin_settings, parent=None): """Constructor.""" super().__init__(parent) self.setupUi(self) self.iface = iface self.plugin_settings = plugin_settings self.communication = UICommunication(self.iface, "3Di Models and Simulations", self.lv_log) self.simulations_progresses_thread = None self.simulations_progresses_sentinel = None self.threedi_api = None self.current_user = None self.current_user_full_name = None self.organisations = {} self.current_local_schematisation = None self.build_options = BuildOptions(self) self.simulation_overview_dlg = None self.simulation_results_dlg = None self.upload_dlg = None self.btn_log_in_out.clicked.connect(self.on_log_in_log_out) self.btn_load_schematisation.clicked.connect( self.build_options.load_local_schematisation) self.btn_load_revision.clicked.connect( self.build_options.load_local_schematisation) self.btn_new.clicked.connect(self.build_options.new_schematisation) self.btn_download.clicked.connect( self.build_options.download_schematisation) self.btn_upload.clicked.connect(self.show_upload_dialog) self.btn_simulate.clicked.connect(self.show_simulation_overview) self.btn_results.clicked.connect(self.show_simulation_results) self.btn_manage.clicked.connect(self.on_manage) self.plugin_settings.settings_changed.connect(self.on_log_out) set_icon(self.btn_new, "new.svg") set_icon(self.btn_download, "download.svg") set_icon(self.btn_upload, "upload.svg") set_icon(self.btn_simulate, "api.svg") set_icon(self.btn_results, "results.svg") set_icon(self.btn_manage, "manage.svg") set_icon(self.btn_log_in_out, "arrow.svg") set_icon(self.btn_load_schematisation, "arrow.svg") set_icon(self.btn_load_revision, "arrow.svg") def closeEvent(self, event): if self.threedi_api is not None: self.on_log_out() self.current_local_schematisation = None self.update_schematisation_view() self.closingPlugin.emit() event.accept() def on_log_in_log_out(self): """Trigger log-in or log-out action.""" if self.threedi_api is None: self.on_log_in() else: self.on_log_out() def on_log_in(self): """Handle logging-in.""" log_in_dialog = LogInDialog(self) log_in_dialog.show() QTimer.singleShot(10, log_in_dialog.log_in_threedi) log_in_dialog.exec_() if log_in_dialog.LOGGED_IN: self.threedi_api = log_in_dialog.threedi_api self.current_user = log_in_dialog.user self.current_user_full_name = log_in_dialog.user_full_name self.organisations = log_in_dialog.organisations self.initialize_authorized_view() def on_log_out(self): """Handle logging-out.""" if self.simulations_progresses_thread is not None: self.stop_fetching_simulations_progresses() self.simulation_overview_dlg.model_selection_dlg.unload_cached_layers( ) self.simulation_overview_dlg = None if self.simulation_results_dlg is not None: self.simulation_results_dlg.terminate_download_thread() self.simulation_results_dlg = None if self.upload_dlg: self.upload_dlg.hide() self.upload_dlg = None self.threedi_api = None self.current_user = None self.current_user_full_name = None self.organisations.clear() self.label_user.setText("-") set_icon(self.btn_log_in_out, "arrow.svg") self.btn_log_in_out.setToolTip("Log in") def update_schematisation_view(self): """Method for updating loaded schematisation labels.""" if self.current_local_schematisation: schema_name = self.current_local_schematisation.name schema_dir = self.current_local_schematisation.main_dir schema_label_text = f'<a href="file:///{schema_dir}">{schema_name}</a>' schema_tooltip = f"{schema_name}\n{schema_dir}" self.label_schematisation.setText(schema_label_text) self.label_schematisation.setOpenExternalLinks(True) self.label_schematisation.setToolTip(schema_tooltip) if self.current_local_schematisation.wip_revision: self.label_revision.setText( str(self.current_local_schematisation.wip_revision.number) or "") else: self.label_revision.setText("") else: self.label_schematisation.setText("") self.label_schematisation.setToolTip("") self.label_revision.setText("") def initialize_authorized_view(self): """Method for initializing processes after logging in 3Di API.""" set_icon(self.btn_log_in_out, "x.svg") self.btn_log_in_out.setToolTip("Log out") self.label_user.setText(self.current_user_full_name) self.initialize_simulations_progresses_thread() self.initialize_simulation_overview() self.initialize_simulation_results() def initialize_simulations_progresses_thread(self): """Initializing of the background thread.""" if self.simulations_progresses_thread is not None: self.terminate_fetching_simulations_progresses_thread() self.simulations_progresses_thread = QThread() username, personal_api_key = self.plugin_settings.get_3di_auth() self.simulations_progresses_sentinel = WSProgressesSentinel( self.threedi_api, self.plugin_settings.wss_url, personal_api_key) self.simulations_progresses_sentinel.moveToThread( self.simulations_progresses_thread) self.simulations_progresses_sentinel.thread_finished.connect( self.on_fetching_simulations_progresses_finished) self.simulations_progresses_sentinel.thread_failed.connect( self.on_fetching_simulations_progresses_failed) self.simulations_progresses_thread.started.connect( self.simulations_progresses_sentinel.run) self.simulations_progresses_thread.start() def stop_fetching_simulations_progresses(self): """Changing 'thread_active' flag inside background task that is fetching simulations progresses.""" if self.simulations_progresses_sentinel is not None: self.simulations_progresses_sentinel.stop_listening() def on_fetching_simulations_progresses_finished(self, msg): """Method for cleaning up background thread after it sends 'thread_finished'.""" self.communication.bar_info(msg) self.simulations_progresses_thread.quit() self.simulations_progresses_thread.wait() self.simulations_progresses_thread = None self.simulations_progresses_sentinel = None def on_fetching_simulations_progresses_failed(self, msg): """Reporting fetching progresses failure.""" self.communication.bar_error(msg, log_text_color=Qt.red) def terminate_fetching_simulations_progresses_thread(self): """Forcing termination of background thread if it's still running.""" if self.simulations_progresses_thread is not None and self.simulations_progresses_thread.isRunning( ): self.communication.bar_info( "Terminating fetching simulations progresses thread.") self.simulations_progresses_thread.terminate() self.communication.bar_info( "Waiting for fetching simulations progresses thread termination." ) self.simulations_progresses_thread.wait() self.communication.bar_info( "Fetching simulations progresses worker terminated.") self.simulations_progresses_thread = None self.simulations_progresses_sentinel = None def initialize_simulation_overview(self): """Initialization of the Simulation Overview window.""" self.simulation_overview_dlg = SimulationOverview(self) self.simulation_overview_dlg.label_user.setText( self.current_user_full_name) @api_client_required def show_simulation_overview(self): """Showing Simulation Overview with running simulations progresses.""" if self.simulation_overview_dlg is None: self.initialize_simulation_overview() self.simulation_overview_dlg.show() self.simulation_overview_dlg.raise_() self.simulation_overview_dlg.activateWindow() def initialize_simulation_results(self): """Initialization of the Simulations Results window.""" self.simulation_results_dlg = SimulationResults(self) @api_client_required def show_simulation_results(self): """Showing finished simulations.""" if self.simulation_results_dlg is None: self.initialize_simulation_results() self.simulation_results_dlg.show() self.simulation_results_dlg.raise_() self.simulation_results_dlg.activateWindow() def initialize_upload_status(self): """Initialization of the Upload Status dialog.""" self.upload_dlg = UploadOverview(self) @api_client_required def show_upload_dialog(self): """Show upload status dialog.""" if self.upload_dlg is None: self.initialize_upload_status() self.upload_dlg.show() self.upload_dlg.raise_() self.upload_dlg.activateWindow() @staticmethod def on_manage(): """Open 3Di management webpage.""" webbrowser.open("https://management.3di.live/")
class NGWResourcesModelJob(QObject): started = pyqtSignal() statusChanged = pyqtSignal(str) warningOccurred = pyqtSignal(object) errorOccurred = pyqtSignal(object) finished = pyqtSignal() def __init__(self, parent, worker, model_response=None): """Create job. Arguments: job_id -- Job identification worker -- The class object inherits from NGWResourceModelJob """ QObject.__init__(self, parent) self.__result = None self.__worker = worker self.__job_id = self.__worker.id self.__error = None self.__warnings = [] # self.__job_id = "%s_%s" % (self.__worker.id, str(uuid.uuid1())) self.__worker.started.connect(self.started.emit) self.__worker.dataReceived.connect(self.__rememberResult) self.__worker.statusChanged.connect(self.statusChanged.emit) self.__worker.errorOccurred.connect(self.processJobError) self.__worker.warningOccurred.connect(self.processJobWarnings) self.model_response = model_response def setResponseObject(self, resp): self.model_response = resp self.model_response.job_id = self.__job_id def __rememberResult(self, result): self.__result = result def getJobId(self): return self.__job_id def getResult(self): return self.__result def error(self): return self.__error def processJobError(self, job_error): self.__error = job_error self.errorOccurred.emit(job_error) def processJobWarnings(self, job_error): if self.model_response: self.model_response._warnings.append(job_error) # self.warningOccurred.emit(job_error) def start(self): self.__thread = QThread(self) self.__worker.moveToThread(self.__thread) self.__worker.finished.connect(self.finishProcess) self.__thread.started.connect(self.__worker.run) self.__thread.start() def finishProcess(self): self.__worker.started.disconnect() self.__worker.dataReceived.disconnect() self.__worker.statusChanged.disconnect() self.__worker.errorOccurred.disconnect() self.__worker.warningOccurred.disconnect() self.__worker.finished.disconnect() self.__thread.quit() self.__thread.wait() self.finished.emit()
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()
class OpenTripPlannerPlugin(): """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( self.plugin_dir, 'i18n', 'OpenTripPlannerPlugin_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&OpenTripPlanner Plugin') # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads self.first_start = None # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('OpenTripPlannerPlugin', message) def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: # Adds plugin icon to Plugins toolbar self.iface.addToolBarIcon(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/otp_plugin/icon.png' self.add_action(icon_path, text=self.tr(u'OpenTripPlanner Plugin'), callback=self.run, parent=self.iface.mainWindow()) # will be set False in run() self.first_start = True def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu(self.tr(u'&OpenTripPlanner Plugin'), action) self.iface.removeToolBarIcon(action) def isochronesStartWorker(self): # method to start the worker thread if not self.gf.isochrones_selectedlayer: # dont execute if no layer is selected QgsMessageLog.logMessage( "Warning! No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.isochrones_selectedlayer.featureCount() == 0: QgsMessageLog.logMessage( "Warning! Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return isochrones_memorylayer_vl = QgsVectorLayer( "MultiPolygon?crs=epsg:4326", "Isochrones", "memory") # Create temporary polygon layer (output file) self.isochrones_thread = QThread() self.isochrones_worker = OpenTripPlannerPluginIsochronesWorker( self.dlg, self.iface, self.gf, isochrones_memorylayer_vl) # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html self.isochrones_worker.moveToThread( self.isochrones_thread) # move Worker-Class to a thread # Connect signals and slots: self.isochrones_thread.started.connect(self.isochrones_worker.run) self.isochrones_worker.isochrones_finished.connect( self.isochrones_thread.quit) self.isochrones_worker.isochrones_finished.connect( self.isochrones_worker.deleteLater) self.isochrones_thread.finished.connect( self.isochrones_thread.deleteLater) self.isochrones_worker.isochrones_progress.connect( self.isochronesReportProgress) self.isochrones_worker.isochrones_finished.connect( self.isochronesFinished) self.isochrones_thread.start() # finally start the thread # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress self.gf.disableIsochronesGui() self.gf.disableGeneralSettingsGui() self.isochrones_thread.finished.connect( lambda: self.gf.enableIsochronesGui()) self.isochrones_thread.finished.connect( lambda: self.gf.enableGeneralSettingsGui()) def isochronesKillWorker(self): # method to kill/cancel the worker thread # print('pushed cancel') # debugging # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html try: # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except self.isochrones_worker.stop( ) # call the stop method in worker class to break the work-loop so we can quit the thread if self.isochrones_thread.isRunning( ): # check if a thread is running # print('pushed cancel, thread is running, trying to cancel') # debugging self.isochrones_thread.requestInterruption() self.isochrones_thread.exit( ) # Tells the thread’s event loop to exit with a return code. self.isochrones_thread.quit( ) # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0). self.isochrones_thread.wait( ) # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait except: self.dlg.Isochrones_ProgressBar.setValue(0) self.dlg.Isochrones_StatusBox.setText('') def isochronesReportProgress( self, progress, status): # method to report the progress to gui self.dlg.Isochrones_ProgressBar.setValue( progress) # set the current progress in progress bar self.dlg.Isochrones_StatusBox.setText(status) def isochronesFinished( self, isochrones_resultlayer, isochrones_state, unique_errors="", runtime="00:00:00 (unknown)" ): # method to interact with gui when thread is finished or canceled QgsProject.instance().addMapLayer( isochrones_resultlayer) # Show resultlayer in project # isochrones_state is indicating different states of the thread/result as integer if unique_errors: self.iface.messageBar().pushMessage( "Warning", " Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, level=Qgis.Warning, duration=12) QgsMessageLog.logMessage( "Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, Qgis.Warning) if isochrones_state == 0: self.iface.messageBar().pushMessage( "Warning", " Run-Method was never executed.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) elif isochrones_state == 1: self.iface.messageBar().pushMessage( "Done!", " Isochrones job finished after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif isochrones_state == 2: self.iface.messageBar().pushMessage( "Done!", " Isochrones job canceled after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif isochrones_state == 3: self.iface.messageBar().pushMessage( "Warning", " No Isochrones to create - Check your settings and retry.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif isochrones_state == 99: self.iface.messageBar().pushMessage( "Debugging", " Just having some debugging fun :)", MESSAGE_CATEGORY, level=Qgis.Info, duration=6) else: self.iface.messageBar().pushMessage( "Warning", " Unknown error occurred during execution.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) def aggregated_isochronesStartWorker( self): # method to start the worker thread if not self.gf.aggregated_isochrones_selectedlayer: # dont execute if no layer is selected QgsMessageLog.logMessage( "Warning! No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.aggregated_isochrones_selectedlayer.featureCount() == 0: QgsMessageLog.logMessage( "Warning! Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return aggregated_isochrones_memorylayer_vl = QgsVectorLayer( "MultiPolygon?crs=epsg:4326", "AggregatedIsochrones", "memory") # Create temporary polygon layer (output file) self.aggregated_isochrones_thread = QThread() self.aggregated_isochrones_worker = OpenTripPlannerPluginAggregatedIsochronesWorker( self.dlg, self.iface, self.gf, aggregated_isochrones_memorylayer_vl) # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html self.aggregated_isochrones_worker.moveToThread( self.aggregated_isochrones_thread) # move Worker-Class to a thread # Connect signals and slots: self.aggregated_isochrones_thread.started.connect( self.aggregated_isochrones_worker.run) self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect( self.aggregated_isochrones_thread.quit) self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect( self.aggregated_isochrones_worker.deleteLater) self.aggregated_isochrones_thread.finished.connect( self.aggregated_isochrones_thread.deleteLater) self.aggregated_isochrones_worker.aggregated_isochrones_progress.connect( self.aggregated_isochronesReportProgress) self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect( self.aggregated_isochronesFinished) self.aggregated_isochrones_thread.start() # finally start the thread # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress self.gf.disableAggregatedIsochronesGui() self.gf.disableGeneralSettingsGui() self.aggregated_isochrones_thread.finished.connect( lambda: self.gf.enableAggregatedIsochronesGui()) self.aggregated_isochrones_thread.finished.connect( lambda: self.gf.enableGeneralSettingsGui()) def aggregated_isochronesKillWorker( self): # method to kill/cancel the worker thread # print('pushed cancel') # debugging # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html try: # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except self.aggregated_isochrones_worker.stop( ) # call the stop method in worker class to break the work-loop so we can quit the thread if self.aggregated_isochrones_thread.isRunning( ): # check if a thread is running # print('pushed cancel, thread is running, trying to cancel') # debugging self.aggregated_isochrones_thread.requestInterruption() self.aggregated_isochrones_thread.exit( ) # Tells the thread’s event loop to exit with a return code. self.aggregated_isochrones_thread.quit( ) # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0). self.aggregated_isochrones_thread.wait( ) # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait except: self.dlg.AggregatedIsochrones_ProgressBar.setValue(0) self.dlg.AggregatedIsochrones_StatusBox.setText('') def aggregated_isochronesReportProgress( self, progress, status): # method to report the progress to gui self.dlg.AggregatedIsochrones_ProgressBar.setValue( progress) # set the current progress in progress bar self.dlg.AggregatedIsochrones_StatusBox.setText(status) def aggregated_isochronesFinished( self, aggregated_isochrones_resultlayer, aggregated_isochrones_state, unique_errors="", runtime="00:00:00 (unknown)" ): # method to interact with gui when thread is finished or canceled QgsProject.instance().addMapLayer( aggregated_isochrones_resultlayer) # Show resultlayer in project # aggregated_isochrones_state is indicating different states of the thread/result as integer if unique_errors: self.iface.messageBar().pushMessage( "Warning", " Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors, MESSAGE_CATEGORY, level=Qgis.Warning, duration=12) QgsMessageLog.logMessage( "Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors, MESSAGE_CATEGORY, Qgis.Warning) if aggregated_isochrones_state == 0: self.iface.messageBar().pushMessage( "Warning", " Run-Method was never executed.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) elif aggregated_isochrones_state == 1: self.iface.messageBar().pushMessage( "Done!", " Isochrones job finished after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif aggregated_isochrones_state == 2: self.iface.messageBar().pushMessage( "Done!", " Isochrones job canceled after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif aggregated_isochrones_state == 3: self.iface.messageBar().pushMessage( "Warning", " No Isochrones to create - Check your settings and retry.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif aggregated_isochrones_state == 4: self.iface.messageBar().pushMessage( "Warning", " There is something wrong with your DateTime-Settings, check them and try again.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif aggregated_isochrones_state == 99: self.iface.messageBar().pushMessage( "Debugging", " Just having some debugging fun :)", MESSAGE_CATEGORY, level=Qgis.Info, duration=6) else: self.iface.messageBar().pushMessage( "Warning", " Unknown error occurred during execution.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) def routesStartWorker(self): # method to start the worker thread if not self.gf.routes_selectedlayer_source or not self.gf.routes_selectedlayer_target: QgsMessageLog.logMessage( "Warning! No inputlayer selected. Choose your inputlayers and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " No sourcelayer or no targetlayer selected. Choose your inputlayers and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.routes_selectedlayer_source.fields().count( ) == 0 or self.gf.routes_selectedlayer_target.fields().count() == 0: QgsMessageLog.logMessage( "Warning! Inputlayer has no fields. Script wont work until you add at least one dummy ID-Field.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " Inputlayer has no fields - Add at least a dummy-id field.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.routes_selectedlayer_source.featureCount( ) == 0 or self.gf.routes_selectedlayer_target.featureCount() == 0: QgsMessageLog.logMessage( "Warning! One or both inputlayers are empty. Add some features and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " One or both inputlayers are empty. Add some features and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return routes_memorylayer_vl = QgsVectorLayer( "LineString?crs=epsg:4326", "Routes", "memory") # Create temporary polygon layer (output file) self.routes_thread = QThread() self.routes_worker = OpenTripPlannerPluginRoutesWorker( self.dlg, self.iface, self.gf, routes_memorylayer_vl) # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html self.routes_worker.moveToThread( self.routes_thread) # move Worker-Class to a thread # Connect signals and slots: self.routes_thread.started.connect(self.routes_worker.run) self.routes_worker.routes_finished.connect(self.routes_thread.quit) self.routes_worker.routes_finished.connect( self.routes_worker.deleteLater) self.routes_thread.finished.connect(self.routes_thread.deleteLater) self.routes_worker.routes_progress.connect(self.routesReportProgress) self.routes_worker.routes_finished.connect(self.routesFinished) self.routes_thread.start() # finally start the thread # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress self.gf.disableRoutesGui() self.gf.disableGeneralSettingsGui() self.routes_thread.finished.connect(lambda: self.gf.enableRoutesGui()) self.routes_thread.finished.connect( lambda: self.gf.enableGeneralSettingsGui()) def routesKillWorker(self): # method to kill/cancel the worker thread # print('pushed cancel') # debugging # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html try: # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except self.routes_worker.stop( ) # call the stop method in worker class to break the work-loop so we can quit the thread if self.routes_thread.isRunning(): # check if a thread is running # print('pushed cancel, thread is running, trying to cancel') # debugging self.routes_thread.requestInterruption() self.routes_thread.exit( ) # Tells the thread’s event loop to exit with a return code. self.routes_thread.quit( ) # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0). self.routes_thread.wait( ) # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait except: self.dlg.Routes_ProgressBar.setValue(0) self.dlg.Routes_StatusBox.setText('') def routesReportProgress(self, progress, status): # method to report the progress to gui self.dlg.Routes_ProgressBar.setValue( progress) # set the current progress in progress bar self.dlg.Routes_StatusBox.setText(status) def routesFinished( self, routes_resultlayer, routes_state, unique_errors="", runtime="00:00:00 (unknown)" ): # method to interact with gui when thread is finished or canceled QgsProject.instance().addMapLayer( routes_resultlayer) # Show resultlayer in project # routes_state is indicating different states of the thread/result as integer if unique_errors: self.iface.messageBar().pushMessage( "Warning", " Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, level=Qgis.Warning, duration=12) QgsMessageLog.logMessage( "Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, Qgis.Warning) if routes_state == 0: self.iface.messageBar().pushMessage( "Warning", " Run-Method was never executed.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) elif routes_state == 1: self.iface.messageBar().pushMessage("Done!", " Routes job finished after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif routes_state == 2: self.iface.messageBar().pushMessage("Done!", " Routes job canceled after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif routes_state == 3: self.iface.messageBar().pushMessage( "Warning", " No Routes to create / no matching attributes - Check your settings and retry.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif routes_state == 99: self.iface.messageBar().pushMessage( "Debugging", " Just having some debugging fun :)", MESSAGE_CATEGORY, level=Qgis.Info, duration=6) else: self.iface.messageBar().pushMessage( "Warning", " Unknown error occurred during execution.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) def run(self): """Run method that performs all the real work""" # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = OpenTripPlannerPluginDialog() self.gf = OpenTripPlannerPluginGeneralFunctions( self.dlg, self.iface) # Calling maplayer selection on first startup to load layers into QgsMapLayerComboBox and initialize QgsOverrideButton stuff so selections can be done without actually using the QgsMapLayerComboBox (related to currentIndexChanged.connect(self.isochrones_maplayerselection) below) self.gf.routes_maplayerselection() self.gf.isochrones_maplayerselection() self.gf.aggregated_isochrones_maplayerselection() # Execute Main-Functions on Click: Placing them here prevents them from beeing executed multiple times, see https://gis.stackexchange.com/a/137161/107424 self.dlg.Isochrones_RequestIsochrones.clicked.connect( lambda: self.isochronesStartWorker() ) #Call the start worker method self.dlg.Isochrones_Cancel.clicked.connect( lambda: self.isochronesKillWorker()) self.dlg.AggregatedIsochrones_RequestIsochrones.clicked.connect( lambda: self.aggregated_isochronesStartWorker() ) #Call the start worker method self.dlg.AggregatedIsochrones_Cancel.clicked.connect( lambda: self.aggregated_isochronesKillWorker()) self.dlg.Routes_RequestRoutes.clicked.connect( lambda: self.routesStartWorker()) self.dlg.Routes_Cancel.clicked.connect( lambda: self.routesKillWorker()) # Calling Functions on button click self.dlg.GeneralSettings_CheckServerStatus.clicked.connect( self.gf.check_server_status) self.dlg.GeneralSettings_Save.clicked.connect( self.gf.store_general_variables ) #Call store_general_variables function when clicking on save button self.dlg.GeneralSettings_Restore.clicked.connect( self.gf.restore_general_variables) self.dlg.Isochrones_SaveSettings.clicked.connect( self.gf.store_isochrone_variables) self.dlg.Isochrones_RestoreDefaultSettings.clicked.connect( self.gf.restore_isochrone_variables) self.dlg.Isochrones_Now.clicked.connect( self.gf.set_datetime_now_isochrone) self.dlg.AggregatedIsochrones_SaveSettings.clicked.connect( self.gf.store_aggregated_isochrone_variables) self.dlg.AggregatedIsochrones_RestoreDefaultSettings.clicked.connect( self.gf.restore_aggregated_isochrone_variables) self.dlg.AggregatedIsochrones_Now.clicked.connect( self.gf.set_datetime_now_aggregated_isochrone) self.dlg.Routes_SaveSettings.clicked.connect( self.gf.store_route_variables) self.dlg.Routes_RestoreDefaultSettings.clicked.connect( self.gf.restore_route_variables) self.dlg.Routes_Now.clicked.connect(self.gf.set_datetime_now_route) # Calling Functions to update layer stuff when layerselection has changed self.dlg.Isochrones_SelectInputLayer.currentIndexChanged.connect( self.gf.isochrones_maplayerselection ) # Call function isochrones_maplayerselection to update all selection related stuff when selection has been changed self.dlg.AggregatedIsochrones_SelectInputLayer.currentIndexChanged.connect( self.gf.aggregated_isochrones_maplayerselection) self.dlg.Routes_SelectInputLayer_Source.currentIndexChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_SelectInputLayer_Target.currentIndexChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_SelectInputField_Source.currentIndexChanged.connect( self.gf.routes_maplayerselection) # or "fieldChanged"? self.dlg.Routes_SelectInputField_Target.currentIndexChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_DataDefinedLayer_Source.stateChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_DataDefinedLayer_Target.stateChanged.connect( self.gf.routes_maplayerselection) # Setting GUI stuff for startup every time the plugin is opened self.dlg.Isochrones_Date.setDateTime( QtCore.QDateTime.currentDateTime() ) # Set Dateselection to today on restart or firststart, only functional if never used save settings, otherwise overwritten by read_route_variables() self.dlg.AggregatedIsochrones_FromDateTime.setDateTime( QtCore.QDateTime.currentDateTime()) self.dlg.AggregatedIsochrones_ToDateTime.setDateTime( QtCore.QDateTime.currentDateTime()) self.dlg.Routes_Date.setDateTime(QtCore.QDateTime.currentDateTime()) self.dlg.Isochrones_ProgressBar.setValue( 0) # Set Progressbar to 0 on restart or first start self.dlg.AggregatedIsochrones_ProgressBar.setValue(0) self.dlg.Routes_ProgressBar.setValue(0) self.dlg.GeneralSettings_ServerStatusResult.setText( "Serverstatus Unknown") self.dlg.GeneralSettings_ServerStatusResult.setStyleSheet( "background-color: white; color: black ") # Functions to execute every time the plugin is opened self.gf.read_general_variables( ) #Run Read-Stored-Variables-Function on every start self.gf.read_isochrone_variables() self.gf.read_aggregated_isochrone_variables() self.gf.read_route_variables() # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: # Do something useful here - delete the line containing pass and # substitute with your code. QgsMessageLog.logMessage( "OpenTripPlanner Plugin is already running! Close it before, if you wish to restart it.", MESSAGE_CATEGORY, Qgis.Warning) self.iface.messageBar().pushMessage( "Error", "OpenTripPlanner Plugin is already running! Close it before, if you wish to restart it.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6)
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()
class LocatePointsDialog(QtWidgets.QDialog, FORM_CLASS): """Main dialog.""" def __init__(self, iface, parent=None): super(LocatePointsDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.in_combo.currentIndexChanged.connect(self.combo_changed) self.out_lyr.textChanged.connect(self.line_edit_text_changed) self.check_vertices.stateChanged.connect(self.checkbox_changed) self.run_button.clicked.connect(self.on_start) # Extra attributes self.in_name = False self.out_name = False # Extra thread attributes self.worker = None self.thread = None def on_start(self): self.pbar.setRange(0, 0) self.run_button.setEnabled(False) self.close_button.setEnabled(False) self.in_combo.setEnabled(False) self.out_lyr.setEnabled(False) self.offset.setEnabled(False) self.interval.setEnabled(False) self.check_attrs.setEnabled(False) self.check_vertices.setEnabled(False) self.check_endpoints.setEnabled(False) inlyr = self.in_combo.itemData(self.in_combo.currentIndex()) outlyr = self.out_lyr.text() offset = self.offset.value() interval = self.interval.value() keep_attrs = self.check_attrs.isChecked() add_ver = self.check_vertices.isChecked() add_end = self.check_endpoints.isChecked() self.worker = Worker(inlyr, outlyr, offset, interval, keep_attrs, add_ver, add_end) self.thread = QThread() self.worker.moveToThread(self.thread) self.worker.finished.connect(self.on_finished) self.thread.started.connect(self.worker.run) self.thread.start() def on_finished(self, error): vl = self.worker.vl self.worker.deleteLater() self.thread.quit() self.thread.wait() self.thread.deleteLater() self.pbar.setRange(0, 10) self.pbar.setValue(10) self.run_button.setEnabled(True) self.close_button.setEnabled(True) self.in_combo.setEnabled(True) self.out_lyr.setEnabled(True) self.offset.setEnabled(True) self.interval.setEnabled(True) self.check_attrs.setEnabled(True) self.check_vertices.setEnabled(True) if self.check_vertices.isChecked() is False: self.check_endpoints.setEnabled(True) if error: self.iface.messageBar().pushMessage('Failed to create points', '{}'.format(error), level=1) else: try: QgsProject.instance().addMapLayer(vl) except AttributeError: QgsMapLayerRegistry.instance().addMapLayer(vl) self.iface.messageBar().pushMessage('Calculations finished', 'Points successfully created!', level=0) def combo_changed(self, idx): if idx > 0: crs = self.in_combo.itemData(self.in_combo.currentIndex()).crs() units = QgsUnitTypes.toString(crs.mapUnits()) self.offset.setToolTip('Offset value ({})'.format(units)) self.interval.setToolTip('Interval value ({})'.format(units)) self.in_name = True if self.out_name is True: self.run_button.setEnabled(True) else: self.run_button.setEnabled(False) else: self.offset.setToolTip('') self.interval.setToolTip('') self.in_name = False self.run_button.setEnabled(False) def line_edit_text_changed(self, text): if text: self.out_name = True if self.in_name is True: self.run_button.setEnabled(True) else: self.run_button.setEnabled(False) else: self.out_name = False self.run_button.setEnabled(False) def checkbox_changed(self, state): if state == 2: self.check_endpoints.setChecked(0) self.check_endpoints.setEnabled(False) else: self.check_endpoints.setEnabled(True)