Beispiel #1
0
    def uploadDoc(self):
        """
        Upload the file to the central repository in a separate thread using the specified file manager
        """
        if isinstance(self.fileManager, NetworkFileManager):
            self.pgBar.setVisible(True)
            self._docSize = self.fileInfo.size()
            '''
            Create document transfer helper for multi-threading capabilities.
            Use of queued connections will guarantee that signals and slots are captured
            in any thread.
            '''
            workerThread = QThread(self)
            docWorker = DocumentTransferWorker(
                self.fileManager,
                self.fileInfo,
                "%s" % (self._source_entity),
                "%s" % (self._doc_type),
                self
            )
            docWorker.moveToThread(workerThread)

            workerThread.started.connect(docWorker.transfer)
            docWorker.blockWrite.connect(self.onBlockWritten)
            docWorker.complete.connect(self.onCompleteTransfer)
            workerThread.finished.connect(docWorker.deleteLater)
            workerThread.finished.connect(workerThread.deleteLater)

            workerThread.start()
            # Call transfer() to get fileUUID early
            # docWorker.transfer()
            self.fileUUID = docWorker.file_uuid
    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 start_computing(self):
        """computing called in new thread and showing a progressBar"""

        computation = Move(self.input.text(), self.output.text(),
                           self.ellipsoidSelector.text(),
                           self.units.currentText(), self.value.text())

        messageBar = self.iface.messageBar().createMessage('Computing...')
        progressBar = QProgressBar()
        cancelButton = QPushButton()
        cancelButton.setText('Cancel')
        messageBar.layout().addWidget(progressBar)
        messageBar.layout().addWidget(cancelButton)

        computationThread = QThread(self)
        computation.moveToThread(computationThread)
        computation.finished.connect(self.computing_finished)
        computation.progress.connect(progressBar.setValue)
        cancelButton.clicked.connect(self.cancel_computing)
        computationThread.started.connect(computation.shift)
        computation.error.connect(self.value_error)

        self.iface.messageBar().pushWidget(messageBar, Qgis.Info)

        if not computationThread.isRunning():
            self.computationRunning = True
            computationThread.start()

        self.computation = computation
        self.computationThread = computationThread
        self.messageBar = messageBar
    def startWorker(self):
        self.dlg.segmentingProgress.reset()
        self.settings = self.dlg.get_settings()
        if self.settings['output_type'] == 'postgis':
            db_settings = self.dlg.get_dbsettings()
            self.settings.update(db_settings)

        if lfh.getLayerByName(self.settings['input']).crs().postgisSrid() == 4326:
            self.giveMessage('Re-project the layer. EPSG:4326 not allowed.', Qgis.Info)
        elif self.settings['output'] != '':
            segmenting = self.Worker(self.settings, self.iface)
            self.dlg.lockGUI(True)
            # start the segmenting in a new thread
            thread = QThread()
            self.thread_error = ''
            segmenting.moveToThread(thread)
            segmenting.finished.connect(self.workerFinished)
            segmenting.error.connect(self.workerError)
            segmenting.warning.connect(self.giveMessage)
            segmenting.segm_progress.connect(self.dlg.segmentingProgress.setValue)

            thread.started.connect(segmenting.run)

            thread.start()

            self.thread = thread
            self.segmenting = segmenting
        else:
            self.giveMessage('Missing user input!', Qgis.Info)
            return
 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()
Beispiel #6
0
    def runSearch(self):
        '''Called when the user pushes the Search button'''
        # selectedLayer = self.layerListComboBox.currentIndex()
        self.updateTableStructure(",".join(self.display_field_list_temp))


        selectedLayer = self.layerListComboBox.currentText()
        comparisonMode = self.comparisonComboBox.currentIndex()
        self.noSelection = True
        try:
            sstr = self.findStringEdit.text().strip()
        except:
            self.showErrorMessage('Invalid Search String')
            return

        type_index=self.chooseType.currentIndex()

            # 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('Please add respective layers')
            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('')

        selectedField = self.searchFieldComboBox.currentText()
        if 'all field'  in selectedField.lower() or 'all fields'  in selectedField.lower():
                infield = self.searchFieldComboBox.currentIndex() >= 1
        else:
            infield = self.searchFieldComboBox.currentIndex() >= 0

        # 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 runSearch(self):
        '''Called when the user pushes the Search button'''
        searchT = str(self.searchTypeComboBox.currentText())
        searchP = self.plugin.searchTypes[searchT][0]
        selectedField = self.plugin.searchTypes[searchT][1]
        infield = selectedField != "*"
        comparisonMode = self.comparisonComboBox.currentIndex()
        self.noSelection = True
        try:
            sstr = self.findStringEdit.text().strip()
        except:
            self.showErrorMessage(self.tr(u'Invalid Search String'))
            return

        if sstr == '':
            self.showErrorMessage(self.tr(u'Search string is empty'))
            return
        # the vector layers that are to be searched
        self.vlayers = []
        # find layer by path or name
        for lay in self.iface.mapCanvas().layers():
            lp = lay.dataProvider().dataSourceUri().split('|')[0]
            if lp in searchP or lay.name() in searchP:
                self.vlayers.append(lay)
        # layers found?
        if len(self.vlayers) == 0:
            self.showErrorMessage(
                self.
                tr(u'There are no open/visible vector layers to search through'
                   ))
            self.showErrorMessage(
                self.tr(u'Add one of the following layer to your project: ') +
                str(searchP))
            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('')

        # 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()
Beispiel #8
0
    def startWorker(self):
        self.dlg.cleaningProgress.reset()
        self.settings = self.dlg.get_settings()
        if self.settings['output_type'] == 'postgis':
            db_settings = self.dlg.get_dbsettings()
            if 'dbname' not in db_settings:
                self.giveMessage(
                    'Please make sure a database is set for exporting to PostGIS',
                    Qgis.Info)
                return
            if 'schema' not in db_settings:
                self.giveMessage(
                    'Please make sure a schema is set for exporting to PostGIS',
                    Qgis.Info)
                return
            if 'table_name' not in db_settings:
                self.giveMessage(
                    'Please make sure a table is set for exporting to PostGIS',
                    Qgis.Info)
                return
            self.settings.update(db_settings)

        if lfh.getLayerByName(
                self.settings['input']).crs().postgisSrid() == 4326:
            self.giveMessage('Re-project the layer. EPSG:4326 not allowed.',
                             Qgis.Info)
            return
        elif self.settings['output'] != '':

            cleaning = self.Worker(self.settings, self.iface)
            # start the cleaning in a new thread
            self.dlg.lockGUI(True)
            self.dlg.lockSettingsGUI(True)
            thread = QThread()
            self.thread_error = ''
            cleaning.moveToThread(thread)
            cleaning.finished.connect(self.workerFinished)
            cleaning.error.connect(self.workerError)
            cleaning.warning.connect(self.giveMessage)
            cleaning.cl_progress.connect(self.dlg.cleaningProgress.setValue)

            thread.started.connect(cleaning.run)

            thread.start()

            self.thread = thread
            self.cleaning = cleaning

            if is_debug:
                print('started')
        else:
            self.giveMessage('Missing user input!', Qgis.Info)
            return
Beispiel #9
0
    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)
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...")
Beispiel #11
0
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
Beispiel #12
0
    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()
Beispiel #13
0
    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
Beispiel #14
0
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
Beispiel #15
0
class ClipLayersDialog(QDialog, FORM_CLASS):
    def __init__(self, iface, parent=None):
        """Constructor."""
        super(ClipLayersDialog, self).__init__(parent)
        # Set up the user interface from Designer through FORM_CLASS.
        # After self.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)

        self.iface = iface

        self.settings = QgsSettings("Hideto Yonezawa", "cliplayerss")

        # ExtentGroupBoxにMapCanvasを設定
        self.mExtentGroupBox.setMapCanvas(self.iface.mapCanvas())
        self.mExtentGroupBox.setOutputExtentFromCurrent()

        # 常にJGD2000で表示
        self.mExtentGroupBox.setOutputCrs(
            QgsCoordinateReferenceSystem('EPSG:4612'))
        self.mExtentGroupBox.findChild(
            QPushButton, "mButtonCalcFromLayer").setEnabled(False)
        self.mExtentGroupBox.findChild(QPushButton,
                                       "mButtonDrawOnCanvas").setEnabled(False)

        # QgsMapCanvas のシグナルに接続
        canvas = self.iface.mapCanvas()
        canvas.extentsChanged.connect(
            self.mExtentGroupBox.setOutputExtentFromCurrent)

        self.mExportDBNameQgsFileWidget.setStorageMode(QgsFileWidget.SaveFile)
        self.mExportDBNameQgsFileWidget.setConfirmOverwrite(True)
        self.mExportDBNameQgsFileWidget.setDialogTitle(
            self.tr("エクスポート先のGeoPackageファイル名を指定する"))
        self.mExportDBNameQgsFileWidget.setDefaultRoot(
            self.settings.value("lastGpkgDirectory",
                                QgsProject.instance().homePath(), str))
        self.mExportDBNameQgsFileWidget.setFilter(
            self.tr("GeoPackege (*.gpkg *.GPKG)"))
        self.mExportDBNameQgsFileWidget.fileChanged.connect(
            self.updateLastGpkgPath)

        self.mExportProjectNameQgsFileWidget.setStorageMode(
            QgsFileWidget.SaveFile)
        self.mExportProjectNameQgsFileWidget.setConfirmOverwrite(True)
        self.mExportProjectNameQgsFileWidget.setDialogTitle(
            self.tr("エクスポート先のプロジェクトファイル名を指定する"))
        self.mExportProjectNameQgsFileWidget.setDefaultRoot(
            self.settings.value("lastQGSProjectDirectory",
                                QgsProject.instance().homePath(), str))
        self.mExportProjectNameQgsFileWidget.setFilter(
            self.tr("QGIS Projectfile (*.qgs *.QGS)"))
        self.mExportProjectNameQgsFileWidget.fileChanged.connect(
            self.updateLastQGSProjectPath)

        self.thread = QThread()
        self.exporter = ClipByExtent()

        self.btnOk = self.mDlgButton_box.button(QDialogButtonBox.Ok)
        self.btnCancel = self.mDlgButton_box.button(QDialogButtonBox.Cancel)

        self.exporter.exportError.connect(self.exportCanceled)
        self.exporter.exportMessage.connect(self.logMessage)
        self.exporter.exportFinished.connect(self.exportCompleted)
        self.exporter.exportProcessed.connect(self.updateProgress)

        # デバッグするときはスレッドを使わないほうがデバッグしやすい
        self.exporter.moveToThread(self.thread)
        self.exporter.exportError.connect(self.thread.quit)
        self.exporter.exportFinished.connect(self.thread.quit)
        self.thread.started.connect(self.exporter.clipAndExport)

        self.mMinMaxClearCheckBox.setChecked(
            self.settings.value("minmaxclear", True, bool))

    def closeEvent(self, event):
        self._saveSettings()
        self._restoreGui()
        QDialog.closeEvent(self, event)

    def updateLastGpkgPath(self, gpkgPath):
        self.mExportDBNameQgsFileWidget.setDefaultRoot(gpkgPath)
        self.settings.setValue("lastGpkgDirectory", os.path.dirname(gpkgPath))

    def updateLastQGSProjectPath(self, projectpath):
        self.mExportProjectNameQgsFileWidget.setDefaultRoot(projectpath)
        self.settings.setValue("lastQGSProjectDirectory",
                               os.path.dirname(projectpath))

    def reject(self):
        self._saveSettings()
        self._restoreGui()
        QDialog.reject(self)

    def accept(self):
        self._saveSettings()

        gpkgName = self.mExportDBNameQgsFileWidget.filePath()
        if gpkgName == '':
            self.iface.messageBar().pushWarning(
                self.tr("出力先ファイル名未指定"),
                self.tr("出力先のファイル名がセットされていません。出力先ファイル名をセットしてください。"))
            return

        qgsName = self.mExportProjectNameQgsFileWidget.filePath()
        if qgsName == "":
            self.iface.messageBar().pushWarning(
                self.tr("出力先QGISプロジェクトファイル未指定"),
                self.
                tr("出力先QGISプロジェクトファイル名がセットされていません。出力先QGISプロジェクトファイル名をセットしてください。"
                   ))
            return

        self.exporter.setOutputGpkgPath(gpkgName)
        self.exporter.setOutputProjectPath(qgsName)
        self.exporter.setMinMaxClear(self.mMinMaxClearCheckBox.isChecked())

        # 表示エリアのエクステント以外ははじく ユーザ定義(直接入力)もはじく
        if self.mExtentGroupBox.extentState(
        ) != self.mExtentGroupBox.ExtentState.CurrentExtent:
            self.iface.messageBar().pushWarning(
                self.tr("エクステント指定"),
                self.tr("エクステント指定が不正です。キャンバスの領域を指定してください。"))
            return

        self.exporter.setExtent(self.mExtentGroupBox.outputExtent())

        self.btnOk.setEnabled(False)
        self.btnCancel.setEnabled(False)

        # デバッグするときはスレッドを使わないほうがデバッグしやすい
        self.thread.start()
        #self.exporter.clipAndExport()

    def updateProgress(self, value):
        self.mProgressBar.setValue(value)

    def logMessage(self, message, level=Qgis.Info):
        QgsMessageLog.logMessage(message, "cliplayers", level)
        self.mLogplainTextEdit.appendPlainText(message)

    def exportCanceled(self, message):
        self.iface.messageBar().pushWarning(self.tr("Export error"), message)
        self._restoreGui()

    def exportCompleted(self):
        self.iface.messageBar().pushSuccess(self.tr("Export completed"),
                                            self.tr("Export sucessfully."))

        #QDialog.accept(self)
        self.btnCancel.setEnabled(True)

    def _restoreGui(self):
        self.mProgressBar.setValue(0)
        self.btnOk.setEnabled(True)
        self.btnCancel.setEnabled(True)
        self.mLogplainTextEdit.clear()

    def _saveSettings(self):
        self.settings.setValue("minmaxclear",
                               self.mMinMaxClearCheckBox.isChecked())
class SimilarityPlugin:
    """
    Similarity Plugin parent class
    
    ..

    Attributes
    -----------

    dlg : SimilarityPluginDialog
        MainPluginDialog
    simpleDialog : SimpleWarningDialog
        Show simple warning
    similarLayer : list=[]
        The result of calculation process
    previewLayer: int=0
        Current index similarLayer that previewed in canvas widget  
    calcThread : QThread(self.iface)
        Thread for data processing
    calcTask : CalculationModule
        Calculation module for checking similarity
    iface : QgsInterface
        An interface instance that will be passed to this class
        which provides the hook by which you can manipulate the QGIS
        application at run time.

    """

    layer: QgsVectorLayer  #: First layer
    layer2: QgsVectorLayer  #: Second layer
    simpleDialog: SimpleWarnDialog  #: Simple warning dialog

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        self.similarLayer = []
        """  """
        self.previewLayer = 0
        self.calcTask = CalculationModule()
        # Save reference to the QGIS interface
        self.iface = iface

        # Registering worker calculation
        self.calcThread = QThread(self.iface)
        self.calcTask.moveToThread(self.calcThread)
        self.calcThread.started.connect(self.calcTask.run)
        self.calcThread.setTerminationEnabled(True)

        # multithreading signal calculation
        self.calcTask.progress.connect(self.updateCalcProgress)
        self.calcTask.progressSim.connect(self.updateSimList)
        self.calcTask.finished.connect(self.finishedCalcThread)
        self.calcTask.error.connect(self.errorCalcThread)
        self.calcTask.eventTask.connect(self.eventCalcThread)

        # pan event
        self.actionPan = QAction("Pan", self.iface)

        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(self.plugin_dir, 'i18n',
                                   'SimilarityPlugin_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Calculate Similarity Map')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None

    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('SimilarityPlugin', message)

    def add_action(self,
                   icon_path,
                   text,
                   callback,
                   enabled_flag=True,
                   add_to_menu=True,
                   add_to_toolbar=True,
                   status_tip=None,
                   whats_this=None,
                   parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToVectorMenu(self.menu, action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/similarity_plugin/icon-24.png'
        self.add_action(icon_path,
                        text=self.tr(u'Check Similarity ...'),
                        callback=self.run,
                        parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginVectorMenu(
                self.tr(u'&Calculate Similarity Map'), action)
            self.iface.removeToolBarIcon(action)

    def methodChange(self):
        """Signal when method changed"""
        if self.dlg.methodComboBox.currentIndex() == 2:
            # self.dlg.mergeCenterCheck.setChecked(False)
            # self.dlg.setPKBtn.setVisible(True)
            self.dlg.mergeCenterCheck.setEnabled(True)
            self.dlg.lineEditTreshold.setEnabled(False)
            self.dlg.nnRadiusEdit.setEnabled(False)
            # self.pkSelector.layerListWidget.clear()
            # self.pkSelector.layerListWidget.addItems(
            #     self.dlg.layerSel1.currentLayer().fields().names()
            # )
            # self.pkSelector.layer2ListWidget.clear()
            # self.pkSelector.layer2ListWidget.addItems(
            #     self.dlg.layerSel2.currentLayer().fields().names()
            # )
            # self.pkSelector.open()
        elif self.dlg.methodComboBox.currentIndex() == 0:
            self.dlg.mergeCenterCheck.setChecked(False)
            self.dlg.mergeCenterCheck.setEnabled(False)
            # self.dlg.setPKBtn.setVisible(False)
            self.dlg.lineEditTreshold.setEnabled(True)
            self.dlg.nnRadiusEdit.setEnabled(False)
        elif self.dlg.methodComboBox.currentIndex() == 1:
            self.dlg.mergeCenterCheck.setChecked(True)
            self.dlg.mergeCenterCheck.setEnabled(False)
            # self.dlg.setPKBtn.setVisible(False)
            self.dlg.lineEditTreshold.setEnabled(True)
            self.dlg.nnRadiusEdit.setEnabled(True)

    def resultPreview(self):
        """Activate preview section

        This method will called if calculation process is finished

        See Also
        ----------
            refreshPreview()
            SimilarityPluginDialog.widgetCanvas
            SimilarityPluginDialog.nextBtn
            SimilarityPluginDialog.previousBtn
            SimilarityPluginDialog.removeBtn

        """
        self.previewLayer = 0
        self.refreshPreview()

        self.dlg.widgetCanvas.enableAntiAliasing(True)

        self.dlg.nextBtn.setEnabled(True)
        self.dlg.previousBtn.setEnabled(True)
        self.dlg.removeBtn.setEnabled(True)

    def attrPrinter(self, fieldsList: object, feature: QgsFeature,
                    place: QTextEdit):
        """print the attribute table on preview panel

        :param fieldsList object: List the attribute value of feature
        :param feature QgsFeature: The feature will be printed
        :param place QTextEdit: The place for editing text
        
        """
        temp = ''

        for f in fieldsList:
            temp += f.name()
            temp += ' : '
            temp += str(feature.attribute(f.name()))
            temp += '\n'
        # print(place)
        place.setText(temp)

    def refreshPreview(self):
        """refreshing canvas on preview"""
        if len(self.similarLayer) > 0:
            # set the layer
            self.layerCanvas = QgsVectorLayer("Polygon?crs=ESPG:4326",
                                              'SimilarityLayer', 'memory')
            self.layer2Canvas = QgsVectorLayer("Polygon?crs=ESPG:4326",
                                               'SimilarityLayer', 'memory')

            # set the feature
            previewLayerFeature = self.calcTask.getLayersDup()[0].getFeature(
                self.similarLayer[self.previewLayer][0])
            previewLayerFeature2 = self.calcTask.getLayersDup()[1].getFeature(
                self.similarLayer[self.previewLayer][1])

            # set the label score on preview
            scoreLabel = "Score : " + str(
                round(self.similarLayer[self.previewLayer][2], 3))

            # set cumulative score on preview
            if (not self.calcTask.getTranslate()):
                scoreLabel += " - Cumulative score : " + str(
                    self.calcTask.getCumulative(
                        self.similarLayer[self.previewLayer]))

            # show distance if the layer merge centered (NN and WK only)
            if (self.dlg.methodComboBox.currentIndex() == 1
                    or self.dlg.methodComboBox.currentIndex() == 2):
                distance = QgsGeometry.distance(
                    previewLayerFeature.geometry().centroid(),
                    previewLayerFeature2.geometry().centroid())
                if distance < 0.00001:
                    distance = 0
                self.dlg.labelScore.setText(scoreLabel + " - Distance : " +
                                            str(round(distance, 3)))
            else:
                self.dlg.labelScore.setText(scoreLabel)

            self.attrPrinter(
                self.calcTask.getLayersDup()
                [0].dataProvider().fields().toList(), previewLayerFeature,
                self.dlg.previewAttr)
            self.attrPrinter(
                self.calcTask.getLayersDup()
                [1].dataProvider().fields().toList(), previewLayerFeature2,
                self.dlg.previewAttr_2)

            self.layerCanvas.dataProvider().addFeature(previewLayerFeature)

            # translating preview
            if self.calcTask.getTranslate():
                tGeom = self.calcTask.translateCenterGeom(
                    previewLayerFeature2.geometry(),
                    previewLayerFeature.geometry())
                nFeat = QgsFeature(previewLayerFeature2)
                nFeat.setGeometry(tGeom)
                self.layer2Canvas.dataProvider().addFeature(nFeat)
            else:
                self.layer2Canvas.dataProvider().addFeature(
                    previewLayerFeature2)

            # set canvas to preview feature layer
            self.dlg.widgetCanvas.setExtent(
                previewLayerFeature.geometry().boundingBox(), True)

            self.dlg.widgetCanvas.setDestinationCrs(
                self.layerCanvas.sourceCrs())

            symbol = self.layerCanvas.renderer().symbol()
            symbol.setColor(QColor(0, 147, 221, 127))

            symbol2 = self.layer2Canvas.renderer().symbol()
            symbol2.setColor(QColor(231, 120, 23, 127))

            self.dlg.widgetCanvas.setLayers(
                [self.layer2Canvas, self.layerCanvas])

            # redraw the canvas
            self.dlg.widgetCanvas.refresh()

    def nextPreview(self):
        """Next preview signal for next button in preview section"""
        # f2 = open("engine/f2.txt", "w")
        if (self.previewLayer < len(self.similarLayer) - 1):
            self.previewLayer = int(self.previewLayer) + 1
        # self.dlg.consoleTextEdit.setText(self.dlg.consoleTextEdit.toPlainText()+"\n\n Current Similar Layer Index : \n  "+str([self.similarLayer[self.previewLayer], self.previewLayer]))
        self.refreshPreview()

    def previousPreview(self):
        """Previous preview signal"""
        if (self.previewLayer > 0):
            self.previewLayer = int(self.previewLayer) - 1
        # self.dlg.consoleTextEdit.setText(self.dlg.consoleTextEdit.toPlainText()+"\n\n Current Similar Layer Index : \n  "+str([self.similarLayer[self.previewLayer], self.previewLayer]))
        self.refreshPreview()

    def rmFeatResult(self):
        """Removing similarity info current result"""
        self.similarLayer.pop(self.previewLayer)
        if (self.previewLayer > len(self.similarLayer)):
            self.previewLayer = len(self.similarLayer - 1)
        self.refreshPreview()
        self.warnDlg.close()

    def rmWarn(self):
        """prevention remove item preview"""
        self.warnDlg = self.warnDialogInit(
            'Are you sure to delete this feature ?')
        self.warnDlg.yesBtn.clicked.connect(self.rmFeatResult)
        self.warnDlg.noBtn.clicked.connect(self.warnDlg.close)
        self.warnDlg.show()

    def updateCalcProgress(self, value):
        """Progress signal for calcTask"""
        self.dlg.progressBar.setValue(int(round(value, 1)))

    def updateSimList(self, simList: list):
        """Updating similiarity result signal"""
        self.similarLayer.append(simList)
        cText = "Number of Result: " + str(len(self.similarLayer))
        self.dlg.counterLabel.setText(cText)

    # thread signal
    def errorCalcThread(self, value: str):
        """Signal when an error occured"""
        print("error : ", value)
        self.dlg.consoleTextEdit.append("error : " + value + "\n\n")
        self.simpleWarnDialogInit(value)
        self.dlg.calcBtn.setEnabled(True)
        self.dlg.stopBtn.setEnabled(False)

    def finishedCalcThread(self, itemVal: list):
        """signal when calcTask calculation is finished

        :param itemVal list: the returned value emit
        
        """
        # print("finished returned : ", itemVal)
        # self.similarLayer = itemVal
        # self.setLayers(self.calcTask.getLayersDup())
        self.calcThread.exit()
        self.calcTask.kill()
        cText = "Number of Result: " + str(len(self.similarLayer))
        self.dlg.consoleTextEdit.append(cText + "\n\n")
        if len(self.similarLayer) > 0:
            # self.addScoreItem()
            self.dlg.consoleTextEdit.append(
                "The 2 vector layer has been checked\n\n")
            self.previewLayer = 0
            self.dlg.saveBtn.setEnabled(True)
            self.dlg.counterLabel.setText(cText)
            self.resultPreview()
        else:
            self.previewLayer = 0
            self.dlg.counterLabel.setText(cText)
        self.dlg.calcBtn.setEnabled(True)
        self.dlg.stopBtn.setEnabled(False)

    def stopCalcThread(self):
        """Signal when calcTask is stopped """
        self.calcThread.exit()
        self.dlg.eventLabel.setText("Event: Stopped")
        self.calcTask.kill()
        if (self.calcTask.getLayersDup()[0].featureCount() > 0
                and self.calcTask.getLayersDup()[1].featureCount() > 0):
            cText = "Number of Result: " + str(len(self.similarLayer))
            self.dlg.consoleTextEdit.append(cText + "\n\n")
            if len(self.similarLayer) > 0:
                # self.addScoreItem()
                self.previewLayer = 0
                self.dlg.saveBtn.setEnabled(True)
                self.dlg.counterLabel.setText(cText)
                self.resultPreview()
            else:
                self.previewLayer = 0
                self.dlg.counterLabel.setText(cText)
            self.dlg.calcBtn.setEnabled(True)
            self.dlg.stopBtn.setEnabled(False)

    def eventCalcThread(self, value: str):
        """Receiving signal event
        
        :param value str: the returned value emit
        
        """
        self.dlg.eventLabel.setText("Event: " + value)

    # executing calculation
    def calculateScore(self):
        """Signal for executing calculation for cheking maps"""
        if (isinstance(self.dlg.layerSel1.currentLayer(), QgsVectorLayer) and
                isinstance(self.dlg.layerSel1.currentLayer(), QgsVectorLayer)):
            # set plugin to initial condition
            self.dlg.progressBar.setValue(0)
            self.dlg.saveBtn.setEnabled(False)
            self.dlg.nextBtn.setEnabled(False)
            self.dlg.previousBtn.setEnabled(False)
            self.dlg.removeBtn.setEnabled(False)
            self.dlg.widgetCanvas.setLayers([
                QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer',
                               'memory')
            ])
            self.dlg.previewAttr.setText("")
            self.dlg.previewAttr_2.setText("")
            self.dlg.widgetCanvas.refresh()
            scoreLabel = "Score : 0"
            self.dlg.counterLabel.setText("Number of Result: 0")
            self.dlg.labelScore.setText(scoreLabel)
            self.similarLayer = []

            # set input-output option
            self.calcTask.setLayers(self.dlg.layerSel1.currentLayer(),
                                    self.dlg.layerSel2.currentLayer())
            self.calcTask.setTreshold(self.dlg.lineEditTreshold.value())
            self.calcTask.setMethod(int(
                self.dlg.methodComboBox.currentIndex()))
            self.calcTask.setTranslate(self.dlg.mergeCenterCheck.isChecked())
            self.calcTask.setRadius(self.dlg.nnRadiusEdit.value())
            self.calcTask.setSuffix(str(self.dlg.sufLineEdit.text()))
            self.calcTask.setScoreName(str(self.dlg.attrOutLineEdit.text()))
            # print("input option set")
            # activating task
            self.calcTask.alive()
            # print("task alive")
            self.calcThread.start()
            # print("thread started")

            # set button
            self.dlg.calcBtn.setEnabled(False)
            self.dlg.stopBtn.setEnabled(True)
        else:
            # prevention on QgsVectorLayer only
            self.simpleWarnDialogInit("This plugin support Vector Layer only")

    # signal when saveBtn clicked
    def registerToProject(self):
        """Signal to registering project"""
        QgsProject.instance().addMapLayers(self.calcTask.getLayersResult())

    # warning dialog for error or prevention
    def warnDialogInit(self, msg: str):
        """This dialog have Yes and No button.
        :param msg: str Display the warning message 
        """

        dialog = WarnDialog()
        #set the message
        dialog.msgLabel.setText(msg)
        return dialog

    # initializing simple warning dialog
    def simpleWarnDialogInit(self, msg: str):
        """ This dialog have ok button only
        
        :param: msg str: Display the warning message 
        
        """

        # Set the message
        self.simpleDialog.msgLabel.setText(msg)
        self.simpleDialog.show()

    # def pkSelectorAccepted(self):
    #     if( len(self.pkSelector.layerListWidget.selectedItems()) > 0 and len(self.pkSelector.layer2ListWidget.selectedItems()) > 0 and
    #             (len(self.pkSelector.layerListWidget.selectedItems()) == len(self.pkSelector.layer2ListWidget.selectedItems()))
    #         ):
    #         names = [j.text() for j in self.pkSelector.layerListWidget.selectedItems()]
    #         names.sort()
    #         names2 = [j.text() for j in self.pkSelector.layer2ListWidget.selectedItems()]
    #         names2.sort()
    #         print(names)
    #         print(names2)
    #         self.pkSelector.accept()
    #     else:
    #         self.simpleWarnDialogInit("Primary Key must be same in length as key or not null")

    def run(self):
        """Run method that performs all the real work"""

        # print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())

        # init run variable
        # self.canvas = QgsMapCanvas()

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.previewLayer = 0
            self.currentCheckLayer = [0, 0]
            self.first_start = False
            self.dlg = SimilarityPluginDialog()
            # self.dlg.setPKBtn.setVisible(False)
            self.simpleDialog = SimpleWarnDialog()
            # self.pkSelector = PkSelector()
            # set help documentation
            self.dlg.helpTextBrowser.load(
                QUrl(
                    'https://github.com/panickspa/SimilarityPlugin/wiki/User-Guide'
                ))
            self.dlg.nextHelpBtn.clicked.connect(
                self.dlg.helpTextBrowser.forward)
            self.dlg.previousHelpBtn.clicked.connect(
                self.dlg.helpTextBrowser.back)
            # filtering selection layer (empty layer not allowed)
            self.dlg.layerSel1.setAllowEmptyLayer(False)
            self.dlg.layerSel1.setAllowEmptyLayer(False)

            # self.pkSelector.okPushButton.clicked.connect(self.pkSelectorAccepted)
            # self.dlg.setPKBtn.clicked.connect(self.pkSelector.open)
            # method combobox initialiazation
            self.dlg.methodComboBox.clear()
            self.dlg.methodComboBox.addItems(
                ['Squential', 'Nearest Neightbour', 'Wilkerstat BPS'])

            # registering signal

            self.dlg.methodComboBox.currentIndexChanged.connect(
                self.methodChange)
            self.dlg.nextBtn.clicked.connect(self.nextPreview)
            self.dlg.previousBtn.clicked.connect(self.previousPreview)
            self.dlg.calcBtn.clicked.connect(self.calculateScore)
            self.dlg.saveBtn.clicked.connect(self.registerToProject)
            self.dlg.removeBtn.clicked.connect(self.rmWarn)
            self.dlg.stopBtn.clicked.connect(self.stopCalcThread)

            # intialize pan tool
            panTool = QgsMapToolPan(self.dlg.widgetCanvas)
            # set signal
            panTool.setAction(self.actionPan)
            # set map tool
            self.dlg.widgetCanvas.setMapTool(panTool)
            # set pan tool to be activate
            panTool.activate()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            self.similarLayer = []
            self.dlg.widgetCanvas.setLayers([
                QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer',
                               'memory')
            ])
            self.dlg.previewAttr.setText("")
            self.dlg.previewAttr_2.setText("")
            self.dlg.widgetCanvas.refresh()
            scoreLabel = "Score : 0"
            self.dlg.labelScore.setText(scoreLabel)
    def enableFeaturesWithGPSToolbarItems(self):

        TOMsMessageLog.logMessage("In enablefeaturesWithGPSToolbarItems", level=Qgis.Warning)
        self.gpsAvailable = False
        self.closeTOMs = False

        self.tableNames = TOMsLayers(self.iface)
        self.params = gpsParams()

        self.tableNames.TOMsLayersNotFound.connect(self.setCloseTOMsFlag)
        #self.tableNames.gpsLayersNotFound.connect(self.setCloseCaptureGPSFeaturesFlag)
        self.params.TOMsParamsNotFound.connect(self.setCloseCaptureGPSFeaturesFlag)

        self.TOMsConfigFileObject = TOMsConfigFile()
        self.TOMsConfigFileObject.TOMsConfigFileNotFound.connect(self.setCloseTOMsFlag)
        self.TOMsConfigFileObject.initialiseTOMsConfigFile()

        self.tableNames.getLayers(self.TOMsConfigFileObject)

        self.prj = QgsProject().instance()
        self.dest_crs = self.prj.crs()
        TOMsMessageLog.logMessage("In captureGPSFeatures::init project CRS is " + self.dest_crs.description(),
                                 level=Qgis.Warning)
        self.transformation = QgsCoordinateTransform(QgsCoordinateReferenceSystem("EPSG:4326"), self.dest_crs,
                                                     self.prj)

        self.params.getParams()

        if self.closeTOMs:
            QMessageBox.information(self.iface.mainWindow(), "ERROR", ("Unable to start editing tool ..."))
            #self.actionProposalsPanel.setChecked(False)
            return   # TODO: allow function to continue without GPS enabled ...

        # Now check to see if the port is set. If not assume that just normal tools

        gpsPort = self.params.setParam("gpsPort")
        TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems: GPS port is: {}".format(gpsPort), level=Qgis.Warning)
        self.gpsConnection = None

        if gpsPort:
            self.gpsAvailable = True

        if self.gpsAvailable == True:
            self.curr_gps_location = None
            self.curr_gps_info = None

            TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems - GPS port is specified ",
                                     level=Qgis.Info)
            self.gps_thread = GPS_Thread(self.dest_crs, 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(functools.partial(self.gpsStopped))
            self.gps_thread.gpsError.connect(self.gpsErrorEncountered)
            #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)

            time.sleep(1.0)

            try:
                self.roamDistance = float(self.params.setParam("roamDistance"))
            except Exception as e:
                TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems:init: roamDistance issue: {}".format(e), level=Qgis.Warning)
                self.roamDistance = 5.0

        self.enableToolbarItems()

        self.createMapToolDict = {}
class NGWResourcesModelJob(QObject):
    started = pyqtSignal()
    statusChanged = pyqtSignal(str)
    warningOccurred = pyqtSignal(object)
    errorOccurred = pyqtSignal(object)
    finished = pyqtSignal()

    def __init__(self, parent, worker, model_response=None):
        """Create job.

            Arguments:
                job_id -- Job identification
                worker -- The class object inherits from NGWResourceModelJob
        """
        QObject.__init__(self, parent)
        self.__result = None
        self.__worker = worker
        self.__job_id = self.__worker.id
        self.__error = None
        self.__warnings = []
        # self.__job_id = "%s_%s" % (self.__worker.id, str(uuid.uuid1()))

        self.__worker.started.connect(self.started.emit)
        self.__worker.dataReceived.connect(self.__rememberResult)
        self.__worker.statusChanged.connect(self.statusChanged.emit)
        self.__worker.errorOccurred.connect(self.processJobError)
        self.__worker.warningOccurred.connect(self.processJobWarnings)

        self.model_response = model_response

    def setResponseObject(self, resp):
        self.model_response = resp
        self.model_response.job_id = self.__job_id

    def __rememberResult(self, result):
        self.__result = result

    def getJobId(self):
        return self.__job_id

    def getResult(self):
        return self.__result

    def error(self):
        return self.__error

    def processJobError(self, job_error):
        self.__error = job_error
        self.errorOccurred.emit(job_error)

    def processJobWarnings(self, job_error):
        if self.model_response:
            self.model_response._warnings.append(job_error)
        # self.warningOccurred.emit(job_error)

    def start(self):
        self.__thread = QThread(self)
        self.__worker.moveToThread(self.__thread)
        self.__worker.finished.connect(self.finishProcess)
        self.__thread.started.connect(self.__worker.run)

        self.__thread.start()

    def finishProcess(self):
        self.__worker.started.disconnect()
        self.__worker.dataReceived.disconnect()
        self.__worker.statusChanged.disconnect()
        self.__worker.errorOccurred.disconnect()
        self.__worker.warningOccurred.disconnect()
        self.__worker.finished.disconnect()

        self.__thread.quit()
        self.__thread.wait()

        self.finished.emit()
    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)
Beispiel #21
0
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 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):
     """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 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 ResourceSharingDialog(QDialog, FORM_CLASS):
    TAB_ALL = 0
    TAB_INSTALLED = 1
    TAB_SETTINGS = 2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Reload data and widget
        self.reload_data_and_widget()

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

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

        if not repo_name:
            return

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

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

        if not dlg.exec_():
            return

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

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

        new_auth_cfg = dlg.line_edit_auth_id.text()

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

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

        # Reload data and widget
        self.reload_data_and_widget()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            # Show  metadata
            self.show_collection_metadata(collection_id)

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

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

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

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

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

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

        self.progress_dialog.show()
class GisFIRELightnings:
    """
    GisFIRE Lightnings QGIS plugin implementation

    TODO: Add attributes information
    """
    iface: QgisInterface

    def __init__(self, iface: QgisInterface):
        """
        Constructor.

        :param iface: An interface instance that will be passed to this class which provides the hook by which you can
        manipulate the QGIS application at run time.
        :type iface: qgis.gui.QgisInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface

        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)

        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(self.plugin_dir, 'i18n',
                                   'gisfire_lightnings_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Initialization of UI references
        self._toolbar_actions = dict()
        self._menu_actions = dict()
        self._menu = None
        self._menu_gisfire = None
        self._toolbar = None
        self._dlg = None
        # Initialization of GisFIRE data layers
        self._layers = {}

    # noinspection PyMethodMayBeStatic
    def tr(self, message: str) -> str:
        """
        Get the translation for a string using Qt translation API.

        :param message: String for translation.
        :type message: str
        :returns: Translated version of message.
        :rtype: str
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('GisFIRELightnings', message)

    def __add_toolbar_actions(self):
        """
        Creates the toolbar buttons that GisFIRE Lightnings uses as shortcuts.
        """
        # Setup parameters
        action = QAction(QIcon(':/gisfire_lightnings/setup.png'),
                         self.tr('Setup GisFIRE Lightnings'), None)
        action.triggered.connect(
            self.__on_setup
        )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
        action.setEnabled(True)
        action.setCheckable(False)
        action.setStatusTip(self.tr('Setup GisFIRE Lightnings'))
        action.setWhatsThis(self.tr('Setup GisFIRE Lightnings'))
        self._toolbar.addAction(action)
        self._toolbar_actions['setup'] = action
        # Separator
        self._toolbar.addSeparator()
        # Download lightnings
        action = QAction(QIcon(':/gisfire_lightnings/download-lightnings.png'),
                         self.tr('Download Lightnings'), None)
        action.triggered.connect(
            self.__on_download_lightnings
        )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
        action.setEnabled(True)
        action.setCheckable(False)
        action.setStatusTip(self.tr('Download Lightnings'))
        action.setWhatsThis(self.tr('Download Lightnings'))
        self._toolbar.addAction(action)
        self._toolbar_actions['download-lightnings'] = action
        """
        # Clip lightnings
        action = QAction(
            QIcon(':/gisfire_lightnings/clip-lightnings.png'),
            self.tr('Clip lightnings on layer and features'),
            None
        )
        action.triggered.connect(self.onClipLightnings)
        action.setEnabled(True)
        action.setCheckable(False)
        action.setStatusTip(self.tr('Clip lightnings on layer and features'))
        action.setWhatsThis(self.tr('Clip lightnings on layer and features'))
        self._toolbar.addAction(action)
        self._toolbar_actions['clip-lightnings'] = action
        # Clip lightnings
        action = QAction(
            QIcon(':/gisfire_lightnings/filter-lightnings.png'),
            self.tr('Filter lightnings'),
            None
        )
        action.triggered.connect(self.onFilterLightnings)
        action.setEnabled(True)
        action.setCheckable(False)
        action.setStatusTip(self.tr('Filter lightnings'))
        action.setWhatsThis(self.tr('Filter lightnings'))
        self._toolbar.addAction(action)
        self._toolbar_actions['filter-lightnings'] = action
        # Process lightnings
        action = QAction(
            QIcon(':/gisfire_lightnings/process-lightnings.png'),
            self.tr('Calculate lightnings route'),
            None
        )
        action.triggered.connect(self.onProcessLightnings)
        action.setEnabled(True)
        action.setCheckable(False)
        action.setStatusTip(self.tr('Calculate lightnings route'))
        action.setWhatsThis(self.tr('Calculate lightnings route'))
        self._toolbar.addAction(action)
        self._toolbar_actions['process-lightnings'] = action"""

    def __add_menu_actions(self):
        """
        Creates the menu entries that allow GisFIRE procedures.
        """
        # Setup parameters
        action: QAction = self._menu.addAction(self.tr('Setup'))
        action.setIcon(QIcon(':/gisfire_lightnings/setup.png'))
        action.setIconVisibleInMenu(True)
        action.triggered.connect(
            self.__on_setup
        )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
        self._menu_actions['setup'] = action
        # Download lightnings
        action = self._menu.addAction(self.tr('Download Lightnings'))
        action.setIcon(QIcon(':/gisfire_lightnings/download-lightnings.png'))
        action.setIconVisibleInMenu(True)
        action.triggered.connect(
            self.__on_download_lightnings
        )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
        self._menu_actions['download-lightnings'] = action
        """# Clip lightnings
        action = self._menu.addAction(self.tr('Clip lightnings on layer and features'))
        action.setIcon(QIcon(':/gisfire_lightnings/clip-lightnings.png'))
        action.setIconVisibleInMenu(True)
        action.triggered.connect(self.onClipLightnings)
        self._menu_actions['clip-lightnings'] = action
        # Filter lightnings
        action = self._menu.addAction(self.tr('Filter lightnings'))
        action.setIcon(QIcon(':/gisfire_lightnings/filter-lightnings.png'))
        action.setIconVisibleInMenu(True)
        action.triggered.connect(self.onFilterLightnings)
        self._menu_actions['clip-lightnings'] = action
        # Process lightnings
        action = self._menu.addAction(self.tr('Calculate lightnings route'))
        action.setIcon(QIcon(':/gisfire_lightnings/process-lightnings.png'))
        action.setIconVisibleInMenu(True)
        action.triggered.connect(self.onProcessLightnings)
        self._menu_actions['process-lightnings'] = action"""

    def __add_relations(self):
        """
        Creates mutually exclusive relations between toolbar buttons.
        """
        pass

    # noinspection PyPep8Naming
    # noinspection DuplicatedCode
    def initGui(self):
        """
        Initializes the QGIS GUI for the GisFIRE Lightning plugin.
        """
        # Set up the menu
        menu_name = self.tr(u'Gis&FIRE')
        parent_menu = self.iface.mainWindow().menuBar()
        # Search if the menu exists (there are other GisFIRE modules installed)
        for action in parent_menu.actions():
            if action.text() == menu_name:
                self._menu_gisfire = action.menu()
        # Create the menu if it does not exist and add it to the current menubar
        if self._menu_gisfire is None:
            self._menu_gisfire = QMenu(menu_name, parent_menu)
            actions = parent_menu.actions()
            if len(actions) > 0:
                self.iface.mainWindow().menuBar().insertMenu(
                    actions[-1], self._menu_gisfire)
            else:
                self.iface.mainWindow().menuBar().addMenu(self._menu_gisfire)
        # Create Lightnings menu
        self._menu = QMenu(self.tr(u'Lightnings'), self._menu_gisfire)
        self._menu.setIcon(QIcon(':/gisfire_lightnings/lightnings.png'))
        self._menu_gisfire.addMenu(self._menu)
        # Set up the toolbar for lightnings plugin
        self._toolbar = self.iface.addToolBar(u'GisFIRE Lightnings')
        self._toolbar.setObjectName(u'GisFIRE Lightnings')

        # Add toolbar buttons
        self.__add_toolbar_actions()
        # Add menu entries
        self.__add_menu_actions()
        # Create relations with existing menus and buttons
        self.__add_relations()

    # noinspection DuplicatedCode
    def unload(self):
        """
        Removes the plugin menu item and icon from QGIS GUI.
        """
        # Remove toolbar items
        for action in self._toolbar_actions.values():
            action.triggered.disconnect()
            self.iface.removeToolBarIcon(action)
            action.deleteLater()
        # Remove toolbar
        if not (self._toolbar is None):
            self._toolbar.deleteLater()
        # Remove menu items
        for action in self._menu_actions.values():
            action.triggered.disconnect()
            self._menu.removeAction(action)
            action.deleteLater()
        # Remove menu
        if not (self._menu is None):
            self._menu.deleteLater()
        # Remove the menu_gisfire only if I'm the only GisFIRE module installed
        count = 0
        for name in qgis.utils.active_plugins:
            if name.startswith('gisfire'):
                count += 1
        if count == 1:
            if not (self._menu_gisfire is None):
                self.iface.mainWindow().menuBar().removeAction(
                    self._menu_gisfire.menuAction())
                self._menu_gisfire.menuAction().deleteLater()
                self._menu_gisfire.deleteLater()

    def __on_setup(self):
        # TODO: Improve setting enabling and disabling data providers
        self._dlg = DlgSettings(self.iface.mainWindow())
        qgs_settings = QgsSettings()
        # Get values and initialize dialog
        self._dlg.meteocat_api_key = qgs_settings.value(
            "gisfire_lightnings/meteocat_api_key", "")
        self._dlg.gisfire_api_url = qgs_settings.value(
            "gisfire_lightnings/gisfire_api_url", "")
        self._dlg.gisfire_api_username = qgs_settings.value(
            "gisfire_lightnings/gisfire_api_username", "")
        self._dlg.gisfire_api_token = qgs_settings.value(
            "gisfire_lightnings/gisfire_api_token", "")
        result = self._dlg.exec_()
        if result == QDialog.Accepted:
            # Store correct values
            qgs_settings.setValue("gisfire_lightnings/meteocat_api_key",
                                  self._dlg.meteocat_api_key)
            qgs_settings.setValue("gisfire_lightnings/gisfire_api_url",
                                  self._dlg.gisfire_api_url)
            qgs_settings.setValue("gisfire_lightnings/gisfire_api_username",
                                  self._dlg.gisfire_api_username)
            qgs_settings.setValue("gisfire_lightnings/gisfire_api_token",
                                  self._dlg.gisfire_api_token)

    def __on_download_lightnings(self):
        # Get values and initialize dialog
        qgs_settings: QgsSettings = QgsSettings()
        meteocat_api_key = qgs_settings.value(
            "gisfire_lightnings/meteocat_api_key", "")
        gisfire_api_url: str = qgs_settings.value(
            "gisfire_lightnings/gisfire_api_url", "")
        gisfire_api_username: str = qgs_settings.value(
            "gisfire_lightnings/gisfire_api_username", "")
        gisfire_api_token: str = qgs_settings.value(
            "gisfire_lightnings/gisfire_api_token", "")
        # Errors
        if meteocat_api_key == "" or len(meteocat_api_key) < 10:
            self.iface.messageBar().pushMessage(
                "",
                self.tr("MeteoCat API Key missing"),
                level=Qgis.Critical,
                duration=5)
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setText(self.tr("Error"))
            msg.setInformativeText(self.tr("MeteoCat API Key missing"))
            msg.setWindowTitle(self.tr("Error"))
            msg.exec_()
            return
        if gisfire_api_url == "" or len(gisfire_api_url) < 10:
            self.iface.messageBar().pushMessage(
                "",
                self.tr("GisFIRE API URL missing"),
                level=Qgis.Critical,
                duration=5)
            msg: QMessageBox = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setText(self.tr("Error"))
            msg.setInformativeText(self.tr("GisFIRE API URL missing"))
            msg.setWindowTitle(self.tr("Error"))
            msg.exec_()
            return
        if gisfire_api_username == "" or len(gisfire_api_username) < 1:
            self.iface.messageBar().pushMessage(
                "",
                self.tr("GisFIRE API Username missing"),
                level=Qgis.Critical,
                duration=5)
            msg: QMessageBox = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setText(self.tr("Error"))
            msg.setInformativeText(self.tr("GisFIRE API Username missing"))
            msg.setWindowTitle(self.tr("Error"))
            msg.exec_()
            return
        if gisfire_api_token == "" or len(gisfire_api_token) < 10:
            self.iface.messageBar().pushMessage(
                "",
                self.tr("GisFIRE API token missing"),
                level=Qgis.Critical,
                duration=5)
            msg: QMessageBox = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setText(self.tr("Error"))
            msg.setInformativeText(self.tr("GisFIRE API token missing"))
            msg.setWindowTitle(self.tr("Error"))
            msg.exec_()
            return
        # Check that the layers do not exist already and can cause conflicts
        layer_names: List[str] = [
            layer.name()
            for layer in QgsProject.instance().mapLayers().values()
        ]
        if self.tr('lightnings') in layer_names or self.tr(
                'lightnings-measurement-error') in layer_names:
            self.iface.messageBar().pushMessage(
                "",
                self.
                tr("Lightning layers exists, please remove them before downloading "
                   "new lightnings"),
                level=Qgis.Critical,
                duration=5)
            return
        # Show dialog
        dlg: DlgDownloadLightnings = DlgDownloadLightnings(
            self.iface.mainWindow(), ['MeteoCat'])
        result = dlg.exec_()
        if result == QDialog.Accepted:
            day: datetime.date = dlg.download_day
            self._thread_download_meteocat = QThread()
            self._worker_download_meteocat = DownloadLightningsUsingMeteocat(
                day, gisfire_api_url, gisfire_api_username, gisfire_api_token,
                meteocat_api_key, 25831)
            self._worker_download_meteocat.moveToThread(
                self._thread_download_meteocat)
            self._thread_download_meteocat.started.connect(
                self._worker_download_meteocat.run
            )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
            self._worker_download_meteocat.finished.connect(
                self._thread_download_meteocat.quit
            )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
            self._worker_download_meteocat.finished.connect(
                self._worker_download_meteocat.deleteLater
            )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
            self._thread_download_meteocat.finished.connect(
                self._thread_download_meteocat.deleteLater
            )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
            self._worker_download_meteocat.progress.connect(
                self.__report_progress_download_lightnings_meteocat
            )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
            self._worker_download_meteocat.data.connect(
                self.__received_data_download_lightnings_meteocat
            )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
            self._worker_download_meteocat.finished.connect(
                self.__report_end_download_lightnings_meteocat
            )  # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908
            self.iface.messageBar().pushMessage(
                "",
                self.tr(
                    "Downloading Meteo.cat Lightning data through GisFIRE API."
                ),
                level=Qgis.Info,
                duration=1)
            self._thread_download_meteocat.start()

    def __report_progress_download_lightnings_meteocat(self, n):
        pass

    def __report_end_download_lightnings_meteocat(self):
        self.iface.messageBar().pushMessage("",
                                            self.tr("Finished."),
                                            level=Qgis.Info,
                                            duration=1)

    def __received_data_download_lightnings_meteocat(
            self, lightnings: List[Lightning]):
        self.iface.messageBar().pushMessage(
            "",
            self.tr("Received {0:d} lightnings.".format(len(lightnings))),
            level=Qgis.Info,
            duration=1)
        self.lightnings_layer = create_lightnings_layer(
            'Point', 'lightnings', 25831)
        self.lightning_errors_layer = create_lightnings_layer(
            'Polygon', 'lightning-errors', 25831)
        add_layer_in_position(self.lightnings_layer, 1)
        add_layer_in_position(self.lightning_errors_layer, 2)
        for lightning in lightnings:
            add_lightning_point(self.lightnings_layer, lightning)
            add_lightning_polygon(self.lightning_errors_layer, lightning)
Beispiel #27
0
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)
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()
Beispiel #29
0
class OpenTripPlannerPlugin():
    """QGIS Plugin Implementation."""
    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """

        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir, 'i18n',
            'OpenTripPlannerPlugin_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&OpenTripPlanner Plugin')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('OpenTripPlannerPlugin', message)

    def add_action(self,
                   icon_path,
                   text,
                   callback,
                   enabled_flag=True,
                   add_to_menu=True,
                   add_to_toolbar=True,
                   status_tip=None,
                   whats_this=None,
                   parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToMenu(self.menu, action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/otp_plugin/icon.png'
        self.add_action(icon_path,
                        text=self.tr(u'OpenTripPlanner Plugin'),
                        callback=self.run,
                        parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(self.tr(u'&OpenTripPlanner Plugin'),
                                        action)
            self.iface.removeToolBarIcon(action)

    def isochronesStartWorker(self):  # method to start the worker thread
        if not self.gf.isochrones_selectedlayer:  # dont execute if no layer is selected
            QgsMessageLog.logMessage(
                "Warning! No inputlayer selected. Choose an inputlayer and try again.",
                MESSAGE_CATEGORY, Qgis.Critical)
            self.iface.messageBar().pushMessage(
                "Warning",
                " No inputlayer selected. Choose an inputlayer and try again.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
            return
        if self.gf.isochrones_selectedlayer.featureCount() == 0:
            QgsMessageLog.logMessage(
                "Warning! Inputlayer is empty. Add some features and try again.",
                MESSAGE_CATEGORY, Qgis.Critical)
            self.iface.messageBar().pushMessage(
                "Warning",
                " Inputlayer is empty. Add some features and try again.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
            return
        isochrones_memorylayer_vl = QgsVectorLayer(
            "MultiPolygon?crs=epsg:4326", "Isochrones",
            "memory")  # Create temporary polygon layer (output file)
        self.isochrones_thread = QThread()
        self.isochrones_worker = OpenTripPlannerPluginIsochronesWorker(
            self.dlg, self.iface, self.gf, isochrones_memorylayer_vl)
        # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis
        # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html
        self.isochrones_worker.moveToThread(
            self.isochrones_thread)  # move Worker-Class to a thread
        # Connect signals and slots:
        self.isochrones_thread.started.connect(self.isochrones_worker.run)
        self.isochrones_worker.isochrones_finished.connect(
            self.isochrones_thread.quit)
        self.isochrones_worker.isochrones_finished.connect(
            self.isochrones_worker.deleteLater)
        self.isochrones_thread.finished.connect(
            self.isochrones_thread.deleteLater)
        self.isochrones_worker.isochrones_progress.connect(
            self.isochronesReportProgress)
        self.isochrones_worker.isochrones_finished.connect(
            self.isochronesFinished)
        self.isochrones_thread.start()  # finally start the thread
        # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress
        self.gf.disableIsochronesGui()
        self.gf.disableGeneralSettingsGui()
        self.isochrones_thread.finished.connect(
            lambda: self.gf.enableIsochronesGui())
        self.isochrones_thread.finished.connect(
            lambda: self.gf.enableGeneralSettingsGui())

    def isochronesKillWorker(self):  # method to kill/cancel the worker thread
        # print('pushed cancel') # debugging
        # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html
        try:  # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except
            self.isochrones_worker.stop(
            )  # call the stop method in worker class to break the work-loop so we can quit the thread
            if self.isochrones_thread.isRunning(
            ):  # check if a thread is running
                # print('pushed cancel, thread is running, trying to cancel') # debugging
                self.isochrones_thread.requestInterruption()
                self.isochrones_thread.exit(
                )  # Tells the thread’s event loop to exit with a return code.
                self.isochrones_thread.quit(
                )  # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0).
                self.isochrones_thread.wait(
                )  # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait
        except:
            self.dlg.Isochrones_ProgressBar.setValue(0)
            self.dlg.Isochrones_StatusBox.setText('')

    def isochronesReportProgress(
            self, progress, status):  # method to report the progress to gui
        self.dlg.Isochrones_ProgressBar.setValue(
            progress)  # set the current progress in progress bar
        self.dlg.Isochrones_StatusBox.setText(status)

    def isochronesFinished(
        self,
        isochrones_resultlayer,
        isochrones_state,
        unique_errors="",
        runtime="00:00:00 (unknown)"
    ):  # method to interact with gui when thread is finished or canceled
        QgsProject.instance().addMapLayer(
            isochrones_resultlayer)  # Show resultlayer in project
        # isochrones_state is indicating different states of the thread/result as integer
        if unique_errors:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Errors occurred. Check the resultlayer for details. The errors were: "
                + unique_errors +
                " - Created dummy geometries at coordinate 0,0 on error features",
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=12)
            QgsMessageLog.logMessage(
                "Errors occurred. Check the resultlayer for details. The errors were: "
                + unique_errors +
                " - Created dummy geometries at coordinate 0,0 on error features",
                MESSAGE_CATEGORY, Qgis.Warning)
        if isochrones_state == 0:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Run-Method was never executed.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
        elif isochrones_state == 1:
            self.iface.messageBar().pushMessage(
                "Done!",
                " Isochrones job finished after " + runtime,
                MESSAGE_CATEGORY,
                level=Qgis.Success,
                duration=6)
        elif isochrones_state == 2:
            self.iface.messageBar().pushMessage(
                "Done!",
                " Isochrones job canceled after " + runtime,
                MESSAGE_CATEGORY,
                level=Qgis.Success,
                duration=6)
        elif isochrones_state == 3:
            self.iface.messageBar().pushMessage(
                "Warning",
                " No Isochrones to create - Check your settings and retry.",
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=6)
        elif isochrones_state == 99:
            self.iface.messageBar().pushMessage(
                "Debugging",
                " Just having some debugging fun :)",
                MESSAGE_CATEGORY,
                level=Qgis.Info,
                duration=6)
        else:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Unknown error occurred during execution.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)

    def aggregated_isochronesStartWorker(
            self):  # method to start the worker thread
        if not self.gf.aggregated_isochrones_selectedlayer:  # dont execute if no layer is selected
            QgsMessageLog.logMessage(
                "Warning! No inputlayer selected. Choose an inputlayer and try again.",
                MESSAGE_CATEGORY, Qgis.Critical)
            self.iface.messageBar().pushMessage(
                "Warning",
                " No inputlayer selected. Choose an inputlayer and try again.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
            return
        if self.gf.aggregated_isochrones_selectedlayer.featureCount() == 0:
            QgsMessageLog.logMessage(
                "Warning! Inputlayer is empty. Add some features and try again.",
                MESSAGE_CATEGORY, Qgis.Critical)
            self.iface.messageBar().pushMessage(
                "Warning",
                " Inputlayer is empty. Add some features and try again.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
            return
        aggregated_isochrones_memorylayer_vl = QgsVectorLayer(
            "MultiPolygon?crs=epsg:4326", "AggregatedIsochrones",
            "memory")  # Create temporary polygon layer (output file)
        self.aggregated_isochrones_thread = QThread()
        self.aggregated_isochrones_worker = OpenTripPlannerPluginAggregatedIsochronesWorker(
            self.dlg, self.iface, self.gf,
            aggregated_isochrones_memorylayer_vl)
        # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis
        # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html
        self.aggregated_isochrones_worker.moveToThread(
            self.aggregated_isochrones_thread)  # move Worker-Class to a thread
        # Connect signals and slots:
        self.aggregated_isochrones_thread.started.connect(
            self.aggregated_isochrones_worker.run)
        self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect(
            self.aggregated_isochrones_thread.quit)
        self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect(
            self.aggregated_isochrones_worker.deleteLater)
        self.aggregated_isochrones_thread.finished.connect(
            self.aggregated_isochrones_thread.deleteLater)
        self.aggregated_isochrones_worker.aggregated_isochrones_progress.connect(
            self.aggregated_isochronesReportProgress)
        self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect(
            self.aggregated_isochronesFinished)
        self.aggregated_isochrones_thread.start()  # finally start the thread
        # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress
        self.gf.disableAggregatedIsochronesGui()
        self.gf.disableGeneralSettingsGui()
        self.aggregated_isochrones_thread.finished.connect(
            lambda: self.gf.enableAggregatedIsochronesGui())
        self.aggregated_isochrones_thread.finished.connect(
            lambda: self.gf.enableGeneralSettingsGui())

    def aggregated_isochronesKillWorker(
            self):  # method to kill/cancel the worker thread
        # print('pushed cancel') # debugging
        # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html
        try:  # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except
            self.aggregated_isochrones_worker.stop(
            )  # call the stop method in worker class to break the work-loop so we can quit the thread
            if self.aggregated_isochrones_thread.isRunning(
            ):  # check if a thread is running
                # print('pushed cancel, thread is running, trying to cancel') # debugging
                self.aggregated_isochrones_thread.requestInterruption()
                self.aggregated_isochrones_thread.exit(
                )  # Tells the thread’s event loop to exit with a return code.
                self.aggregated_isochrones_thread.quit(
                )  # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0).
                self.aggregated_isochrones_thread.wait(
                )  # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait
        except:
            self.dlg.AggregatedIsochrones_ProgressBar.setValue(0)
            self.dlg.AggregatedIsochrones_StatusBox.setText('')

    def aggregated_isochronesReportProgress(
            self, progress, status):  # method to report the progress to gui
        self.dlg.AggregatedIsochrones_ProgressBar.setValue(
            progress)  # set the current progress in progress bar
        self.dlg.AggregatedIsochrones_StatusBox.setText(status)

    def aggregated_isochronesFinished(
        self,
        aggregated_isochrones_resultlayer,
        aggregated_isochrones_state,
        unique_errors="",
        runtime="00:00:00 (unknown)"
    ):  # method to interact with gui when thread is finished or canceled
        QgsProject.instance().addMapLayer(
            aggregated_isochrones_resultlayer)  # Show resultlayer in project
        # aggregated_isochrones_state is indicating different states of the thread/result as integer
        if unique_errors:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Errors occurred. Check the resultlayer for details. The errors were: "
                + unique_errors,
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=12)
            QgsMessageLog.logMessage(
                "Errors occurred. Check the resultlayer for details. The errors were: "
                + unique_errors, MESSAGE_CATEGORY, Qgis.Warning)
        if aggregated_isochrones_state == 0:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Run-Method was never executed.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
        elif aggregated_isochrones_state == 1:
            self.iface.messageBar().pushMessage(
                "Done!",
                " Isochrones job finished after " + runtime,
                MESSAGE_CATEGORY,
                level=Qgis.Success,
                duration=6)
        elif aggregated_isochrones_state == 2:
            self.iface.messageBar().pushMessage(
                "Done!",
                " Isochrones job canceled after " + runtime,
                MESSAGE_CATEGORY,
                level=Qgis.Success,
                duration=6)
        elif aggregated_isochrones_state == 3:
            self.iface.messageBar().pushMessage(
                "Warning",
                " No Isochrones to create - Check your settings and retry.",
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=6)
        elif aggregated_isochrones_state == 4:
            self.iface.messageBar().pushMessage(
                "Warning",
                " There is something wrong with your DateTime-Settings, check them and try again.",
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=6)
        elif aggregated_isochrones_state == 99:
            self.iface.messageBar().pushMessage(
                "Debugging",
                " Just having some debugging fun :)",
                MESSAGE_CATEGORY,
                level=Qgis.Info,
                duration=6)
        else:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Unknown error occurred during execution.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)

    def routesStartWorker(self):  # method to start the worker thread
        if not self.gf.routes_selectedlayer_source or not self.gf.routes_selectedlayer_target:
            QgsMessageLog.logMessage(
                "Warning! No inputlayer selected. Choose your inputlayers and try again.",
                MESSAGE_CATEGORY, Qgis.Critical)
            self.iface.messageBar().pushMessage(
                "Warning",
                " No sourcelayer or no targetlayer selected. Choose your inputlayers and try again.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
            return
        if self.gf.routes_selectedlayer_source.fields().count(
        ) == 0 or self.gf.routes_selectedlayer_target.fields().count() == 0:
            QgsMessageLog.logMessage(
                "Warning! Inputlayer has no fields. Script wont work until you add at least one dummy ID-Field.",
                MESSAGE_CATEGORY, Qgis.Critical)
            self.iface.messageBar().pushMessage(
                "Warning",
                " Inputlayer has no fields - Add at least a dummy-id field.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
            return
        if self.gf.routes_selectedlayer_source.featureCount(
        ) == 0 or self.gf.routes_selectedlayer_target.featureCount() == 0:
            QgsMessageLog.logMessage(
                "Warning! One or both inputlayers are empty. Add some features and try again.",
                MESSAGE_CATEGORY, Qgis.Critical)
            self.iface.messageBar().pushMessage(
                "Warning",
                " One or both inputlayers are empty. Add some features and try again.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
            return
        routes_memorylayer_vl = QgsVectorLayer(
            "LineString?crs=epsg:4326", "Routes",
            "memory")  # Create temporary polygon layer (output file)
        self.routes_thread = QThread()
        self.routes_worker = OpenTripPlannerPluginRoutesWorker(
            self.dlg, self.iface, self.gf, routes_memorylayer_vl)
        # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis
        # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html
        self.routes_worker.moveToThread(
            self.routes_thread)  # move Worker-Class to a thread
        # Connect signals and slots:
        self.routes_thread.started.connect(self.routes_worker.run)
        self.routes_worker.routes_finished.connect(self.routes_thread.quit)
        self.routes_worker.routes_finished.connect(
            self.routes_worker.deleteLater)
        self.routes_thread.finished.connect(self.routes_thread.deleteLater)
        self.routes_worker.routes_progress.connect(self.routesReportProgress)
        self.routes_worker.routes_finished.connect(self.routesFinished)
        self.routes_thread.start()  # finally start the thread
        # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress
        self.gf.disableRoutesGui()
        self.gf.disableGeneralSettingsGui()
        self.routes_thread.finished.connect(lambda: self.gf.enableRoutesGui())
        self.routes_thread.finished.connect(
            lambda: self.gf.enableGeneralSettingsGui())

    def routesKillWorker(self):  # method to kill/cancel the worker thread
        # print('pushed cancel') # debugging
        # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html
        try:  # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except
            self.routes_worker.stop(
            )  # call the stop method in worker class to break the work-loop so we can quit the thread
            if self.routes_thread.isRunning():  # check if a thread is running
                # print('pushed cancel, thread is running, trying to cancel') # debugging
                self.routes_thread.requestInterruption()
                self.routes_thread.exit(
                )  # Tells the thread’s event loop to exit with a return code.
                self.routes_thread.quit(
                )  # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0).
                self.routes_thread.wait(
                )  # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait
        except:
            self.dlg.Routes_ProgressBar.setValue(0)
            self.dlg.Routes_StatusBox.setText('')

    def routesReportProgress(self, progress,
                             status):  # method to report the progress to gui
        self.dlg.Routes_ProgressBar.setValue(
            progress)  # set the current progress in progress bar
        self.dlg.Routes_StatusBox.setText(status)

    def routesFinished(
        self,
        routes_resultlayer,
        routes_state,
        unique_errors="",
        runtime="00:00:00 (unknown)"
    ):  # method to interact with gui when thread is finished or canceled
        QgsProject.instance().addMapLayer(
            routes_resultlayer)  # Show resultlayer in project
        # routes_state is indicating different states of the thread/result as integer
        if unique_errors:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Errors occurred. Check the resultlayer for details. The errors were: "
                + unique_errors +
                " - Created dummy geometries at coordinate 0,0 on error features",
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=12)
            QgsMessageLog.logMessage(
                "Errors occurred. Check the resultlayer for details. The errors were: "
                + unique_errors +
                " - Created dummy geometries at coordinate 0,0 on error features",
                MESSAGE_CATEGORY, Qgis.Warning)
        if routes_state == 0:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Run-Method was never executed.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)
        elif routes_state == 1:
            self.iface.messageBar().pushMessage("Done!",
                                                " Routes job finished after " +
                                                runtime,
                                                MESSAGE_CATEGORY,
                                                level=Qgis.Success,
                                                duration=6)
        elif routes_state == 2:
            self.iface.messageBar().pushMessage("Done!",
                                                " Routes job canceled after " +
                                                runtime,
                                                MESSAGE_CATEGORY,
                                                level=Qgis.Success,
                                                duration=6)
        elif routes_state == 3:
            self.iface.messageBar().pushMessage(
                "Warning",
                " No Routes to create / no matching attributes - Check your settings and retry.",
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=6)
        elif routes_state == 99:
            self.iface.messageBar().pushMessage(
                "Debugging",
                " Just having some debugging fun :)",
                MESSAGE_CATEGORY,
                level=Qgis.Info,
                duration=6)
        else:
            self.iface.messageBar().pushMessage(
                "Warning",
                " Unknown error occurred during execution.",
                MESSAGE_CATEGORY,
                level=Qgis.Critical,
                duration=6)

    def run(self):
        """Run method that performs all the real work"""
        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = OpenTripPlannerPluginDialog()
            self.gf = OpenTripPlannerPluginGeneralFunctions(
                self.dlg, self.iface)
            # Calling maplayer selection on first startup to load layers into QgsMapLayerComboBox and initialize QgsOverrideButton stuff so selections can be done without actually using the QgsMapLayerComboBox (related to currentIndexChanged.connect(self.isochrones_maplayerselection) below)
            self.gf.routes_maplayerselection()
            self.gf.isochrones_maplayerselection()
            self.gf.aggregated_isochrones_maplayerselection()
            # Execute Main-Functions on Click: Placing them here prevents them from beeing executed multiple times, see https://gis.stackexchange.com/a/137161/107424
            self.dlg.Isochrones_RequestIsochrones.clicked.connect(
                lambda: self.isochronesStartWorker()
            )  #Call the start worker method
            self.dlg.Isochrones_Cancel.clicked.connect(
                lambda: self.isochronesKillWorker())
            self.dlg.AggregatedIsochrones_RequestIsochrones.clicked.connect(
                lambda: self.aggregated_isochronesStartWorker()
            )  #Call the start worker method
            self.dlg.AggregatedIsochrones_Cancel.clicked.connect(
                lambda: self.aggregated_isochronesKillWorker())
            self.dlg.Routes_RequestRoutes.clicked.connect(
                lambda: self.routesStartWorker())
            self.dlg.Routes_Cancel.clicked.connect(
                lambda: self.routesKillWorker())

            # Calling Functions on button click
            self.dlg.GeneralSettings_CheckServerStatus.clicked.connect(
                self.gf.check_server_status)
            self.dlg.GeneralSettings_Save.clicked.connect(
                self.gf.store_general_variables
            )  #Call store_general_variables function when clicking on save button
            self.dlg.GeneralSettings_Restore.clicked.connect(
                self.gf.restore_general_variables)
            self.dlg.Isochrones_SaveSettings.clicked.connect(
                self.gf.store_isochrone_variables)
            self.dlg.Isochrones_RestoreDefaultSettings.clicked.connect(
                self.gf.restore_isochrone_variables)
            self.dlg.Isochrones_Now.clicked.connect(
                self.gf.set_datetime_now_isochrone)
            self.dlg.AggregatedIsochrones_SaveSettings.clicked.connect(
                self.gf.store_aggregated_isochrone_variables)
            self.dlg.AggregatedIsochrones_RestoreDefaultSettings.clicked.connect(
                self.gf.restore_aggregated_isochrone_variables)
            self.dlg.AggregatedIsochrones_Now.clicked.connect(
                self.gf.set_datetime_now_aggregated_isochrone)
            self.dlg.Routes_SaveSettings.clicked.connect(
                self.gf.store_route_variables)
            self.dlg.Routes_RestoreDefaultSettings.clicked.connect(
                self.gf.restore_route_variables)
            self.dlg.Routes_Now.clicked.connect(self.gf.set_datetime_now_route)

            # Calling Functions to update layer stuff when layerselection has changed
            self.dlg.Isochrones_SelectInputLayer.currentIndexChanged.connect(
                self.gf.isochrones_maplayerselection
            )  # Call function isochrones_maplayerselection to update all selection related stuff when selection has been changed
            self.dlg.AggregatedIsochrones_SelectInputLayer.currentIndexChanged.connect(
                self.gf.aggregated_isochrones_maplayerselection)
            self.dlg.Routes_SelectInputLayer_Source.currentIndexChanged.connect(
                self.gf.routes_maplayerselection)
            self.dlg.Routes_SelectInputLayer_Target.currentIndexChanged.connect(
                self.gf.routes_maplayerselection)
            self.dlg.Routes_SelectInputField_Source.currentIndexChanged.connect(
                self.gf.routes_maplayerselection)  # or "fieldChanged"?
            self.dlg.Routes_SelectInputField_Target.currentIndexChanged.connect(
                self.gf.routes_maplayerselection)
            self.dlg.Routes_DataDefinedLayer_Source.stateChanged.connect(
                self.gf.routes_maplayerselection)
            self.dlg.Routes_DataDefinedLayer_Target.stateChanged.connect(
                self.gf.routes_maplayerselection)

        # Setting GUI stuff for startup every time the plugin is opened
        self.dlg.Isochrones_Date.setDateTime(
            QtCore.QDateTime.currentDateTime()
        )  # Set Dateselection to today on restart or firststart, only functional if never used save settings, otherwise overwritten by read_route_variables()
        self.dlg.AggregatedIsochrones_FromDateTime.setDateTime(
            QtCore.QDateTime.currentDateTime())
        self.dlg.AggregatedIsochrones_ToDateTime.setDateTime(
            QtCore.QDateTime.currentDateTime())
        self.dlg.Routes_Date.setDateTime(QtCore.QDateTime.currentDateTime())
        self.dlg.Isochrones_ProgressBar.setValue(
            0)  # Set Progressbar to 0 on restart or first start
        self.dlg.AggregatedIsochrones_ProgressBar.setValue(0)
        self.dlg.Routes_ProgressBar.setValue(0)
        self.dlg.GeneralSettings_ServerStatusResult.setText(
            "Serverstatus Unknown")
        self.dlg.GeneralSettings_ServerStatusResult.setStyleSheet(
            "background-color: white; color: black ")

        # Functions to execute every time the plugin is opened
        self.gf.read_general_variables(
        )  #Run Read-Stored-Variables-Function on every start
        self.gf.read_isochrone_variables()
        self.gf.read_aggregated_isochrone_variables()
        self.gf.read_route_variables()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.
            QgsMessageLog.logMessage(
                "OpenTripPlanner Plugin is already running! Close it before, if you wish to restart it.",
                MESSAGE_CATEGORY, Qgis.Warning)
            self.iface.messageBar().pushMessage(
                "Error",
                "OpenTripPlanner Plugin is already running! Close it before, if you wish to restart it.",
                MESSAGE_CATEGORY,
                level=Qgis.Warning,
                duration=6)
class ThreediDockWidget(QtWidgets.QDockWidget, FORM_CLASS):

    closingPlugin = pyqtSignal()

    def __init__(self, iface, plugin_settings, parent=None):
        """Constructor."""
        super().__init__(parent)
        self.setupUi(self)
        self.iface = iface
        self.plugin_settings = plugin_settings
        self.communication = UICommunication(self.iface,
                                             "3Di Models and Simulations",
                                             self.lv_log)
        self.simulations_progresses_thread = None
        self.simulations_progresses_sentinel = None
        self.threedi_api = None
        self.current_user = None
        self.current_user_full_name = None
        self.organisations = {}
        self.current_local_schematisation = None
        self.build_options = BuildOptions(self)
        self.simulation_overview_dlg = None
        self.simulation_results_dlg = None
        self.upload_dlg = None
        self.btn_log_in_out.clicked.connect(self.on_log_in_log_out)
        self.btn_load_schematisation.clicked.connect(
            self.build_options.load_local_schematisation)
        self.btn_load_revision.clicked.connect(
            self.build_options.load_local_schematisation)
        self.btn_new.clicked.connect(self.build_options.new_schematisation)
        self.btn_download.clicked.connect(
            self.build_options.download_schematisation)
        self.btn_upload.clicked.connect(self.show_upload_dialog)
        self.btn_simulate.clicked.connect(self.show_simulation_overview)
        self.btn_results.clicked.connect(self.show_simulation_results)
        self.btn_manage.clicked.connect(self.on_manage)
        self.plugin_settings.settings_changed.connect(self.on_log_out)
        set_icon(self.btn_new, "new.svg")
        set_icon(self.btn_download, "download.svg")
        set_icon(self.btn_upload, "upload.svg")
        set_icon(self.btn_simulate, "api.svg")
        set_icon(self.btn_results, "results.svg")
        set_icon(self.btn_manage, "manage.svg")
        set_icon(self.btn_log_in_out, "arrow.svg")
        set_icon(self.btn_load_schematisation, "arrow.svg")
        set_icon(self.btn_load_revision, "arrow.svg")

    def closeEvent(self, event):
        if self.threedi_api is not None:
            self.on_log_out()
        self.current_local_schematisation = None
        self.update_schematisation_view()
        self.closingPlugin.emit()
        event.accept()

    def on_log_in_log_out(self):
        """Trigger log-in or log-out action."""
        if self.threedi_api is None:
            self.on_log_in()
        else:
            self.on_log_out()

    def on_log_in(self):
        """Handle logging-in."""
        log_in_dialog = LogInDialog(self)
        log_in_dialog.show()
        QTimer.singleShot(10, log_in_dialog.log_in_threedi)
        log_in_dialog.exec_()
        if log_in_dialog.LOGGED_IN:
            self.threedi_api = log_in_dialog.threedi_api
            self.current_user = log_in_dialog.user
            self.current_user_full_name = log_in_dialog.user_full_name
            self.organisations = log_in_dialog.organisations
            self.initialize_authorized_view()

    def on_log_out(self):
        """Handle logging-out."""
        if self.simulations_progresses_thread is not None:
            self.stop_fetching_simulations_progresses()
            self.simulation_overview_dlg.model_selection_dlg.unload_cached_layers(
            )
            self.simulation_overview_dlg = None
        if self.simulation_results_dlg is not None:
            self.simulation_results_dlg.terminate_download_thread()
            self.simulation_results_dlg = None
        if self.upload_dlg:
            self.upload_dlg.hide()
            self.upload_dlg = None
        self.threedi_api = None
        self.current_user = None
        self.current_user_full_name = None
        self.organisations.clear()
        self.label_user.setText("-")
        set_icon(self.btn_log_in_out, "arrow.svg")
        self.btn_log_in_out.setToolTip("Log in")

    def update_schematisation_view(self):
        """Method for updating loaded schematisation labels."""
        if self.current_local_schematisation:
            schema_name = self.current_local_schematisation.name
            schema_dir = self.current_local_schematisation.main_dir
            schema_label_text = f'<a href="file:///{schema_dir}">{schema_name}</a>'
            schema_tooltip = f"{schema_name}\n{schema_dir}"
            self.label_schematisation.setText(schema_label_text)
            self.label_schematisation.setOpenExternalLinks(True)
            self.label_schematisation.setToolTip(schema_tooltip)
            if self.current_local_schematisation.wip_revision:
                self.label_revision.setText(
                    str(self.current_local_schematisation.wip_revision.number)
                    or "")
            else:
                self.label_revision.setText("")
        else:
            self.label_schematisation.setText("")
            self.label_schematisation.setToolTip("")
            self.label_revision.setText("")

    def initialize_authorized_view(self):
        """Method for initializing processes after logging in 3Di API."""
        set_icon(self.btn_log_in_out, "x.svg")
        self.btn_log_in_out.setToolTip("Log out")
        self.label_user.setText(self.current_user_full_name)
        self.initialize_simulations_progresses_thread()
        self.initialize_simulation_overview()
        self.initialize_simulation_results()

    def initialize_simulations_progresses_thread(self):
        """Initializing of the background thread."""
        if self.simulations_progresses_thread is not None:
            self.terminate_fetching_simulations_progresses_thread()
        self.simulations_progresses_thread = QThread()
        username, personal_api_key = self.plugin_settings.get_3di_auth()
        self.simulations_progresses_sentinel = WSProgressesSentinel(
            self.threedi_api, self.plugin_settings.wss_url, personal_api_key)
        self.simulations_progresses_sentinel.moveToThread(
            self.simulations_progresses_thread)
        self.simulations_progresses_sentinel.thread_finished.connect(
            self.on_fetching_simulations_progresses_finished)
        self.simulations_progresses_sentinel.thread_failed.connect(
            self.on_fetching_simulations_progresses_failed)
        self.simulations_progresses_thread.started.connect(
            self.simulations_progresses_sentinel.run)
        self.simulations_progresses_thread.start()

    def stop_fetching_simulations_progresses(self):
        """Changing 'thread_active' flag inside background task that is fetching simulations progresses."""
        if self.simulations_progresses_sentinel is not None:
            self.simulations_progresses_sentinel.stop_listening()

    def on_fetching_simulations_progresses_finished(self, msg):
        """Method for cleaning up background thread after it sends 'thread_finished'."""
        self.communication.bar_info(msg)
        self.simulations_progresses_thread.quit()
        self.simulations_progresses_thread.wait()
        self.simulations_progresses_thread = None
        self.simulations_progresses_sentinel = None

    def on_fetching_simulations_progresses_failed(self, msg):
        """Reporting fetching progresses failure."""
        self.communication.bar_error(msg, log_text_color=Qt.red)

    def terminate_fetching_simulations_progresses_thread(self):
        """Forcing termination of background thread if it's still running."""
        if self.simulations_progresses_thread is not None and self.simulations_progresses_thread.isRunning(
        ):
            self.communication.bar_info(
                "Terminating fetching simulations progresses thread.")
            self.simulations_progresses_thread.terminate()
            self.communication.bar_info(
                "Waiting for fetching simulations progresses thread termination."
            )
            self.simulations_progresses_thread.wait()
            self.communication.bar_info(
                "Fetching simulations progresses worker terminated.")
            self.simulations_progresses_thread = None
            self.simulations_progresses_sentinel = None

    def initialize_simulation_overview(self):
        """Initialization of the Simulation Overview window."""
        self.simulation_overview_dlg = SimulationOverview(self)
        self.simulation_overview_dlg.label_user.setText(
            self.current_user_full_name)

    @api_client_required
    def show_simulation_overview(self):
        """Showing Simulation Overview with running simulations progresses."""
        if self.simulation_overview_dlg is None:
            self.initialize_simulation_overview()
        self.simulation_overview_dlg.show()
        self.simulation_overview_dlg.raise_()
        self.simulation_overview_dlg.activateWindow()

    def initialize_simulation_results(self):
        """Initialization of the Simulations Results window."""
        self.simulation_results_dlg = SimulationResults(self)

    @api_client_required
    def show_simulation_results(self):
        """Showing finished simulations."""
        if self.simulation_results_dlg is None:
            self.initialize_simulation_results()
        self.simulation_results_dlg.show()
        self.simulation_results_dlg.raise_()
        self.simulation_results_dlg.activateWindow()

    def initialize_upload_status(self):
        """Initialization of the Upload Status dialog."""
        self.upload_dlg = UploadOverview(self)

    @api_client_required
    def show_upload_dialog(self):
        """Show upload status dialog."""
        if self.upload_dlg is None:
            self.initialize_upload_status()
        self.upload_dlg.show()
        self.upload_dlg.raise_()
        self.upload_dlg.activateWindow()

    @staticmethod
    def on_manage():
        """Open 3Di management webpage."""
        webbrowser.open("https://management.3di.live/")
class 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()
Beispiel #32
0
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"))
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())
Beispiel #34
0
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()
Beispiel #35
0
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)
            inputlayerwkbtext = QgsWkbTypes.displayString(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)
            joinlayerwkbtext = QgsWkbTypes.displayString(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.PointZ:
            self.showWarning(
                'The Z coordinate will be ignored for PointZ layers')
            return "PointZ"
        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:
            self.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 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)
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())
Beispiel #38
0
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())
Beispiel #39
0
class ResourceSharingDialog(QDialog, FORM_CLASS):
    TAB_ALL = 0
    TAB_INSTALLED = 1
    TAB_SETTINGS = 2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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