def __init__(self, files, needPrj): QThread.__init__(self, QThread.currentThread()) self.inFiles = files self.needPrj = needPrj self.mutex = QMutex() self.stopMe = 0
def runSearch(self): '''Called when the user pushes the Search button''' selectedLayer = self.layerListComboBox.currentIndex() comparisonMode = self.comparisonComboBox.currentIndex() self.noSelection = True try: sstr = self.findStringEdit.text().strip() except: self.showErrorMessage('Invalid Search String') return if str == '': self.showErrorMessage('Search string is empty') return if selectedLayer == 0: # Include all vector layers layers = QgsProject.instance().mapLayers().values() elif selectedLayer == 1: # Include all selected vector layers layers = self.iface.layerTreeView().selectedLayers() else: # Only search on the selected vector layer layers = [self.searchLayers[selectedLayer]] self.vlayers=[] # Find the vector layers that are to be searched for layer in layers: if isinstance(layer, QgsVectorLayer): self.vlayers.append(layer) if len(self.vlayers) == 0: self.showErrorMessage('There are no vector layers to search through') return # vlayers contains the layers that we will search in self.searchButton.setEnabled(False) self.stopButton.setEnabled(True) self.doneButton.setEnabled(False) self.clearButton.setEnabled(False) self.clearResults() self.resultsLabel.setText('') infield = self.searchFieldComboBox.currentIndex() >= 1 if infield is True: selectedField = self.searchFieldComboBox.currentText() else: selectedField = None # Because this could take a lot of time, set up a separate thread # for a worker function to do the searching. thread = QThread() worker = Worker(self.vlayers, infield, sstr, comparisonMode, selectedField, self.maxResults) worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(self.workerFinished) worker.foundmatch.connect(self.addFoundItem) worker.error.connect(self.workerError) self.thread = thread self.worker = worker self.noSelection = False thread.start()
def __init__(self, search_text, mutex, parent=None, geom_filter=None, status_filter=None): QThread.__init__(self, parent) self.search_text = search_text self.geom_filter = geom_filter self.status_filter = status_filter self.searcher = Client() self.searcher.set_proxy(*QGISSettings.get_qgis_proxy()) self.mutex = mutex self.img_cach = {} self.need_stop = False
def connection(self): """Creates and returns a spatialite connection, if the existing connection was created in another thread invalidates it and create a new one. """ if self._connection is None or self._current_thread != int(QThread.currentThreadId()): self._current_thread = int(QThread.currentThreadId()) try: self._connection = spatialite_connect(str(self.dbname)) except self.connection_error_types() as e: raise ConnectionError(e) return self._connection
def write(self, m): # This manage the case when console is called from another thread if QThread.currentThread() != QCoreApplication.instance().thread(): QMetaObject.invokeMethod(self, "write", Qt.QueuedConnection, Q_ARG(str, m)) return if self.style == "_traceback": # Show errors in red stderrColor = QColor(self.sO.settings.value("pythonConsole/stderrFontColor", QColor(self.ERROR_COLOR))) self.sO.SendScintilla(QsciScintilla.SCI_STYLESETFORE, 0o01, stderrColor) self.sO.SendScintilla(QsciScintilla.SCI_STYLESETITALIC, 0o01, True) self.sO.SendScintilla(QsciScintilla.SCI_STYLESETBOLD, 0o01, True) pos = self.sO.SendScintilla(QsciScintilla.SCI_GETCURRENTPOS) self.sO.SendScintilla(QsciScintilla.SCI_STARTSTYLING, pos, 31) self.sO.append(m) self.sO.SendScintilla(QsciScintilla.SCI_SETSTYLING, len(m), 0o01) else: self.sO.append(m) if self.out: self.out.write(m) self.move_cursor_to_end() if self.style != "_traceback": self.sO.repaint() if self.fire_keyboard_interrupt: self.fire_keyboard_interrupt = False raise KeyboardInterrupt
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 start_gnss(self): TOMsMessageLog.logMessage("In gnss_tool:start_gnss - GPS port is specified ", level=Qgis.Info) if self.gpsPort: self.gpsAvailable = True self.gpsConnection = None self.curr_gps_location = None self.curr_gps_info = None self.gps_thread = GPS_Thread(self.dest_crs, self.gpsPort) thread = QThread() self.gps_thread.moveToThread(thread) self.gps_thread.gpsActivated.connect(self.gpsStarted) self.gps_thread.gpsPosition.connect(self.gpsPositionProvided) self.gps_thread.gpsDeactivated.connect(self.gpsStopped) #self.gps_thread.gpsError.connect(self.gpsErrorEncountered) #objThread = QThread() #obj = SomeObject() #obj.moveToThread(objThread) #self.gps_thread.gpsDeactivated.connect(thread.quit) #obj.finished.connect(objThread.quit) #self.gps_thread.started.connect(self.startGPS) #objThread.started.connect(obj.long_running) #thread.finished.connect(self.gpsStopped) #objThread.finished.connect(app.exit) #thread.start() #objThread.start() # self.gps_thread.progress.connect(progressBar.setValue) thread.started.connect(self.gps_thread.startGPS) # thread.finished.connect(functools.partial(self.gpsStopped, thread)) thread.start() self.thread = thread TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems - attempting connection ", level=Qgis.Info)
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 qgis_excepthook(type, value, tb): # detect if running in the main thread in_main_thread = True if QThread.currentThread() != QgsApplication.instance().thread(): in_main_thread = False # only use messagebar if running in main thread - otherwise it will crash! showException(type, value, tb, None, messagebar=in_main_thread)
def showException(etype, value, tb, msg, *args, **kwargs): if QThread.currentThread() == qApp.thread(): # we can show the exception directly show_debug_widget((etype,value,tb)) else: # we need to pass the exception details to main thread - we can't do GUI stuff here deferred_dw_handler.debug_widget_data = (etype,value,tb) QMetaObject.invokeMethod(deferred_dw_handler, "start_deferred", Qt.QueuedConnection)
def qgis_excepthook(type, value, tb): # detect if running in the main thread in_main_thread = QCoreApplication.instance( ) is None or QThread.currentThread() == QCoreApplication.instance().thread( ) # only use messagebar if running in main thread - otherwise it will crash! showException(type, value, tb, None, messagebar=in_main_thread)
class BackgroundLayerDownload(QObject): finished = pyqtSignal('PyQt_PyObject', str) def __init__(self, server, user, repo, layername, commitid, filepath, extent): QObject.__init__(self) self.server = server self.user = user self.repo = repo self.layername = layername self.commitid = commitid self.filepath = filepath self.extent = extent self.nfeaturesRead = 0 self.actualLayerName = None # this puts the LayerGetter in its own thread self.worker = QThread() self.layer_getter = LayerGetter(server, user, repo, layername, commitid, filepath, extent) self.worker.started.connect(self.layer_getter.start) self.layer_getter.moveToThread(self.worker) self.layer_getter.completed.connect(self.completed) self.layer_getter.progress_occurred.connect(self.featuresRead) def start(self): startProgressBar("Downloading from GeoGig server", 0, currentWindow().messageBar()) self.worker.start() # start load # called (on main thread) when the work is done def completed(self): self.worker.quit() # get rid of worker thread closeProgressBar() self.actualLayerName = self.layer_getter.actualLayerName layer = QgsVectorLayer( self.filepath + "|layername=" + self.actualLayerName, self.actualLayerName) self.finished.emit(layer, self.filepath) # called as feature are read def featuresRead(self, nfeatsBatch): self.nfeaturesRead += nfeatsBatch setProgressText("Downloaded " + "{:,}".format(self.nfeaturesRead) + " features...")
def __init__(self, iface, parent=None): super(Photo2ShapeDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.settings = QgsSettings("alexbruy", "photo2shape") self.fwPhotosPath.setStorageMode(QgsFileWidget.GetDirectory) self.fwPhotosPath.setDialogTitle(self.tr("Select directory")) self.fwPhotosPath.setDefaultRoot( self.settings.value("lastPhotosDirectory", os.path.expanduser("~"), str)) self.fwPhotosPath.fileChanged.connect(self.updateLastPhotosPath) self.fwOutputShape.setStorageMode(QgsFileWidget.SaveFile) self.fwOutputShape.setConfirmOverwrite(True) self.fwOutputShape.setDialogTitle(self.tr("Select file")) self.fwOutputShape.setDefaultRoot( self.settings.value("lastShapeDirectory", QgsProject.instance().homePath(), str)) self.fwOutputShape.setFilter(self.tr("ESRI Shapefile (*.shp *.SHP)")) self.fwOutputShape.fileChanged.connect(self.updateLastShapePath) self.thread = QThread() self.importer = PhotoImporter() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.importer.moveToThread(self.thread) self.importer.importError.connect(self.thread.quit) self.importer.importError.connect(self.importCanceled) self.importer.importMessage.connect(self.logMessage) self.importer.importFinished.connect(self.thread.quit) self.importer.importFinished.connect(self.importCompleted) self.importer.photoProcessed.connect(self.updateProgress) self.thread.started.connect(self.importer.importPhotos) self.encoding = self.settings.value("encoding", "utf-8", str) self.chkRecurse.setChecked(self.settings.value("recurse", True, bool)) self.chkAppend.setChecked(self.settings.value("append", True, bool)) self.chkLoadLayer.setChecked( self.settings.value("loadLayer", True, bool))
def run(self): # TODO Verificar a lógica. stopMonitonitor is not called after the emit # TODO Verificar a lógica. UpdateTickTimer não é chamado no primeiro pomodoro while self.running: if self.hasChangedCanvas: self.hasChangedCanvas = False self.updateWorkTime() self.updateTickTimer.emit() print('Updating work time!') QThread.sleep(60) elif not self.hasChangedCanvas and not self.isMonitoring: self.updateIdleTime() self.updateTickTimer.emit() print('Updating idle time!') QThread.sleep(60) elif not self.hasChangedCanvas and self.isMonitoring: self.stopMonitoring() self.updateByMonitor.emit()
def start_worker(worker, message_bar, message): """ Configure the QgsMessageBar with a :guilabel:`Cancel` button and start the worker in a new thread :param worker: the worker to be started :param message_bar: the message bar to be used to display progress :param message: a message describing the task to be performed """ # configure the QgsMessageBar message_bar_item = message_bar.createMessage(message) progress_bar = QProgressBar() progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) cancel_button = QPushButton() cancel_button.setText('Cancel') cancel_button.clicked.connect(worker.kill) message_bar_item.layout().addWidget(progress_bar) message_bar_item.layout().addWidget(cancel_button) message_bar.pushWidget(message_bar_item, message_bar.INFO) # start the worker in a new thread thread = QThread(message_bar.parent()) worker.moveToThread(thread) worker.set_message.connect( lambda message: set_worker_message(message, message_bar_item)) worker.toggle_show_progress.connect( lambda show: toggle_worker_progress(show, progress_bar)) worker.toggle_show_cancel.connect( lambda show: toggle_worker_cancel(show, cancel_button)) worker.finished.connect(lambda result: worker_finished( result, thread, worker, message_bar, message_bar_item)) worker.error.connect( lambda e, exception_str: worker_error(e, exception_str, message_bar)) worker.progress.connect(progress_bar.setValue) thread.started.connect(worker.run) thread.start() return thread, message_bar_item
def __init__(self, server, user, repo, layername, commitid, filepath, extent): QObject.__init__(self) self.server = server self.user = user self.repo = repo self.layername = layername self.commitid = commitid self.filepath = filepath self.extent = extent self.nfeaturesRead = 0 self.actualLayerName = None # this puts the LayerGetter in its own thread self.worker = QThread() self.layer_getter = LayerGetter(server, user, repo, layername, commitid, filepath, extent) self.worker.started.connect(self.layer_getter.start) self.layer_getter.moveToThread(self.worker) self.layer_getter.completed.connect(self.completed) self.layer_getter.progress_occurred.connect(self.featuresRead)
def __init__(self, iface): self.iface = iface self.canvas = self.iface.mapCanvas() threadcount = QThread.idealThreadCount() # use all available cores and parallel rendering QgsApplication.setMaxThreads(threadcount) QSettings().setValue("/qgis/parallel_rendering", True) # OpenCL acceleration QSettings().setValue("/core/OpenClEnabled", True) self.orbitalViewer = None self.server = None self.make_server()
def runAnalysis(self): self.dlg.analysisProgress.reset() # Create an analysis instance self.settings = self.getAnalysisSettings() if self.settings != {} and self.settings is not None: analysis = ca.CatchmentAnalysis(self.iface, self.settings) # Create new thread and move the analysis class to it self.dlg.lockGUI(True) analysis_thread = QThread() analysis.moveToThread(analysis_thread) # Setup signals analysis.finished.connect(self.analysisFinish) analysis.error.connect(self.analysisError) analysis.warning.connect(self.giveWarningMessage) analysis.progress.connect(self.dlg.analysisProgress.setValue) # Start analysis analysis_thread.started.connect(analysis.analysis) analysis_thread.start() self.analysis_thread = analysis_thread self.analysis = analysis
def __init__(self, terreno=None, st=None, progressiva=None, prism=None, ati=3, cti=3): prismoide.__init__(self) QThread.__init__(self) if prism is None: self.terreno = terreno self.st = st self.progressiva = progressiva self.cti = cti self.ati = ati self.lastGeneratedIndex = 0 sq = square() for i in range(0, len(self.terreno)): c = sq.copy() f = face() f.fromClosedCurve(c) f.setPos( point(c.position.x(), c.position.y(), self.progressiva[i])) self.appendFace(f) self.start() elif type(prism) is prismoide: self.fromFaces(prism.faces) self.lastGeneratedIndex = len(prism.faces) - 1 self.progressiva = prism.progressiva self.terreno = prism.terreno self.st = prism.st self.ati = prism.ati self.cti = prism.ati
def loadAsync(self): """ Asynchronously loads an entity's attribute values. """ self.asyncStarted.emit() # Create model worker workerThread = QThread(self) modelWorker = ModelWorker() modelWorker.moveToThread(workerThread) # Connect signals modelWorker.error.connect(self.errorHandler) workerThread.started.connect(lambda: modelWorker.fetch( self.config.STRModel, self.currentFieldName())) modelWorker.retrieved.connect(self._asyncFinished) modelWorker.retrieved.connect(workerThread.quit) workerThread.finished.connect(modelWorker.deleteLater) workerThread.finished.connect(workerThread.deleteLater) # Start thread workerThread.start()
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 start_worker(worker, iface, message, with_progress=True): # configure the QgsMessageBar message_bar_item = iface.messageBar().createMessage(message) progress_bar = QProgressBar() progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) if not with_progress: progress_bar.setMinimum(0) progress_bar.setMaximum(0) cancel_button = QPushButton() cancel_button.setText('Cancel') cancel_button.clicked.connect(worker.kill) message_bar_item.layout().addWidget(progress_bar) message_bar_item.layout().addWidget(cancel_button) iface.messageBar().pushWidget(message_bar_item, Qgis.Info) # start the worker in a new thread # let Qt take ownership of the QThread thread = QThread(iface.mainWindow()) worker.moveToThread(thread) worker.set_message.connect(lambda message: set_worker_message( message, message_bar_item)) worker.toggle_show_progress.connect(lambda show: toggle_worker_progress( show, progress_bar)) worker.toggle_show_cancel.connect(lambda show: toggle_worker_cancel( show, cancel_button)) worker.finished.connect(lambda result: worker_finished( result, thread, worker, iface, message_bar_item)) worker.error.connect(lambda e: worker_error(e)) worker.was_killed.connect(lambda result: worker_killed( result, thread, worker, iface, message_bar_item)) worker.progress.connect(progress_bar.setValue) thread.started.connect(worker.run) thread.start() return thread, message_bar_item
def __init__(self, parentThread, parentObject, settings, axial, uid, unlinks): QThread.__init__(self, parentThread) self.parent = parentObject self.running = False self.verification_settings = settings self.axial_layer = axial self.unlinks_layer = unlinks self.user_id = uid # verification globals self.problem_nodes = [] # error types to identify: self.axial_errors = { 'orphan': [], 'island': [], 'short line': [], 'invalid geometry': [], 'polyline': [], 'coinciding points': [], 'small line': [], 'duplicate geometry': [], 'overlap': [] }
def __init__(self, iface, parent=None): super(Photo2ShapeDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.settings = QgsSettings("alexbruy", "photo2shape") self.fwPhotosPath.setStorageMode(QgsFileWidget.GetDirectory) self.fwPhotosPath.setDialogTitle(self.tr("Select directory")) self.fwPhotosPath.setDefaultRoot(self.settings.value("lastPhotosDirectory", os.path.expanduser("~"), str)) self.fwPhotosPath.fileChanged.connect(self.updateLastPhotosPath) self.fwOutputShape.setStorageMode(QgsFileWidget.SaveFile) self.fwOutputShape.setConfirmOverwrite(True) self.fwOutputShape.setDialogTitle(self.tr("Select file")) self.fwOutputShape.setDefaultRoot(self.settings.value("lastShapeDirectory", QgsProject.instance().homePath(), str)) self.fwOutputShape.setFilter(self.tr("ESRI Shapefile (*.shp *.SHP)")) self.fwOutputShape.fileChanged.connect(self.updateLastShapePath) self.thread = QThread() self.importer = PhotoImporter() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.importer.moveToThread(self.thread) self.importer.importError.connect(self.thread.quit) self.importer.importError.connect(self.importCanceled) self.importer.importMessage.connect(self.logMessage) self.importer.importFinished.connect(self.thread.quit) self.importer.importFinished.connect(self.importCompleted) self.importer.photoProcessed.connect(self.updateProgress) self.thread.started.connect(self.importer.importPhotos) self.encoding = self.settings.value("encoding", "utf-8", str) self.chkRecurse.setChecked(self.settings.value("recurse", True, bool)) self.chkAppend.setChecked(self.settings.value("append", True, bool)) self.chkLoadLayer.setChecked(self.settings.value("loadLayer", True, bool))
def __init__(self, parent=None): super(StatistDialog, self).__init__(parent) self.setupUi(self) self.thread = QThread() self.calculator = StatisticsCalculator() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.calculator.moveToThread(self.thread) self.calculator.calculated.connect(self.thread.quit) self.calculator.calculated.connect(self.processFinished) self.thread.started.connect(self.calculator.calculate) self.cmbLayer.setFilters(QgsMapLayerProxyModel.VectorLayer) self.cmbField.setLayer(self.cmbLayer.currentLayer()) self.cmbLayer.layerChanged.connect(self.resetGui) self.cmbLayer.layerChanged.connect(self.cmbField.setLayer) self._resetPlot()
def get_valid_mime_uri(layer_name, uri, wkb_type): """ Gross method to force a valid layer path, only used for very old QGIS versions """ if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.NullGeometry: layer_type = QgsMapLayer.RasterLayer else: layer_type = QgsMapLayer.VectorLayer if QThread.currentThread() == QCoreApplication.instance().thread(): from .datasourceselectdialog import DataSourceSelectDialog # pylint: disable=import-outside-toplevel dlg = DataSourceSelectDialog(layer_name=layer_name, original_uri=uri, layer_type=layer_type) if dlg.exec_(): return dlg.uri # use default dummy path - QGIS 3.4 will crash on invalid layer sources otherwise uri = QgsMimeDataUtils.Uri() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PointGeometry: file = 'dummy_points.shp' elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: file = 'dummy_lines.shp' elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: file = 'dummy_polygon.shp' elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.NullGeometry: file = 'dummy_raster.tif' else: # ??? file = 'dummy_points.shp' path = os.path.dirname(os.path.abspath(__file__)) uri.uri = os.path.realpath(os.path.join(path, '..', '..', file)).replace('\\', '/') uri.providerKey = 'ogr' return uri
def stop(self): self.mutex.lock() self.stopMe = 1 self.mutex.unlock() QThread.wait(self)
def accept(self): try: nodata = self.ui.IDC_tbNoDataExport.text() self.settings.nodata_value = int(nodata) if nodata != "" else None QgsMessageLog.logMessage("Maindlg: nodata: {0}".format(self.settings.nodata_value), "VoGis", Qgis.Info) if self.settings.onlyHektoMode is True and self.settings.mapData.rasters.count() > 0: self.settings.onlyHektoMode = False if self.settings.onlyHektoMode is False: if self.settings.mapData.rasters.count() < 1: retVal = QMessageBox.warning(self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Keine Rasterebene vorhanden oder sichtbar! Nur hektometrieren?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if retVal == QMessageBox.No: return else: self.settings.onlyHektoMode = True self.settings.createHekto = True if self.__getSettingsFromGui() is False: return if self.settings.onlyHektoMode is False: if len(self.settings.mapData.rasters.selectedRasters()) < 1: QMessageBox.warning(self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Kein Raster selektiert!")) return QgsMessageLog.logMessage("modeLine!=line: {0}".format(self.settings.modeLine != enumModeLine.line), "VoGis", Qgis.Info) QgsMessageLog.logMessage("customLine is None: {0}".format(self.settings.mapData.customLine is None), "VoGis", Qgis.Info) if self.settings.modeLine != enumModeLine.line and self.settings.mapData.customLine is None: QMessageBox.warning(self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Keine Profillinie vorhanden!")) return if len(self.settings.mapData.polygons.selected_polygons()) > 0 and len(self.settings.mapData.rasters.selectedRasters()) > 1: raster_names = list(raster.name for raster in self.settings.mapData.rasters.selectedRasters()) sel_raster, ok_clicked = QInputDialog.getItem( self.iface.mainWindow(), "DHM?", "Welches DHM soll zur Flächenverschneidung verwendet werden?", raster_names, 0, False ) if ok_clicked is False: return self.settings.intersection_dhm_idx = raster_names.index(sel_raster) QApplication.setOverrideCursor(Qt.WaitCursor) create_profile = CreateProfile(self.iface, self.settings) thread = QThread(self) create_profile.moveToThread(thread) create_profile.finished.connect(self.profiles_finished) create_profile.error.connect(self.profiles_error) create_profile.progress.connect(self.profiles_progress) thread.started.connect(create_profile.create) thread.start(QThread.LowestPriority) self.thread = thread self.create_profile = create_profile self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) except: QApplication.restoreOverrideCursor() ex = "{0}".format(traceback.format_exc()) msg = "Unexpected ERROR:\n\n{0}".format(ex[:2000]) QMessageBox.critical(self.iface.mainWindow(), "VoGIS-Profiltool", msg)
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)
def __init__(self, parameters, stl_file, dem_matrix): QThread.__init__(self) self.parameters = parameters self.stl_file = stl_file self.matrix_dem = dem_matrix self.quit = False
class ProjectUpdater(QObject): """ Object to handle reporting when new versions of projects are found on the update server. Emits foundProjects when a new projects are found """ foundProjects = pyqtSignal(list, list) projectUpdateStatus = pyqtSignal(str, str) projectInstalled = pyqtSignal(str) def __init__(self, server=None, projects_base=''): super(ProjectUpdater, self).__init__() self.updatethread = None self.server = server self.net = QgsNetworkAccessManager.instance() self.projects_base = projects_base self.create_worker() def quit(self): self.updatethread.quit() def create_worker(self): self.updatethread = QThread() self.worker = UpdateWorker(self.projects_base) self.worker.moveToThread(self.updatethread) self.worker.projectUpdateStatus.connect(self.projectUpdateStatus) self.worker.projectInstalled.connect(self.projectInstalled) self.updatethread.started.connect(self.worker.run) def configurl(self): url = urljoin(add_slash(self.server), "projects/roam.txt") print("URL", url) return url def check_updates(self, server, installedprojects): if not server: return self.server = server req = QNetworkRequest(QUrl(self.configurl())) reply = self.net.get(req) reply.finished.connect( functools.partial(self.list_versions, reply, installedprojects)) def update_server(self, newserverurl, installedprojects): self.check_updates(newserverurl, installedprojects) def list_versions(self, reply, installedprojects): if reply.error() == QNetworkReply.NoError: content = reply.readAll().data() serverversions = parse_serverprojects(content) updateable = list( updateable_projects(installedprojects, serverversions)) new = list(new_projects(installedprojects, serverversions)) print(updateable) print(new) if updateable or new: self.foundProjects.emit(updateable, new) else: msg = "Error in network request for projects: {}".format( reply.errorString()) roam.utils.warning(msg) def update_project(self, projectinfo): self.projectUpdateStatus.emit(projectinfo['name'], "Pending") forupdate.put( (projectinfo, projectinfo['version'], self.server, False)) self.updatethread.start() def install_project(self, projectinfo): self.projectUpdateStatus.emit(projectinfo['name'], "Pending") is_new = True forupdate.put( (projectinfo, projectinfo['version'], self.server, is_new)) self.updatethread.start()
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()
def __init__(self, func): QThread.__init__(self, config.iface.mainWindow()) self.func = func self.returnValue = [] self.exception = None
class StatistDialog(BASE, WIDGET): def __init__(self, parent=None): super(StatistDialog, self).__init__(parent) self.setupUi(self) self.thread = QThread() self.calculator = StatisticsCalculator() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.calculator.moveToThread(self.thread) self.calculator.calculated.connect(self.thread.quit) self.calculator.calculated.connect(self.processFinished) self.thread.started.connect(self.calculator.calculate) self.cmbLayer.setFilters(QgsMapLayerProxyModel.VectorLayer) self.cmbField.setLayer(self.cmbLayer.currentLayer()) self.cmbLayer.layerChanged.connect(self.resetGui) self.cmbLayer.layerChanged.connect(self.cmbField.setLayer) self._resetPlot() def resetGui(self, layer): self._resetPlot() self.tblStatistics.setRowCount(0) if layer.selectedFeatureCount() != 0: self.chkSelectedOnly.setChecked(True) else: self.chkSelectedOnly.setChecked(False) def accept(self): self.tblStatistics.setRowCount(0) layer = self.cmbLayer.currentLayer() if self.chkSelectedOnly.isChecked() and layer.selectedFeatureCount() == 0: QgsMessageBar.pushWarning(self.tr("No selection"), self.tr("There is no selection in the input layer. Uncheck " "corresponding option or select some features before " "running analysis")) return self.calculator.setLayer(layer) self.calculator.setField(self.cmbField.currentField()) self.calculator.setSelectedOnly(self.chkSelectedOnly.isChecked()) self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) self.thread.start() def reject(self): QDialog.reject(self) def processFinished(self): rowCount = len(self.calculator.data) self.tblStatistics.setRowCount(rowCount) for i in range(rowCount): tmp = self.calculator.data[i].split(":") item = QTableWidgetItem(tmp[0]) self.tblStatistics.setItem(i, 0, item) item = QTableWidgetItem(tmp[1]) self.tblStatistics.setItem(i, 1, item) self.refreshPlot() self.btnOk.setEnabled(True) self.btnClose.setEnabled(True) def refreshPlot(self): if len(self.calculator.values) == 0: return # use correct axis formatter field = self.cmbLayer.currentLayer().fields().field(self.cmbField.currentField()) if field.type() == QVariant.Date: values = [datetime(d.year, d.month, d.day) if d else None for d in self.calculator.values] elif field.type() == QVariant.DateTime: values = self.calculator.values elif field.type() == QVariant.Time: values = [datetime.strptime(t.isoformat(), "%H:%M:%S") if t else None for t in self.calculator.values] else: values = self.calculator.values self.mplWidget.axes.hist(values, "auto", alpha=0.5, histtype="bar", color="#006BA4") self.mplWidget.setXAxisCaption(self.cmbField.currentText()) self.mplWidget.setYAxisCaption(self.tr("Count")) self.mplWidget.alignLabels() self.mplWidget.canvas.draw() def keyPressEvent(self, event): if event.modifiers() in [Qt.ControlModifier, Qt.MetaModifier] and event.key() == Qt.Key_C: clipboard = QApplication.clipboard() clipboard.setText("\n".join(self.calculator.data)) else: QDialog.keyPressEvent(self, event) def _resetPlot(self): self.mplWidget.clear() self.mplWidget.setTitle(self.tr("Frequency distribution"))
def startWorker(self): """Initialises and starts the worker thread.""" try: layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) if joinlayer is None: self.showError(self.tr('No join layer defined')) return if joinlayer is not None and joinlayer.crs().isGeographic(): self.showWarning('Geographic CRS used for the join layer -' ' distances will be in decimal degrees!') outputlayername = self.outputDataset.text() approximateinputgeom = self.approximate_input_geom_cb.isChecked() joinprefix = self.joinPrefix.text() # useindex = True useindex = self.use_index_nonpoint_cb.isChecked() useindexapproximation = self.use_indexapprox_cb.isChecked() distancefieldname = self.distancefieldname.text() selectedinputonly = self.inputSelected.isChecked() selectedjoinonly = self.joinSelected.isChecked() excludecontaining = self.exclude_containing_poly_cb.isChecked() # create a new worker instance self.worker = Worker(inputlayer, joinlayer, outputlayername, joinprefix, distancefieldname, approximateinputgeom, useindexapproximation, useindex, selectedinputonly, selectedjoinonly, excludecontaining) # configure the QgsMessageBar msgBar = self.iface.messageBar().createMessage( self.tr('Joining'), '') self.aprogressBar = QProgressBar() self.aprogressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) acancelButton = QPushButton() acancelButton.setText(self.CANCEL) # acancelButton.clicked.connect(self.killWorker) msgBar.layout().addWidget(self.aprogressBar) msgBar.layout().addWidget(acancelButton) # Has to be popped after the thread has finished (in # workerFinished). self.iface.messageBar().pushWidget(msgBar, Qgis.Info) # self.iface.messageBar().INFO) self.messageBar = msgBar # start the worker in a new thread self.mythread = QThread(self) # QT requires the "self" self.worker.status.connect(self.workerInfo) self.worker.progress.connect(self.progressBar.setValue) self.worker.progress.connect(self.aprogressBar.setValue) self.worker.finished.connect(self.workerFinished) self.worker.error.connect(self.workerError) # Must come before movetothread: self.cancelButton.clicked.connect(self.worker.kill) acancelButton.clicked.connect(self.worker.kill) self.worker.finished.connect(self.worker.deleteLater) self.worker.finished.connect(self.mythread.quit) # self.worker.error.connect(self.worker.deleteLater) # self.worker.error.connect(self.mythread.quit) # Must come before thread.started.connect!: self.worker.moveToThread(self.mythread) self.mythread.started.connect(self.worker.run) self.mythread.finished.connect(self.mythread.deleteLater) self.mythread.start() # self.thread = thread # self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) if layerId == joinlayerId: self.showInfo("The join layer is the same as the" " input layer - doing a self join!") except: import traceback self.showError("Error starting worker: " + traceback.format_exc()) else: pass
class SearchPlugin(widget, base, Page): title = "Search" icon = resolve("search.svg") def __init__(self, api, parent=None): super(SearchPlugin, self).__init__(parent) self.setupUi(self) self.api = api self.project = None self.dbpath = None self.searchbox.textChanged.connect(self.search) self.searchbox.installEventFilter(self) self.clearButton.pressed.connect(self.searchbox.clear) self.resultsView.itemClicked.connect(self.jump_to) self.rebuildLabel.linkActivated.connect(self.rebuild_index) self.fuzzyCheck.stateChanged.connect(self.fuzzy_changed) self.indexbuilder = None self.indexthread = None roam.api.utils.install_touch_scroll(self.resultsView) def fuzzy_changed(self, state): self.search(self.searchbox.text()) def index_built(self, dbpath, timing): self.dbpath = dbpath self.resultsView.clear() self.searchbox.setEnabled(True) print("Index built in: {} seconds".format(timing)) def eventFilter(self, object, event): if event.type() == QEvent.FocusIn: RoamEvents.openkeyboard.emit() return False @property def db(self): db = sqlite3.connect(self.dbpath) db.create_function("rank", 1, make_rank_func((1., .1, 0, 0))) return db def project_loaded(self, project): self.project = project self.build_index(project) def rebuild_index(self): self.build_index(self.project) def build_index(self, project): self.searchbox.setEnabled(False) self.resultsView.setEnabled(False) self.resultsView.addItem("building search index...") validformat, settings = valid_search_settings(project.settings) if not validformat: RoamEvents.raisemessage("Searching", "Invalid search config.", level=1) self.searchbox.hide() self.resultsView.clear() self.resultsView.addItem("Invalid search config found") return self.indexthread = QThread() path = os.path.join(os.environ['APPDATA'], "roam", project.name) roam.utils.info("Search index path: {0}".format(path)) if not os.path.exists(path): os.makedirs(path) self.indexbuilder = IndexBuilder(path, settings) self.indexbuilder.moveToThread(self.indexthread) QgsProject.instance().removeAll.connect(self.indexthread.quit) self.indexbuilder.indexBuilt.connect(self.index_built) self.indexbuilder.finished.connect(self.indexthread.quit) self.indexthread.started.connect(self.indexbuilder.build_index) self.indexthread.finished.connect(self.indexbuilder.quit) self.indexthread.start() def search(self, text): db = self.db c = db.cursor() self.resultsView.clear() self.resultsView.setEnabled(False) if not text: return if self.fuzzyCheck.isChecked(): search = "* ".join(text.split()) + "*" else: search = text query = c.execute( """SELECT layer, featureid, snippet(search, '[',']') as snippet FROM search JOIN featureinfo on search.docid = featureinfo.id WHERE search match '{}' LIMIT 100""".format( search)).fetchall() for layer, featureid, snippet in query: item = QListWidgetItem() text = "{}\n {}".format(layer, snippet.replace('\n', ' ')) item.setText(text) item.setData(Qt.UserRole + 1, (layer, featureid, snippet)) self.resultsView.addItem(item) self.resultsView.setEnabled(True) if self.resultsView.count() == 0: self.resultsView.addItem("No Results") self.resultsView.setEnabled(False) db.close() def jump_to(self, item): data = item.data(Qt.UserRole + 1) if not data: return layername, fid = data[0], data[1] layer = roam.api.utils.layer_by_name(layername) feature = next(layer.getFeatures(QgsFeatureRequest(fid))) self.api.mainwindow.showmap() self.api.mainwindow.canvas.zoomToFeatureIds(layer, [fid]) RoamEvents.selectionchanged.emit({layer: [feature]})
def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.snapping = True icon = roam_style.iconsize() self.projecttoolbar.setIconSize(QSize(icon, icon)) self.defaultextent = None self.current_form = None self.last_form = None self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.bridge = QgsLayerTreeMapCanvasBridge( QgsProject.instance().layerTreeRoot(), self.canvas) self.bridge.setAutoSetupOnFirstLayer(False) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.snappingutils = SnappingUtils(self.canvas, self) self.canvas.setSnappingUtils(self.snappingutils) threadcount = QThread.idealThreadCount() threadcount = 2 if threadcount > 2 else 1 QgsApplication.setMaxThreads(threadcount) self.canvas.setParallelRenderingEnabled(True) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) if roam.config.settings.get('north_arrow', False): self.northarrow = NorthArrow(":/icons/north", self.canvas) self.northarrow.setPos(10, 10) self.canvas.scene().addItem(self.northarrow) smallmode = roam.config.settings.get("smallmode", False) self.projecttoolbar.setSmallMode(smallmode) self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget( self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction( self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.gpsMarker = GPSMarker(self.canvas) self.gpsMarker.hide() self.currentfeatureband = CurrentSelection(self.canvas) self.currentfeatureband.setIconSize(30) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(88, 64, 173, 50)) self.currentfeatureband.setOutlineColour(QColor(88, 64, 173)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(165, 111, 212, 75)) self.gpsband.setWidth(5) RoamEvents.refresh_map.connect(self.refresh_map) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.openfeatureform.connect(self.feature_form_loaded) RoamEvents.sync_complete.connect(self.refresh_map) RoamEvents.snappingChanged.connect(self.snapping_changed) self.snappingbutton = QToolButton() self.snappingbutton.setText("Snapping: On") self.snappingbutton.setAutoRaise(True) self.snappingbutton.pressed.connect(self.toggle_snapping) spacer = QWidget() spacer2 = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.scalewidget = QgsScaleComboBox() self.scalebutton = QToolButton() self.scalebutton.setAutoRaise(True) self.scalebutton.setMaximumHeight(self.statusbar.height()) self.scalebutton.pressed.connect(self.selectscale) self.scalebutton.setText("Scale") self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False) self.scalelist.hide() self.scalelist.setlabel("Map Scale") self.scalelist.setmodel(self.scalewidget.model()) self.scalelist.closewidget.connect(self.scalelist.close) self.scalelist.itemselected.connect(self.update_scale_from_item) self.scalelist.itemselected.connect(self.scalelist.close) self.positionlabel = QLabel('') self.gpslabel = QLabel("GPS: Not active") self.gpslabelposition = QLabel("") self.statusbar.addWidget(self.snappingbutton) self.statusbar.addWidget(spacer2) self.statusbar.addWidget(self.gpslabel) self.statusbar.addWidget(self.gpslabelposition) self.statusbar.addPermanentWidget(self.scalebutton) self.canvas.extentsChanged.connect(self.update_status_label) self.canvas.scaleChanged.connect(self.update_status_label) self.connectButtons() scalebar_enabled = roam.config.settings.get('scale_bar', False) self.scalebar_enabled = False if scalebar_enabled: roam.utils.warning( "Unsupported feature: Scale bar support not ported to QGIS 3 API yet." ) RoamEvents.raisemessage( "Unsupported feature", "Scale bar support not ported to QGIS 3 API yet", level=RoamEvents.CRITICAL) self.scalebar_enabled = False
class NNJoinDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" self.iface = iface self.plugin_dir = dirname(__file__) # Some translated text (to enable reuse) self.NNJOIN = self.tr('NNJoin') self.CANCEL = self.tr('Cancel') self.CLOSE = self.tr('Close') self.HELP = self.tr('Help') self.OK = self.tr('OK') super(NNJoinDialog, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html#\ # widgets-and-dialogs-with-auto-connect self.setupUi(self) # Modify ui components okButton = self.button_box.button(QDialogButtonBox.Ok) okButton.setText(self.OK) self.cancelButton = self.button_box.button(QDialogButtonBox.Cancel) self.cancelButton.setText(self.CANCEL) closeButton = self.button_box.button(QDialogButtonBox.Close) closeButton.setText(self.CLOSE) self.approximate_input_geom_cb.setCheckState(Qt.Unchecked) self.approximate_input_geom_cb.setVisible(False) stytxt = "QCheckBox:checked {color: red; background-color: white}" self.approximate_input_geom_cb.setStyleSheet(stytxt) self.use_indexapprox_cb.setCheckState(Qt.Unchecked) self.use_indexapprox_cb.setVisible(False) self.use_indexapprox_cb.setStyleSheet(stytxt) self.use_index_nonpoint_cb.setCheckState(Qt.Unchecked) self.use_index_nonpoint_cb.setVisible(False) self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False) # Help button helpButton = self.helpButton helpButton.setText(self.HELP) # Connect signals okButton.clicked.connect(self.startWorker) # self.cancelButton.clicked.connect(self.killWorker) closeButton.clicked.connect(self.reject) helpButton.clicked.connect(self.help) self.approximate_input_geom_cb.stateChanged['int'].connect( self.useindexchanged) self.use_indexapprox_cb.stateChanged['int'].connect( self.useindexchanged) self.use_index_nonpoint_cb.stateChanged['int'].connect( self.useindexchanged) inpIndexCh = self.inputVectorLayer.currentIndexChanged['QString'] inpIndexCh.connect(self.layerchanged) joinIndexCh = self.joinVectorLayer.currentIndexChanged['QString'] # joinIndexCh.connect(self.layerchanged) joinIndexCh.connect(self.joinlayerchanged) # self.distancefieldname.editingFinished.connect(self.fieldchanged) self.distancefieldname.textChanged.connect(self.distfieldchanged) self.joinPrefix.editingFinished.connect(self.fieldchanged) theRegistry = QgsProject.instance() theRegistry.layersAdded.connect(self.layerlistchanged) theRegistry.layersRemoved.connect(self.layerlistchanged) # Disconnect the cancel button to avoid exiting. self.button_box.rejected.disconnect(self.reject) # Set instance variables self.mem_layer = None self.worker = None self.inputlayerid = None self.joinlayerid = None self.layerlistchanging = False def startWorker(self): """Initialises and starts the worker thread.""" try: layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) if joinlayer is None: self.showError(self.tr('No join layer defined')) return if joinlayer is not None and joinlayer.crs().isGeographic(): self.showWarning('Geographic CRS used for the join layer -' ' distances will be in decimal degrees!') outputlayername = self.outputDataset.text() approximateinputgeom = self.approximate_input_geom_cb.isChecked() joinprefix = self.joinPrefix.text() # useindex = True useindex = self.use_index_nonpoint_cb.isChecked() useindexapproximation = self.use_indexapprox_cb.isChecked() distancefieldname = self.distancefieldname.text() selectedinputonly = self.inputSelected.isChecked() selectedjoinonly = self.joinSelected.isChecked() excludecontaining = self.exclude_containing_poly_cb.isChecked() # create a new worker instance self.worker = Worker(inputlayer, joinlayer, outputlayername, joinprefix, distancefieldname, approximateinputgeom, useindexapproximation, useindex, selectedinputonly, selectedjoinonly, excludecontaining) # configure the QgsMessageBar msgBar = self.iface.messageBar().createMessage( self.tr('Joining'), '') self.aprogressBar = QProgressBar() self.aprogressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) acancelButton = QPushButton() acancelButton.setText(self.CANCEL) # acancelButton.clicked.connect(self.killWorker) msgBar.layout().addWidget(self.aprogressBar) msgBar.layout().addWidget(acancelButton) # Has to be popped after the thread has finished (in # workerFinished). self.iface.messageBar().pushWidget(msgBar, Qgis.Info) # self.iface.messageBar().INFO) self.messageBar = msgBar # start the worker in a new thread self.mythread = QThread(self) # QT requires the "self" self.worker.status.connect(self.workerInfo) self.worker.progress.connect(self.progressBar.setValue) self.worker.progress.connect(self.aprogressBar.setValue) self.worker.finished.connect(self.workerFinished) self.worker.error.connect(self.workerError) # Must come before movetothread: self.cancelButton.clicked.connect(self.worker.kill) acancelButton.clicked.connect(self.worker.kill) self.worker.finished.connect(self.worker.deleteLater) self.worker.finished.connect(self.mythread.quit) # self.worker.error.connect(self.worker.deleteLater) # self.worker.error.connect(self.mythread.quit) # Must come before thread.started.connect!: self.worker.moveToThread(self.mythread) self.mythread.started.connect(self.worker.run) self.mythread.finished.connect(self.mythread.deleteLater) self.mythread.start() # self.thread = thread # self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) if layerId == joinlayerId: self.showInfo("The join layer is the same as the" " input layer - doing a self join!") except: import traceback self.showError("Error starting worker: " + traceback.format_exc()) else: pass # End of startworker def workerFinished(self, ok, ret): """Handles the output from the worker and cleans up after the worker has finished.""" # remove widget from message bar (pop) self.iface.messageBar().popWidget(self.messageBar) if ok and ret is not None: # report the result mem_layer = ret QgsMessageLog.logMessage(self.tr('NNJoin finished'), self.NNJOIN, Qgis.Info) mem_layer.dataProvider().updateExtents() mem_layer.commitChanges() self.layerlistchanging = True QgsProject.instance().addMapLayer(mem_layer) self.layerlistchanging = False else: # notify the user that something went wrong if not ok: self.showError(self.tr('Aborted') + '!') else: self.showError(self.tr('No layer created') + '!') self.progressBar.setValue(0.0) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.button_box.button(QDialogButtonBox.Close).setEnabled(True) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False) # End of workerFinished def workerError(self, exception_string): """Report an error from the worker.""" self.showError(exception_string) def workerInfo(self, message_string): """Report an info message from the worker.""" QgsMessageLog.logMessage(self.tr('Worker') + ': ' + message_string, self.NNJOIN, Qgis.Info) def fieldchanged(self, number=0): # If the layer list is being updated, don't do anything if self.layerlistchanging: return self.updateui() # End of fieldchanged def distfieldchanged(self, number=0): # If the layer list is being updated, don't do anything # if self.layerlistchanging: # return # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Enable the OK button (if layers are OK) if inputlayer is not None and joinlayer is not None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) if inputlayer is not None: # Set the default background (white) for the distance field name self.distancefieldname.setStyleSheet("background:#fff;") # Check if the distance field name already is used inputfields = inputlayer.fields().toList() for infield in inputfields: if infield.name() == self.distancefieldname.text(): self.distancefieldname.setStyleSheet("background:#f00;") self.showInfo( "Distance field name conflict in input layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) if joinlayer is not None: joinfields = joinlayer.fields().toList() for joinfield in joinfields: if (self.joinPrefix.text() + joinfield.name() == self.distancefieldname.text()): self.distancefieldname.setStyleSheet( "background:#f00;") self.showInfo( "Distance field name conflict in join layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) # self.updateui() # End of distfieldchanged def joinlayerchanged(self, number=0): # If the layer list is being updated, don't do anything if self.layerlistchanging: return # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) self.joinlayerid = joinlayerId joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Geographic? - give a warning! if joinlayer is not None and joinlayer.crs().isGeographic(): self.showWarning('Geographic CRS used for the join layer -' ' distances will be in decimal degrees!') self.layerchanged() # End of joinlayerchanged def layerchanged(self, number=0): """Do the necessary updates after a layer selection has been changed.""" # If the layer list is being updated, don't do anything if self.layerlistchanging: return # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) self.inputlayerid = layerId inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) self.joinlayerid = joinlayerId joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Update the input layer UI label with input geometry # type information if inputlayer is not None: inputwkbtype = inputlayer.wkbType() inputlayerwkbtext = self.getwkbtext(inputwkbtype) self.inputgeometrytypelabel.setText(inputlayerwkbtext) # Update the join layer UI label with join geometry type # information if joinlayer is not None: joinwkbtype = joinlayer.wkbType() joinlayerwkbtext = self.getwkbtext(joinwkbtype) self.joingeometrytypelabel.setText(joinlayerwkbtext) # Check the coordinate systems # Different CRSs? - give a warning! if (inputlayer is not None and joinlayer is not None and inputlayer.crs() != joinlayer.crs()): self.showWarning( 'Layers have different CRS! - Input CRS authid: ' + str(inputlayer.crs().authid()) + ' - Join CRS authid: ' + str(joinlayer.crs().authid()) + ". The input layer will be transformed.") self.updateui() # end of layerchanged def useindexchanged(self, number=0): self.updateui() def layerlistchanged(self): # When a layer has been added to or removed by the user, # the comboboxes should be updated to include the new # possibilities. self.layerlistchanging = True # Repopulate the input and join layer combo boxes # Save the currently selected input layer inputlayerid = self.inputlayerid layers = QgsProject.instance().mapLayers() layerslist = [] for id in layers.keys(): if layers[id].type() == QgsMapLayer.VectorLayer: if not layers[id].isValid(): QMessageBox.information(None, self.tr('Information'), 'Layer ' + layers[id].name() + ' is not valid') if layers[id].wkbType() != QgsWkbTypes.NoGeometry: layerslist.append((layers[id].name(), id)) # Add the layers to the input layers combobox self.inputVectorLayer.clear() for layerdescription in layerslist: self.inputVectorLayer.addItem(layerdescription[0], layerdescription[1]) # Set the previous selection for the input layer for i in range(self.inputVectorLayer.count()): if self.inputVectorLayer.itemData(i) == inputlayerid: self.inputVectorLayer.setCurrentIndex(i) # Save the currently selected join layer joinlayerid = self.joinlayerid # Add the layers to the join layers combobox self.joinVectorLayer.clear() for layerdescription in layerslist: self.joinVectorLayer.addItem(layerdescription[0], layerdescription[1]) # Set the previous selection for the join layer for i in range(self.joinVectorLayer.count()): if self.joinVectorLayer.itemData(i) == joinlayerid: self.joinVectorLayer.setCurrentIndex(i) self.layerlistchanging = False self.updateui() # End of layerlistchanged def updateui(self): """Do the necessary updates after a layer selection has been changed.""" # if self.layerlistchanged: # return # Update the output dataset name self.outputDataset.setText(self.inputVectorLayer.currentText() + '_' + self.joinVectorLayer.currentText()) # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Enable the OK button (if layers are OK) if inputlayer is not None and joinlayer is not None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) # Check the geometry type of the input layer and set # user interface options accordingly if inputlayer is not None: wkbType = inputlayer.wkbType() geomType = inputlayer.geometryType() # not used yet joinwkbType = QgsWkbTypes.Unknown joingeomType = QgsWkbTypes.UnknownGeometry # not used yet if joinlayer is not None: joinwkbType = joinlayer.wkbType() joingeomType = joinlayer.geometryType() # If the input layer is not a point layer, allow choosing # approximate geometry (centroid) if wkbType == QgsWkbTypes.Point or wkbType == QgsWkbTypes.Point25D: # Input layer is a simple point layer and can not # be approximated self.approximate_input_geom_cb.blockSignals(True) self.approximate_input_geom_cb.setCheckState(Qt.Unchecked) self.approximate_input_geom_cb.setVisible(False) self.approximate_input_geom_cb.blockSignals(False) else: # Input layer is not a point layer, so approximation # is possible self.approximate_input_geom_cb.blockSignals(True) self.approximate_input_geom_cb.setVisible(True) self.approximate_input_geom_cb.blockSignals(False) # Update the use index checkbox if ((wkbType == QgsWkbTypes.LineString or wkbType == QgsWkbTypes.LineString25D or wkbType == QgsWkbTypes.Polygon or wkbType == QgsWkbTypes.Polygon25D) and not self.approximate_input_geom_cb.isChecked()): # The input layer is a line or polygong layer that # is not approximated, so the user is allowed to # choose not to use the spatial index (not very useful!) if not self.use_index_nonpoint_cb.isVisible(): self.use_index_nonpoint_cb.blockSignals(True) self.use_index_nonpoint_cb.setCheckState(Qt.Checked) self.use_index_nonpoint_cb.setVisible(True) self.use_index_nonpoint_cb.blockSignals(False) else: # The input layer is either a point approximation # or it is a point layer (or some kind of # multigeometry!), anyway we won't allow the user to # choose not to use a spatial index self.use_index_nonpoint_cb.blockSignals(True) self.use_index_nonpoint_cb.setCheckState(Qt.Unchecked) self.use_index_nonpoint_cb.setVisible(False) self.use_index_nonpoint_cb.blockSignals(False) # This does not work!!???? # Update the use index approximation checkbox: if (((wkbType == QgsWkbTypes.Point) or (wkbType == QgsWkbTypes.Point25D) or self.approximate_input_geom_cb.isChecked()) and not (joinwkbType == QgsWkbTypes.Point or joinwkbType == QgsWkbTypes.Point25D)): # For non-point join layers and point input layers, # the user is allowed to choose an approximation (the # index geometry) to be used for the join geometry in # the join. self.use_indexapprox_cb.setVisible(True) else: # For point join layers, and non-point, # non-point-approximated input layers, the user is # not allowed to choose an approximation (the index # geometry) to be used for the join geometry in the # join. self.use_indexapprox_cb.blockSignals(True) self.use_indexapprox_cb.setCheckState(Qt.Unchecked) self.use_indexapprox_cb.setVisible(False) self.use_indexapprox_cb.blockSignals(False) # Update the exclude containing polygon checkbox: if ((wkbType == QgsWkbTypes.Point or wkbType == QgsWkbTypes.Point25D or self.approximate_input_geom_cb.isChecked()) and (joinwkbType == QgsWkbTypes.Polygon or joinwkbType == QgsWkbTypes.Polygon25D) and (not self.use_indexapprox_cb.isChecked())): # For polygon join layers and point input layers, # the user is allowed to choose to exclude the # containing polygon in the join. self.exclude_containing_poly_cb.blockSignals(True) self.exclude_containing_poly_cb.setVisible(True) self.exclude_containing_poly_cb.blockSignals(False) else: self.exclude_containing_poly_cb.blockSignals(True) self.exclude_containing_poly_cb.setCheckState(Qt.Unchecked) self.exclude_containing_poly_cb.setVisible(False) self.exclude_containing_poly_cb.blockSignals(False) # Set the default background (white) for the distance field name self.distancefieldname.setStyleSheet("background:#fff;") # Check if the distance field name already is used inputfields = inputlayer.fields().toList() for infield in inputfields: if infield.name() == self.distancefieldname.text(): self.distancefieldname.setStyleSheet("background:#f00;") self.showInfo( "Distance field name conflict in input layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) break if joinlayer is not None: joinfields = joinlayer.fields().toList() for joinfield in joinfields: if (self.joinPrefix.text() + joinfield.name() == self.distancefieldname.text()): self.distancefieldname.setStyleSheet( "background:#f00;") self.showInfo( "Distance field name conflict in join layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) break else: # No input layer defined, so options are disabled self.approximate_input_geom_cb.setVisible(False) self.use_indexapprox_cb.setVisible(False) self.use_index_nonpoint_cb.setVisible(False) # End of updateui def getwkbtext(self, number): if number == QgsWkbTypes.Unknown: return "Unknown" elif number == QgsWkbTypes.Point: return "Point" elif number == QgsWkbTypes.LineString: return "LineString" elif number == QgsWkbTypes.Polygon: return "Polygon" elif number == QgsWkbTypes.MultiPoint: return "MultiPoint" elif number == QgsWkbTypes.MultiLineString: return "MultiLineString" elif number == QgsWkbTypes.MultiPolygon: return "MultiPolygon" elif number == QgsWkbTypes.NoGeometry: return "NoGeometry" elif number == QgsWkbTypes.Point25D: return "Point25D" elif number == QgsWkbTypes.LineString25D: return "LineString25D" elif number == QgsWkbTypes.Polygon25D: return "Polygon25D" elif number == QgsWkbTypes.MultiPoint25D: return "MultiPoint25D" elif number == QgsWkbTypes.MultiLineString25D: return "MultiLineString25D" elif number == QgsWkbTypes.MultiPolygon25D: return "MultiPolygon25D" else: showError('Unknown or invalid geometry type: ' + str(number)) return "Don't know" # End of getwkbtext def killWorker(self): """Kill the worker thread.""" # if self.worker is not None: # self.showInfo(self.tr('Killing worker')) # self.worker.kill() def showError(self, text): """Show an error.""" self.iface.messageBar().pushMessage(self.tr('Error'), text, level=Qgis.Critical, duration=3) QgsMessageLog.logMessage('Error: ' + text, self.NNJOIN, Qgis.Critical) def showWarning(self, text): """Show a warning.""" self.iface.messageBar().pushMessage(self.tr('Warning'), text, level=Qgis.Warning, duration=2) QgsMessageLog.logMessage('Warning: ' + text, self.NNJOIN, Qgis.Warning) def showInfo(self, text): """Show info.""" self.iface.messageBar().pushMessage(self.tr('Info'), text, level=Qgis.Info, duration=2) QgsMessageLog.logMessage('Info: ' + text, self.NNJOIN, Qgis.Info) def help(self): QDesktopServices.openUrl(QUrl.fromLocalFile( self.plugin_dir + "/help/html/index.html")) # showPluginHelp(None, "help/html/index") def tr(self, message): """Get the translation for a string using Qt translation API. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ return QCoreApplication.translate('NNJoinDialog', message) # Implement the accept method to avoid exiting the dialog when # starting the work def accept(self): """Accept override.""" pass # Implement the reject method to have the possibility to avoid # exiting the dialog when cancelling def reject(self): """Reject override.""" # exit the dialog QDialog.reject(self)
def __init__(self, parameters): QThread.__init__(self) self.parameters = parameters self.matrix_dem = [] self.quit = False self.baseModel = 2
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/")
def qgis_excepthook(type, value, tb): # detect if running in the main thread in_main_thread = QCoreApplication.instance() is None or QThread.currentThread() == QCoreApplication.instance().thread() # only use messagebar if running in main thread - otherwise it will crash! showException(type, value, tb, None, messagebar=in_main_thread)
def __init__(self): QThread.__init__(self)
def startWorker(self): """Initialises and starts the worker thread.""" try: layerindex = self.inputRaster.currentIndex() layerId = self.inputRaster.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) #inputlayer = QgsMapLayerRegistry.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return # create a reference to the layer that is being processed # (for use when creating the resulting raster layer) self.thinninglayer = inputlayer self.levels = [] #self.levelsListView.selectAll() #selected = self.levelsListView.selectedIndexes() if self.levelsListView.model().rowCount() == 0: self.showInfo("Levels must be specified!") return for i in range(self.levelsListView.model().rowCount()): levelstring = self.levelsListView.model().item(i).text() #for i in selected: # levelstring = self.levelsListView.model().itemData(i)[0] if self.intband: self.levels.append(int(levelstring)) else: self.levels.append(float(levelstring)) #self.levelsListView.clearSelection() # create a new worker instance worker = Worker(inputlayer, self.levels, self.intband) # configure the QgsMessageBar msgBar = self.iface.messageBar().createMessage( self.tr('Skeletonising'), '') self.aprogressBar = QProgressBar() self.aprogressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) acancelButton = QPushButton() acancelButton.setText(self.CANCEL) acancelButton.clicked.connect(self.killWorker) msgBar.layout().addWidget(self.aprogressBar) msgBar.layout().addWidget(acancelButton) # Has to be popped after the thread has finished (in # workerFinished). self.iface.messageBar().pushWidget(msgBar, Qgis.Info) self.messageBar = msgBar # start the worker in a new thread thread = QThread(self) worker.moveToThread(thread) worker.finished.connect(self.workerFinished) worker.error.connect(self.workerError) worker.status.connect(self.workerInfo) worker.progress.connect(self.progressBar.setValue) worker.progress.connect(self.aprogressBar.setValue) worker.iterprogress.connect(self.iterProgressBar.setValue) thread.started.connect(worker.run) thread.start() self.thread = thread self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) except: import traceback self.showError(traceback.format_exc()) else: pass
def startWorker(self): # self.showInfo('Ready to start worker') self.degfreedCorr = self.degfreedcorr_cb.isChecked() self.crimestatCorr = self.crimestatcorr_cb.isChecked() # Get the input layer layerindex = self.InputLayer.currentIndex() layerId = self.InputLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return self.featureCount = 0 if self.selectedFeatures_cb.isChecked(): self.featureCount = inputlayer.selectedFeatureCount() if self.featureCount == 0: self.featureCount = inputlayer.featureCount() if self.featureCount < 2: self.showError(self.tr('Not enough features')) # self.scene.clear() return if (self.useWeights_cb.isChecked() and self.inputField.count() == 0): self.showError(self.tr('Missing numerical field')) return fieldindex = self.inputField.currentIndex() fieldname = self.inputField.itemData(fieldindex) # inpfield = inputlayer.fieldNameIndex(fieldindex) # minval = inputlayer.minimumValue(inpfield) if (not self.useWeights_cb.isChecked()): fieldname = None self.result = None self.SDLayer = inputlayer self.method = 1 if self.yuill_rb.isChecked(): self.method = 1 elif self.crimestat_rb.isChecked(): self.method = 2 if self.featureCount < 3 and (self.method == 2 or self.degfreedCorr): self.showError(self.tr('Not enough features')) return # create a new worker instance worker = Worker(inputlayer, self.selectedFeatures_cb.isChecked(), fieldname, self.method) # start the worker in a new thread thread = QThread(self) worker.moveToThread(thread) worker.finished.connect(self.workerFinished) worker.error.connect(self.workerError) worker.status.connect(self.workerInfo) worker.progress.connect(self.progressBar.setValue) # worker.progress.connect(self.aprogressBar.setValue) thread.started.connect(worker.run) thread.start() self.thread = thread self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) self.InputLayer.setEnabled(False) self.inputField.setEnabled(False)
def download_results(self): """Download simulation results files.""" current_index = self.tv_finished_sim_tree.currentIndex() if not current_index.isValid(): return working_dir = self.plugin_dock.plugin_settings.working_dir local_schematisations = list_local_schematisations(working_dir) try: current_row = current_index.row() name_item = self.tv_model.item(current_row, 0) sim_id = name_item.data(Qt.UserRole) simulation = self.finished_simulations[sim_id] simulation_name = simulation.name.replace(" ", "_") simulation_model_id = int(simulation.threedimodel_id) tc = ThreediCalls(self.plugin_dock.threedi_api) try: model_3di = tc.fetch_3di_model(simulation_model_id) gridadmin_downloads = tc.fetch_3di_model_gridadmin_download(simulation_model_id) if model_3di.schematisation_id: model_schematisation_id = model_3di.schematisation_id model_schematisation_name = model_3di.schematisation_name model_revision_number = model_3di.revision_number try: local_schematisation = local_schematisations[model_schematisation_id] except KeyError: local_schematisation = LocalSchematisation( working_dir, model_schematisation_id, model_schematisation_name, create=True ) try: local_revision = local_schematisation.revisions[model_revision_number] except KeyError: local_revision = LocalRevision(local_schematisation, model_revision_number) local_revision.make_revision_structure() results_dir = local_revision.results_dir else: warn_msg = ( "The 3Di model to which these results belong was uploaded with Tortoise and does not " "belong to any schematisation. Therefore, it cannot be determined to which " "schematisation the results should be downloaded.\n\nPlease select a directory to save " "the result files to." ) self.plugin_dock.communication.show_warn(warn_msg) results_dir = self.pick_results_destination_dir() if not results_dir: self.plugin_dock.communication.show_warn(warn_msg) return except ApiException as e: if e.status == 404: warn_msg = ( "The 3Di model to which these results belong is owned by an organisation for which " "you do not have sufficient rights. Therefore, you cannot download the computational " "grid (gridadmin.h5) and it cannot be determined to which schematisation the results " "should be downloaded.\n\nContact the servicedesk to obtain access rights to the " "organisation that owns the 3Di model.\n\nPlease select a directory to save the result" " files to." ) self.plugin_dock.communication.show_warn(warn_msg) results_dir = self.pick_results_destination_dir() if not results_dir: return gridadmin_downloads = None else: raise e simulation_subdirectory = os.path.join(results_dir, f"sim_{sim_id}_{simulation_name}") downloads = tc.fetch_simulation_downloads(sim_id) if gridadmin_downloads is not None: downloads.append(gridadmin_downloads) downloads.sort(key=lambda x: x[-1].size) self.last_progress_item = self.tv_model.item(current_row, self.PROGRESS_COLUMN_IDX) except ApiException as e: error_msg = extract_error_message(e) self.plugin_dock.communication.show_error(error_msg) return except Exception as e: error_msg = f"Error: {e}" self.plugin_dock.communication.show_error(error_msg) return self.pb_download.setDisabled(True) self.download_results_thread = QThread() self.download_worker = DownloadProgressWorker(simulation, downloads, simulation_subdirectory) self.download_worker.moveToThread(self.download_results_thread) self.download_worker.thread_finished.connect(self.on_download_finished_success) self.download_worker.download_failed.connect(self.on_download_finished_failed) self.download_worker.download_progress.connect(self.on_download_progress_update) self.download_results_thread.started.connect(self.download_worker.run) self.download_results_thread.start()
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()
class SimulationResults(uicls, basecls): """Dialog with methods for handling simulations results.""" PROGRESS_COLUMN_IDX = 3 def __init__(self, plugin_dock, parent=None): super().__init__(parent) self.setupUi(self) self.plugin_dock = plugin_dock self.api_client = self.plugin_dock.threedi_api self.download_results_thread = None self.download_worker = None self.finished_simulations = {} self.tv_model = None self.last_progress_item = None self.setup_view_model() self.plugin_dock.simulations_progresses_sentinel.progresses_fetched.connect(self.update_finished_list) self.pb_cancel.clicked.connect(self.close) self.pb_download.clicked.connect(self.download_results) self.tv_finished_sim_tree.selectionModel().selectionChanged.connect(self.toggle_download_results) def setup_view_model(self): """Setting up model and columns for TreeView.""" self.tv_model = QStandardItemModel(0, 4) delegate = DownloadProgressDelegate(self.tv_finished_sim_tree) self.tv_finished_sim_tree.setItemDelegateForColumn(self.PROGRESS_COLUMN_IDX, delegate) self.tv_model.setHorizontalHeaderLabels(["Simulation name", "User", "Expires", "Download progress"]) self.tv_finished_sim_tree.setModel(self.tv_model) def toggle_download_results(self): """Toggle download if any simulation is selected.""" if self.download_results_thread is None: selection_model = self.tv_finished_sim_tree.selectionModel() if selection_model.hasSelection(): self.pb_download.setEnabled(True) else: self.pb_download.setDisabled(True) def add_finished_simulation_to_model(self, simulation, status): """Method for adding simulation to the model.""" sim_id = simulation.id sim_name_item = QStandardItem(f"{simulation.name} ({sim_id})") sim_name_item.setData(sim_id, Qt.UserRole) user_item = QStandardItem(simulation.user) delta = relativedelta(status.created, ThreediCalls.EXPIRATION_TIME) expires_item = QStandardItem(f"{delta.days} day(s)") progress_item = QStandardItem() progress_item.setData(-1, Qt.UserRole) self.tv_model.insertRow(0, [sim_name_item, user_item, expires_item, progress_item]) self.finished_simulations[sim_id] = simulation def update_finished_list(self, progresses): """Update finished simulations list.""" for sim_id, (sim, status, progress) in progresses.items(): status_name = status.name if status_name != "finished": continue if sim_id not in self.finished_simulations: self.add_finished_simulation_to_model(sim, status) def on_download_progress_update(self, percentage): """Update download progress bar.""" self.last_progress_item.setData(percentage, Qt.UserRole) if percentage == 0: row = self.last_progress_item.index().row() name_text = self.tv_model.item(row, 0).text() msg = f"Downloading results of {name_text} started!" self.plugin_dock.communication.bar_info(msg) def on_download_finished_success(self, msg): """Reporting finish successfully status and closing download thread.""" self.plugin_dock.communication.bar_info(msg, log_text_color=Qt.darkGreen) self.download_results_thread.quit() self.download_results_thread.wait() self.download_results_thread = None self.download_worker = None self.toggle_download_results() def on_download_finished_failed(self, msg): """Reporting failure and closing download thread.""" self.plugin_dock.communication.bar_error(msg, log_text_color=Qt.red) self.download_results_thread.quit() self.download_results_thread.wait() self.download_results_thread = None self.download_worker = None self.toggle_download_results() def terminate_download_thread(self): """Forcing termination of download background thread.""" if self.download_results_thread is not None and self.download_results_thread.isRunning(): self.plugin_dock.communication.bar_info("Terminating download thread.") self.download_results_thread.terminate() self.plugin_dock.communication.bar_info("Waiting for download thread termination.") self.download_results_thread.wait() self.plugin_dock.communication.bar_info("Download worker terminated.") self.download_results_thread = None self.download_worker = None def pick_results_destination_dir(self): """Pick folder where results will be written to.""" working_dir = self.plugin_dock.plugin_settings.working_dir last_folder = QSettings().value("threedi/last_results_folder", working_dir, type=str) directory = QFileDialog.getExistingDirectory(self, "Select Results Directory", last_folder) if len(directory) == 0: return None QSettings().setValue("threedi/last_results_folder", directory) return directory def download_results(self): """Download simulation results files.""" current_index = self.tv_finished_sim_tree.currentIndex() if not current_index.isValid(): return working_dir = self.plugin_dock.plugin_settings.working_dir local_schematisations = list_local_schematisations(working_dir) try: current_row = current_index.row() name_item = self.tv_model.item(current_row, 0) sim_id = name_item.data(Qt.UserRole) simulation = self.finished_simulations[sim_id] simulation_name = simulation.name.replace(" ", "_") simulation_model_id = int(simulation.threedimodel_id) tc = ThreediCalls(self.plugin_dock.threedi_api) try: model_3di = tc.fetch_3di_model(simulation_model_id) gridadmin_downloads = tc.fetch_3di_model_gridadmin_download(simulation_model_id) if model_3di.schematisation_id: model_schematisation_id = model_3di.schematisation_id model_schematisation_name = model_3di.schematisation_name model_revision_number = model_3di.revision_number try: local_schematisation = local_schematisations[model_schematisation_id] except KeyError: local_schematisation = LocalSchematisation( working_dir, model_schematisation_id, model_schematisation_name, create=True ) try: local_revision = local_schematisation.revisions[model_revision_number] except KeyError: local_revision = LocalRevision(local_schematisation, model_revision_number) local_revision.make_revision_structure() results_dir = local_revision.results_dir else: warn_msg = ( "The 3Di model to which these results belong was uploaded with Tortoise and does not " "belong to any schematisation. Therefore, it cannot be determined to which " "schematisation the results should be downloaded.\n\nPlease select a directory to save " "the result files to." ) self.plugin_dock.communication.show_warn(warn_msg) results_dir = self.pick_results_destination_dir() if not results_dir: self.plugin_dock.communication.show_warn(warn_msg) return except ApiException as e: if e.status == 404: warn_msg = ( "The 3Di model to which these results belong is owned by an organisation for which " "you do not have sufficient rights. Therefore, you cannot download the computational " "grid (gridadmin.h5) and it cannot be determined to which schematisation the results " "should be downloaded.\n\nContact the servicedesk to obtain access rights to the " "organisation that owns the 3Di model.\n\nPlease select a directory to save the result" " files to." ) self.plugin_dock.communication.show_warn(warn_msg) results_dir = self.pick_results_destination_dir() if not results_dir: return gridadmin_downloads = None else: raise e simulation_subdirectory = os.path.join(results_dir, f"sim_{sim_id}_{simulation_name}") downloads = tc.fetch_simulation_downloads(sim_id) if gridadmin_downloads is not None: downloads.append(gridadmin_downloads) downloads.sort(key=lambda x: x[-1].size) self.last_progress_item = self.tv_model.item(current_row, self.PROGRESS_COLUMN_IDX) except ApiException as e: error_msg = extract_error_message(e) self.plugin_dock.communication.show_error(error_msg) return except Exception as e: error_msg = f"Error: {e}" self.plugin_dock.communication.show_error(error_msg) return self.pb_download.setDisabled(True) self.download_results_thread = QThread() self.download_worker = DownloadProgressWorker(simulation, downloads, simulation_subdirectory) self.download_worker.moveToThread(self.download_results_thread) self.download_worker.thread_finished.connect(self.on_download_finished_success) self.download_worker.download_failed.connect(self.on_download_finished_failed) self.download_worker.download_progress.connect(self.on_download_progress_update) self.download_results_thread.started.connect(self.download_worker.run) self.download_results_thread.start()
class Photo2ShapeDialog(BASE, WIDGET): def __init__(self, iface, parent=None): super(Photo2ShapeDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.settings = QSettings('alexbruy', 'photo2shape') self.thread = QThread() self.importer = PhotoImporter() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.btnSelectInput.clicked.connect(self.selectDirectory) self.btnSelectOutput.clicked.connect(self.selectFile) self.importer.moveToThread(self.thread) self.importer.importError.connect(self.thread.quit) self.importer.importError.connect(self.importCanceled) self.importer.importMessage.connect(self.logMessage) self.importer.importFinished.connect(self.thread.quit) self.importer.importFinished.connect(self.importCompleted) self.importer.photoProcessed.connect(self.updateProgress) self.thread.started.connect(self.importer.importPhotos) self.encoding = self.settings.value('encoding', 'System') self.manageGui() def manageGui(self): self.chkRecurse.setChecked(self.settings.value('recurse', True, bool)) self.chkAppend.setChecked(self.settings.value('append', True, bool)) self.chkLoadLayer.setChecked( self.settings.value('loadLayer', True, bool)) def closeEvent(self, event): self._saveSettings() QDialog.closeEvent(self, event) def selectDirectory(self): lastDir = self.settings.value('lastPhotosDir', '.') dirName = QFileDialog.getExistingDirectory( self, self.tr('Select directory'), lastDir) if dirName == '': return self.lePhotosPath.setText(dirName) self.settings.setValue('lastPhotosDir', os.path.dirname(dirName)) def selectFile(self): lastDir = self.settings.value('lastShapeDir', '.') shpFilter = self.tr('ESRI Shapefiles (*.shp *.SHP)') self.encoding = self.settings.value('encoding', 'System') fileDialog = QgsEncodingFileDialog( self, self.tr('Save file'), lastDir, shpFilter, self.encoding) fileDialog.setDefaultSuffix('shp') fileDialog.setFileMode(QFileDialog.AnyFile) fileDialog.setAcceptMode(QFileDialog.AcceptSave) fileDialog.setConfirmOverwrite(True) if fileDialog.exec_(): fileName = fileDialog.selectedFiles()[0] self.encoding = fileDialog.encoding() self.leOutputShape.setText(fileName) self.settings.setValue('lastShapeDir', QFileInfo(fileName).absoluteDir().absolutePath()) self.settings.setValue('encoding', self.encoding) def reject(self): self._saveSettings() QDialog.reject(self) def accept(self): self._saveSettings() dirName = self.lePhotosPath.text() if dirName == '': self.iface.messageBar().pushWarning( self.tr('Path not set'), self.tr('Path to photos is not set. Please specify directory ' 'with photos and try again.')) return fileName = self.leOutputShape.text() if fileName == '': self.iface.messageBar().pushWarning( self.tr('Output file is not set'), self.tr('Output file name is missing. Please specify correct ' 'output file and try again.')) return # make sure output file always has correct suffix if not fileName.lower().endswith('.shp'): fileName += '.shp' self.leOutputShape.setText(fileName) if self.chkAppend.isChecked() and not os.path.isfile(fileName): self.iface.messageBar().pushWarning( self.tr('Appending is not possible'), self.tr('Output file is a new file and can not be used ' 'in "append" mode. Either specify existing file ' 'or uncheck corresponding checkbox.')) return self.importer.setPhotosDirectory(dirName) self.importer.setOutputPath(fileName) self.importer.setEncoding(self.encoding) self.importer.setRecurseDirs(self.chkRecurse.isChecked()) self.importer.setAppendFile(self.chkAppend.isChecked()) self.thread.start() self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) def updateProgress(self, value): self.progressBar.setValue(value) def logMessage(self, message, level=QgsMessageLog.INFO): QgsMessageLog.logMessage(message, 'Photo2Shape', level) def importCanceled(self, message): self.iface.messageBar().pushWarning(self.tr('Import error'), message) self._restoreGui() def importCompleted(self): self.iface.messageBar().pushSuccess( self.tr('Import completed'), self.tr('Shapefile from photos sucessfully created')) if self.chkLoadLayer.isChecked(): self._loadLayer() self._restoreGui() def _loadLayer(self): fName = self.leOutputShape.text() layer = QgsVectorLayer(fName, QFileInfo(fName).baseName(), 'ogr') if layer.isValid(): layer.loadNamedStyle( os.path.join(pluginPath, 'resources', 'photos.qml')) QgsMapLayerRegistry.instance().addMapLayer(layer) else: self.iface.messageBar().pushWarning( self.tr('No output'), self.tr('Cannot load output shapefile')) def _restoreGui(self): self.progressBar.setValue(0) self.btnOk.setEnabled(True) self.btnClose.setEnabled(True) def _saveSettings(self): self.settings.setValue('recurse', self.chkRecurse.isChecked()) self.settings.setValue('append', self.chkAppend.isChecked()) self.settings.setValue('loadLayer', self.chkLoadLayer.isChecked())
class Photo2ShapeDialog(BASE, WIDGET): def __init__(self, iface, parent=None): super(Photo2ShapeDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.settings = QgsSettings("alexbruy", "photo2shape") self.fwPhotosPath.setStorageMode(QgsFileWidget.GetDirectory) self.fwPhotosPath.setDialogTitle(self.tr("Select directory")) self.fwPhotosPath.setDefaultRoot(self.settings.value("lastPhotosDirectory", os.path.expanduser("~"), str)) self.fwPhotosPath.fileChanged.connect(self.updateLastPhotosPath) self.fwOutputShape.setStorageMode(QgsFileWidget.SaveFile) self.fwOutputShape.setConfirmOverwrite(True) self.fwOutputShape.setDialogTitle(self.tr("Select file")) self.fwOutputShape.setDefaultRoot(self.settings.value("lastShapeDirectory", QgsProject.instance().homePath(), str)) self.fwOutputShape.setFilter(self.tr("ESRI Shapefile (*.shp *.SHP)")) self.fwOutputShape.fileChanged.connect(self.updateLastShapePath) self.thread = QThread() self.importer = PhotoImporter() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.importer.moveToThread(self.thread) self.importer.importError.connect(self.thread.quit) self.importer.importError.connect(self.importCanceled) self.importer.importMessage.connect(self.logMessage) self.importer.importFinished.connect(self.thread.quit) self.importer.importFinished.connect(self.importCompleted) self.importer.photoProcessed.connect(self.updateProgress) self.thread.started.connect(self.importer.importPhotos) self.encoding = self.settings.value("encoding", "utf-8", str) self.chkRecurse.setChecked(self.settings.value("recurse", True, bool)) self.chkAppend.setChecked(self.settings.value("append", True, bool)) self.chkLoadLayer.setChecked(self.settings.value("loadLayer", True, bool)) def closeEvent(self, event): self._saveSettings() QDialog.closeEvent(self, event) def updateLastPhotosPath(self, dirPath): self.fwPhotosPath.setDefaultRoot(dirPath) self.settings.setValue("lastPhotosDirectory", os.path.dirname(dirPath)) def updateLastShapePath(self, shapePath): self.fwOutputShape.setDefaultRoot(shapePath) self.settings.setValue("lastShapeDirectory", os.path.dirname(shapePath)) def reject(self): self._saveSettings() QDialog.reject(self) def accept(self): self._saveSettings() dirName = self.fwPhotosPath.filePath() if dirName == '': self.iface.messageBar().pushWarning( self.tr("Path is not set"), self.tr("Path to photos is not set. Please specify directory " "with photos and try again.")) return fileName = self.fwOutputShape.filePath() if fileName == "": self.iface.messageBar().pushWarning( self.tr("Output file is not set"), self.tr("Output file name is missing. Please specify correct " "output file and try again.")) return if self.chkAppend.isChecked() and not os.path.isfile(fileName): self.iface.messageBar().pushWarning( self.tr("Appending is not possible"), self.tr("Output file is a new file and can not be used " "in 'append' mode. Either specify existing file " "or uncheck corresponding checkbox.")) return self.importer.setPhotosDirectory(dirName) self.importer.setOutputPath(fileName) self.importer.setEncoding(self.encoding) self.importer.setRecurseDirs(self.chkRecurse.isChecked()) self.importer.setAppendFile(self.chkAppend.isChecked()) self.thread.start() self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) def updateProgress(self, value): self.progressBar.setValue(value) def logMessage(self, message, level=QgsMessageLog.INFO): QgsMessageLog.logMessage(message, "Photo2Shape", level) def importCanceled(self, message): self.iface.messageBar().pushWarning(self.tr("Import error"), message) self._restoreGui() def importCompleted(self): self.iface.messageBar().pushSuccess( self.tr("Import completed"), self.tr("Photos imported sucessfully.")) if self.chkLoadLayer.isChecked(): self._loadLayer() self._restoreGui() def _loadLayer(self): fName = self.fwOutputShape.filePath() layer = QgsVectorLayer(fName, os.path.basename(fName), "ogr") if layer.isValid(): layer.loadNamedStyle( os.path.join(pluginPath, "resources", "photos.qml")) QgsProject.instance().addMapLayer(layer) else: self.iface.messageBar().pushWarning( self.tr("No output"), self.tr("Can not load output file.")) def _restoreGui(self): self.progressBar.setValue(0) self.btnOk.setEnabled(True) self.btnClose.setEnabled(True) def _saveSettings(self): self.settings.setValue("recurse", self.chkRecurse.isChecked()) self.settings.setValue("append", self.chkAppend.isChecked()) self.settings.setValue("loadLayer", self.chkLoadLayer.isChecked())