def populate_trv(self, trv_widget, result, expand=False): model = QStandardItemModel() trv_widget.setModel(model) trv_widget.setUniformRowHeights(False) main_parent = QStandardItem('{}'.format('Giswater')) font = main_parent.font() font.setPointSize(8) main_parent.setFont(font) self.icon_folder = self.plugin_dir + os.sep + 'icons' path_icon_blue = self.icon_folder + os.sep + '36.png' path_icon_red = self.icon_folder + os.sep + '100.png' if os.path.exists(path_icon_blue): icon = QIcon(path_icon_blue) main_parent.setIcon(icon) for group, functions in result['fields'].items(): parent1 = QStandardItem( f'{group} [{len(functions)} Giswater algorithm]') self.no_clickable_items.append( f'{group} [{len(functions)} Giswater algorithm]') functions.sort(key=self.sort_list, reverse=False) for function in functions: func_name = QStandardItem(str(function['functionname'])) label = QStandardItem(str(function['alias'])) font = label.font() font.setPointSize(8) label.setFont(font) row = self.controller.check_function(function['functionname']) if not row: if os.path.exists(path_icon_red): icon = QIcon(path_icon_red) label.setIcon(icon) label.setForeground(QColor(255, 0, 0)) msg = f"Function {function['functionname']}" \ f" configured on the table config_toolbox, but not found in the database" label.setToolTip(msg) self.no_clickable_items.append(str(function['alias'])) else: if os.path.exists(path_icon_blue): icon = QIcon(path_icon_blue) label.setIcon(icon) label.setToolTip(function['functionname']) enable_run = QStandardItem("True") if function['input_params'] is not None: if 'btnRunEnabled' in function['input_params']: bool_dict = {True: "True", False: "False"} enable_run = QStandardItem(bool_dict[ function['input_params']['btnRunEnabled']]) parent1.appendRow([label, func_name, enable_run]) main_parent.appendRow(parent1) model.appendRow(main_parent) index = model.indexFromItem(main_parent) trv_widget.expand(index) if expand: trv_widget.expandAll()
class DlgSqlLayerWindow(QWidget, Ui_Dialog): nameChanged = pyqtSignal(str) def __init__(self, iface, layer, parent=None): QWidget.__init__(self, parent) self.iface = iface self.layer = layer uri = QgsDataSourceUri(layer.source()) dbplugin = None db = None if layer.dataProvider().name() == 'postgres': dbplugin = createDbPlugin('postgis', 'postgres') elif layer.dataProvider().name() == 'spatialite': dbplugin = createDbPlugin('spatialite', 'spatialite') elif layer.dataProvider().name() == 'oracle': dbplugin = createDbPlugin('oracle', 'oracle') elif layer.dataProvider().name() == 'virtual': dbplugin = createDbPlugin('vlayers', 'virtual') elif layer.dataProvider().name() == 'ogr': dbplugin = createDbPlugin('gpkg', 'gpkg') if dbplugin: dbplugin.connectToUri(uri) db = dbplugin.db self.dbplugin = dbplugin self.db = db self.filter = "" self.allowMultiColumnPk = isinstance( db, PGDatabase ) # at the moment only PostgreSQL allows a primary key to span multiple columns, SpatiaLite doesn't self.aliasSubQuery = isinstance( db, PGDatabase) # only PostgreSQL requires subqueries to be aliases self.setupUi(self) self.setWindowTitle( u"%s - %s [%s]" % (self.windowTitle(), db.connection().connectionName(), db.connection().typeNameString())) self.defaultLayerName = 'QueryLayer' if self.allowMultiColumnPk: self.uniqueColumnCheck.setText( self.tr("Column(s) with unique values")) else: self.uniqueColumnCheck.setText( self.tr("Column with unique values")) self.editSql.setFocus() self.editSql.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.editSql.setMarginVisible(True) self.initCompleter() # allow copying results copyAction = QAction("copy", self) self.viewResult.addAction(copyAction) copyAction.setShortcuts(QKeySequence.Copy) copyAction.triggered.connect(self.copySelectedResults) self.btnExecute.clicked.connect(self.executeSql) self.btnSetFilter.clicked.connect(self.setFilter) self.btnClear.clicked.connect(self.clearSql) self.presetStore.clicked.connect(self.storePreset) self.presetDelete.clicked.connect(self.deletePreset) self.presetCombo.activated[str].connect(self.loadPreset) self.presetCombo.activated[str].connect(self.presetName.setText) self.editSql.textChanged.connect(self.updatePresetButtonsState) self.presetName.textChanged.connect(self.updatePresetButtonsState) self.presetCombo.currentIndexChanged.connect( self.updatePresetButtonsState) self.updatePresetsCombobox() self.geomCombo.setEditable(True) self.geomCombo.lineEdit().setReadOnly(True) self.uniqueCombo.setEditable(True) self.uniqueCombo.lineEdit().setReadOnly(True) self.uniqueModel = QStandardItemModel(self.uniqueCombo) self.uniqueCombo.setModel(self.uniqueModel) if self.allowMultiColumnPk: self.uniqueCombo.setItemDelegate(QStyledItemDelegate()) self.uniqueModel.itemChanged.connect( self.uniqueChanged) # react to the (un)checking of an item self.uniqueCombo.lineEdit().textChanged.connect( self.uniqueTextChanged ) # there are other events that change the displayed text and some of them can not be caught directly self.layerTypeWidget.hide() # show if load as raster is supported # self.loadLayerBtn.clicked.connect(self.loadSqlLayer) self.updateLayerBtn.clicked.connect(self.updateSqlLayer) self.getColumnsBtn.clicked.connect(self.fillColumnCombos) self.queryBuilderFirst = True self.queryBuilderBtn.setIcon(QIcon(":/db_manager/icons/sql.gif")) self.queryBuilderBtn.clicked.connect(self.displayQueryBuilder) self.presetName.textChanged.connect(self.nameChanged) # Update from layer # First the SQL from QgsDataSourceUri table sql = uri.table() if uri.keyColumn() == '_uid_': match = re.search( r'^\(SELECT .+ AS _uid_,\* FROM \((.*)\) AS _subq_.+_\s*\)$', sql, re.S | re.X) if match: sql = match.group(1) else: match = re.search(r'^\((SELECT .+ FROM .+)\)$', sql, re.S | re.X) if match: sql = match.group(1) # Need to check on table() since the parentheses were removed by the regexp if not uri.table().startswith('(') and not uri.table().endswith(')'): schema = uri.schema() if schema and schema.upper() != 'PUBLIC': sql = 'SELECT * FROM {0}.{1}'.format( self.db.connector.quoteId(schema), self.db.connector.quoteId(sql)) else: sql = 'SELECT * FROM {0}'.format( self.db.connector.quoteId(sql)) self.editSql.setText(sql) self.executeSql() # Then the columns self.geomCombo.setCurrentIndex( self.geomCombo.findText(uri.geometryColumn(), Qt.MatchExactly)) if uri.keyColumn() != '_uid_': self.uniqueColumnCheck.setCheckState(Qt.Checked) if self.allowMultiColumnPk: itemsData = uri.keyColumn().split(',') for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.data() in itemsData: item.setCheckState(Qt.Checked) else: keyColumn = uri.keyColumn() for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.data() == keyColumn: self.uniqueCombo.setCurrentIndex( self.uniqueModel.indexFromItem(item).row()) # Finally layer name, filter and selectAtId self.layerNameEdit.setText(layer.name()) self.filter = uri.sql() if uri.selectAtIdDisabled(): self.avoidSelectById.setCheckState(Qt.Checked) def getQueryHash(self, name): return 'q%s' % md5(name.encode('utf8')).hexdigest() def updatePresetButtonsState(self, *args): """Slot called when the combo box or the sql or the query name have changed: sets store button state""" self.presetStore.setEnabled( bool(self._getSqlQuery() and self.presetName.text())) self.presetDelete.setEnabled( bool(self.presetCombo.currentIndex() != -1)) def updatePresetsCombobox(self): self.presetCombo.clear() names = [] entries = QgsProject.instance().subkeyList('DBManager', 'savedQueries') for entry in entries: name = QgsProject.instance().readEntry( 'DBManager', 'savedQueries/' + entry + '/name')[0] names.append(name) for name in sorted(names): self.presetCombo.addItem(name) self.presetCombo.setCurrentIndex(-1) def storePreset(self): query = self._getSqlQuery() if query == "": return name = self.presetName.text() QgsProject.instance().writeEntry( 'DBManager', 'savedQueries/' + self.getQueryHash(name) + '/name', name) QgsProject.instance().writeEntry( 'DBManager', 'savedQueries/' + self.getQueryHash(name) + '/query', query) index = self.presetCombo.findText(name) if index == -1: self.presetCombo.addItem(name) self.presetCombo.setCurrentIndex(self.presetCombo.count() - 1) else: self.presetCombo.setCurrentIndex(index) def deletePreset(self): name = self.presetCombo.currentText() QgsProject.instance().removeEntry( 'DBManager', 'savedQueries/q' + self.getQueryHash(name)) self.presetCombo.removeItem(self.presetCombo.findText(name)) self.presetCombo.setCurrentIndex(-1) def loadPreset(self, name): query = QgsProject.instance().readEntry( 'DBManager', 'savedQueries/' + self.getQueryHash(name) + '/query')[0] name = QgsProject.instance().readEntry( 'DBManager', 'savedQueries/' + self.getQueryHash(name) + '/name')[0] self.editSql.setText(query) def clearSql(self): self.editSql.clear() self.editSql.setFocus() self.filter = "" def executeSql(self): sql = self._getSqlQuery() if sql == "": return with OverrideCursor(Qt.WaitCursor): # delete the old model old_model = self.viewResult.model() self.viewResult.setModel(None) if old_model: old_model.deleteLater() cols = [] quotedCols = [] try: # set the new model model = self.db.sqlResultModel(sql, self) self.viewResult.setModel(model) self.lblResult.setText( self.tr("{0} rows, {1:.1f} seconds").format( model.affectedRows(), model.secs())) cols = self.viewResult.model().columnNames() for col in cols: quotedCols.append(self.db.connector.quoteId(col)) except BaseError as e: DlgDbError.showError(e, self) self.uniqueModel.clear() self.geomCombo.clear() return self.setColumnCombos(cols, quotedCols) self.update() def _getSqlLayer(self, _filter): hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked if hasUniqueField: if self.allowMultiColumnPk: checkedCols = [] for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.checkState() == Qt.Checked: checkedCols.append(item.data()) uniqueFieldName = ",".join(checkedCols) elif self.uniqueCombo.currentIndex() >= 0: uniqueFieldName = self.uniqueModel.item( self.uniqueCombo.currentIndex()).data() else: uniqueFieldName = None else: uniqueFieldName = None hasGeomCol = self.hasGeometryCol.checkState() == Qt.Checked if hasGeomCol: geomFieldName = self.geomCombo.currentText() else: geomFieldName = None query = self._getSqlQuery() if query == "": return None # remove a trailing ';' from query if present if query.strip().endswith(';'): query = query.strip()[:-1] from qgis.core import QgsMapLayer layerType = QgsMapLayer.VectorLayer if self.vectorRadio.isChecked( ) else QgsMapLayer.RasterLayer # get a new layer name names = [] for layer in list(QgsProject.instance().mapLayers().values()): names.append(layer.name()) layerName = self.layerNameEdit.text() if layerName == "": layerName = self.defaultLayerName newLayerName = layerName index = 1 while newLayerName in names: index += 1 newLayerName = u"%s_%d" % (layerName, index) # create the layer layer = self.db.toSqlLayer(query, geomFieldName, uniqueFieldName, newLayerName, layerType, self.avoidSelectById.isChecked(), _filter) if layer.isValid(): return layer else: return None def loadSqlLayer(self): with OverrideCursor(Qt.WaitCursor): layer = self._getSqlLayer(self.filter) if layer is None: return QgsProject.instance().addMapLayers([layer], True) def updateSqlLayer(self): with OverrideCursor(Qt.WaitCursor): layer = self._getSqlLayer(self.filter) if layer is None: return # self.layer.dataProvider().setDataSourceUri(layer.dataProvider().dataSourceUri()) # self.layer.dataProvider().reloadData() XMLDocument = QDomDocument("style") XMLMapLayers = XMLDocument.createElement("maplayers") XMLMapLayer = XMLDocument.createElement("maplayer") self.layer.writeLayerXml(XMLMapLayer, XMLDocument, QgsReadWriteContext()) XMLMapLayer.firstChildElement( "datasource").firstChild().setNodeValue(layer.source()) XMLMapLayers.appendChild(XMLMapLayer) XMLDocument.appendChild(XMLMapLayers) self.layer.readLayerXml(XMLMapLayer, QgsReadWriteContext()) self.layer.reload() self.iface.actionDraw().trigger() self.iface.mapCanvas().refresh() def fillColumnCombos(self): query = self._getSqlQuery() if query == "": return with OverrideCursor(Qt.WaitCursor): # remove a trailing ';' from query if present if query.strip().endswith(';'): query = query.strip()[:-1] # get all the columns cols = [] quotedCols = [] connector = self.db.connector if self.aliasSubQuery: # get a new alias aliasIndex = 0 while True: alias = "_subQuery__%d" % aliasIndex escaped = re.compile('\\b("?)' + re.escape(alias) + '\\1\\b') if not escaped.search(query): break aliasIndex += 1 sql = u"SELECT * FROM (%s\n) AS %s LIMIT 0" % ( str(query), connector.quoteId(alias)) else: sql = u"SELECT * FROM (%s\n) WHERE 1=0" % str(query) c = None try: c = connector._execute(None, sql) cols = connector._get_cursor_columns(c) for col in cols: quotedCols.append(connector.quoteId(col)) except BaseError as e: DlgDbError.showError(e, self) self.uniqueModel.clear() self.geomCombo.clear() return finally: if c: c.close() del c self.setColumnCombos(cols, quotedCols) def setColumnCombos(self, cols, quotedCols): # get sensible default columns. do this before sorting in case there's hints in the column order (e.g., id is more likely to be first) try: defaultGeomCol = next( col for col in cols if col in ['geom', 'geometry', 'the_geom', 'way']) except: defaultGeomCol = None try: defaultUniqueCol = [col for col in cols if 'id' in col][0] except: defaultUniqueCol = None colNames = sorted(zip(cols, quotedCols)) newItems = [] uniqueIsFilled = False for (col, quotedCol) in colNames: item = QStandardItem(col) item.setData(quotedCol) item.setEnabled(True) item.setCheckable(self.allowMultiColumnPk) item.setSelectable(not self.allowMultiColumnPk) if self.allowMultiColumnPk: matchingItems = self.uniqueModel.findItems(col) if matchingItems: item.setCheckState(matchingItems[0].checkState()) uniqueIsFilled = uniqueIsFilled or matchingItems[ 0].checkState() == Qt.Checked else: item.setCheckState(Qt.Unchecked) newItems.append(item) if self.allowMultiColumnPk: self.uniqueModel.clear() self.uniqueModel.appendColumn(newItems) self.uniqueChanged() else: previousUniqueColumn = self.uniqueCombo.currentText() self.uniqueModel.clear() self.uniqueModel.appendColumn(newItems) if self.uniqueModel.findItems(previousUniqueColumn): self.uniqueCombo.setEditText(previousUniqueColumn) uniqueIsFilled = True oldGeometryColumn = self.geomCombo.currentText() self.geomCombo.clear() self.geomCombo.addItems(cols) self.geomCombo.setCurrentIndex( self.geomCombo.findText(oldGeometryColumn, Qt.MatchExactly)) # set sensible default columns if the columns are not already set try: if self.geomCombo.currentIndex() == -1: self.geomCombo.setCurrentIndex(cols.index(defaultGeomCol)) except: pass items = self.uniqueModel.findItems(defaultUniqueCol) if items and not uniqueIsFilled: if self.allowMultiColumnPk: items[0].setCheckState(Qt.Checked) else: self.uniqueCombo.setEditText(defaultUniqueCol) try: pass except: pass def copySelectedResults(self): if len(self.viewResult.selectedIndexes()) <= 0: return model = self.viewResult.model() # convert to string using tab as separator text = model.headerToString("\t") for idx in self.viewResult.selectionModel().selectedRows(): text += "\n" + model.rowToString(idx.row(), "\t") QApplication.clipboard().setText(text, QClipboard.Selection) QApplication.clipboard().setText(text, QClipboard.Clipboard) def initCompleter(self): dictionary = None if self.db: dictionary = self.db.connector.getSqlDictionary() if not dictionary: # use the generic sql dictionary from .sql_dictionary import getSqlDictionary dictionary = getSqlDictionary() wordlist = [] for name, value in dictionary.items(): wordlist += value # concat lists wordlist = list(set(wordlist)) # remove duplicates api = QsciAPIs(self.editSql.lexer()) for word in wordlist: api.add(word) api.prepare() self.editSql.lexer().setAPIs(api) def displayQueryBuilder(self): dlg = QueryBuilderDlg(self.iface, self.db, self, reset=self.queryBuilderFirst) self.queryBuilderFirst = False r = dlg.exec_() if r == QDialog.Accepted: self.editSql.setText(dlg.query) def _getSqlQuery(self): sql = self.editSql.selectedText() if len(sql) == 0: sql = self.editSql.text() return sql def uniqueChanged(self): # when an item is (un)checked, simply trigger an update of the combobox text self.uniqueTextChanged(None) def uniqueTextChanged(self, text): # Whenever there is new text displayed in the combobox, check if it is the correct one and if not, display the correct one. checkedItems = [] for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.checkState() == Qt.Checked: checkedItems.append(item.text()) label = ", ".join(checkedItems) if text != label: self.uniqueCombo.setEditText(label) def setFilter(self): from qgis.gui import QgsQueryBuilder layer = self._getSqlLayer("") if not layer: return dlg = QgsQueryBuilder(layer) dlg.setSql(self.filter) if dlg.exec_(): self.filter = dlg.sql() layer.deleteLater()
class DlgSqlLayerWindow(QWidget, Ui_Dialog): nameChanged = pyqtSignal(str) def __init__(self, iface, layer, parent=None): QWidget.__init__(self, parent) self.iface = iface self.layer = layer uri = QgsDataSourceUri(layer.source()) dbplugin = None db = None if layer.dataProvider().name() == 'postgres': dbplugin = createDbPlugin('postgis', 'postgres') elif layer.dataProvider().name() == 'spatialite': dbplugin = createDbPlugin('spatialite', 'spatialite') elif layer.dataProvider().name() == 'oracle': dbplugin = createDbPlugin('oracle', 'oracle') elif layer.dataProvider().name() == 'virtual': dbplugin = createDbPlugin('vlayers', 'virtual') elif layer.dataProvider().name() == 'ogr': dbplugin = createDbPlugin('gpkg', 'gpkg') if dbplugin: dbplugin.connectToUri(uri) db = dbplugin.db self.dbplugin = dbplugin self.db = db self.filter = "" self.allowMultiColumnPk = isinstance(db, PGDatabase) # at the moment only PostgreSQL allows a primary key to span multiple columns, spatialite doesn't self.aliasSubQuery = isinstance(db, PGDatabase) # only PostgreSQL requires subqueries to be aliases self.setupUi(self) self.setWindowTitle( u"%s - %s [%s]" % (self.windowTitle(), db.connection().connectionName(), db.connection().typeNameString())) self.defaultLayerName = 'QueryLayer' if self.allowMultiColumnPk: self.uniqueColumnCheck.setText(self.tr("Column(s) with unique values")) else: self.uniqueColumnCheck.setText(self.tr("Column with unique values")) self.editSql.setFocus() self.editSql.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.editSql.setMarginVisible(True) self.initCompleter() # allow copying results copyAction = QAction("copy", self) self.viewResult.addAction(copyAction) copyAction.setShortcuts(QKeySequence.Copy) copyAction.triggered.connect(self.copySelectedResults) self.btnExecute.clicked.connect(self.executeSql) self.btnSetFilter.clicked.connect(self.setFilter) self.btnClear.clicked.connect(self.clearSql) self.presetStore.clicked.connect(self.storePreset) self.presetDelete.clicked.connect(self.deletePreset) self.presetCombo.activated[str].connect(self.loadPreset) self.presetCombo.activated[str].connect(self.presetName.setText) self.updatePresetsCombobox() self.geomCombo.setEditable(True) self.geomCombo.lineEdit().setReadOnly(True) self.uniqueCombo.setEditable(True) self.uniqueCombo.lineEdit().setReadOnly(True) self.uniqueModel = QStandardItemModel(self.uniqueCombo) self.uniqueCombo.setModel(self.uniqueModel) if self.allowMultiColumnPk: self.uniqueCombo.setItemDelegate(QStyledItemDelegate()) self.uniqueModel.itemChanged.connect(self.uniqueChanged) # react to the (un)checking of an item self.uniqueCombo.lineEdit().textChanged.connect(self.uniqueTextChanged) # there are other events that change the displayed text and some of them can not be caught directly self.layerTypeWidget.hide() # show if load as raster is supported #self.loadLayerBtn.clicked.connect(self.loadSqlLayer) self.updateLayerBtn.clicked.connect(self.updateSqlLayer) self.getColumnsBtn.clicked.connect(self.fillColumnCombos) self.queryBuilderFirst = True self.queryBuilderBtn.setIcon(QIcon(":/db_manager/icons/sql.gif")) self.queryBuilderBtn.clicked.connect(self.displayQueryBuilder) self.presetName.textChanged.connect(self.nameChanged) # Update from layer # Fisrtly the SQL from QgsDataSourceUri table sql = uri.table() if uri.keyColumn() == '_uid_': match = re.search('^\(SELECT .+ AS _uid_,\* FROM \((.*)\) AS _subq_.+_\s*\)$', sql, re.S) if match: sql = match.group(1) else: match = re.search('^\((SELECT .+ FROM .+)\)$', sql, re.S) if match: sql = match.group(1) self.editSql.setText(sql) self.executeSql() # Then the columns self.geomCombo.setCurrentIndex(self.geomCombo.findText(uri.geometryColumn(), Qt.MatchExactly)) if uri.keyColumn() != '_uid_': self.uniqueColumnCheck.setCheckState(Qt.Checked) if self.allowMultiColumnPk: itemsData = uri.keyColumn().split(',') for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.data() in itemsData: item.setCheckState(Qt.Checked) else: keyColumn = uri.keyColumn() for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.data() == keyColumn: self.uniqueCombo.setCurrentIndex(self.uniqueModel.indexFromItem(item).row()) # Finally layer name, filter and selectAtId self.layerNameEdit.setText(layer.name()) self.filter = uri.sql() if uri.selectAtIdDisabled(): self.avoidSelectById.setCheckState(Qt.Checked) def updatePresetsCombobox(self): self.presetCombo.clear() names = [] entries = QgsProject.instance().subkeyList('DBManager', 'savedQueries') for entry in entries: name = QgsProject.instance().readEntry('DBManager', 'savedQueries/' + entry + '/name')[0] names.append(name) for name in sorted(names): self.presetCombo.addItem(name) self.presetCombo.setCurrentIndex(-1) def storePreset(self): query = self._getSqlQuery() if query == "": return name = self.presetName.text() QgsProject.instance().writeEntry('DBManager', 'savedQueries/q' + str(name.__hash__()) + '/name', name) QgsProject.instance().writeEntry('DBManager', 'savedQueries/q' + str(name.__hash__()) + '/query', query) index = self.presetCombo.findText(name) if index == -1: self.presetCombo.addItem(name) self.presetCombo.setCurrentIndex(self.presetCombo.count() - 1) else: self.presetCombo.setCurrentIndex(index) def deletePreset(self): name = self.presetCombo.currentText() QgsProject.instance().removeEntry('DBManager', 'savedQueries/q' + str(name.__hash__())) self.presetCombo.removeItem(self.presetCombo.findText(name)) self.presetCombo.setCurrentIndex(-1) def loadPreset(self, name): query = QgsProject.instance().readEntry('DBManager', 'savedQueries/q' + str(name.__hash__()) + '/query')[0] name = QgsProject.instance().readEntry('DBManager', 'savedQueries/q' + str(name.__hash__()) + '/name')[0] self.editSql.setText(query) def clearSql(self): self.editSql.clear() self.editSql.setFocus() self.filter = "" def executeSql(self): sql = self._getSqlQuery() if sql == "": return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # delete the old model old_model = self.viewResult.model() self.viewResult.setModel(None) if old_model: old_model.deleteLater() cols = [] quotedCols = [] try: # set the new model model = self.db.sqlResultModel(sql, self) self.viewResult.setModel(model) self.lblResult.setText(self.tr("%d rows, %.1f seconds") % (model.affectedRows(), model.secs())) cols = self.viewResult.model().columnNames() for col in cols: quotedCols.append(self.db.connector.quoteId(col)) except BaseError as e: QApplication.restoreOverrideCursor() DlgDbError.showError(e, self) self.uniqueModel.clear() self.geomCombo.clear() return self.setColumnCombos(cols, quotedCols) self.update() QApplication.restoreOverrideCursor() def _getSqlLayer(self, _filter): hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked if hasUniqueField: if self.allowMultiColumnPk: checkedCols = [] for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.checkState() == Qt.Checked: checkedCols.append(item.data()) uniqueFieldName = ",".join(checkedCols) elif self.uniqueCombo.currentIndex() >= 0: uniqueFieldName = self.uniqueModel.item(self.uniqueCombo.currentIndex()).data() else: uniqueFieldName = None else: uniqueFieldName = None hasGeomCol = self.hasGeometryCol.checkState() == Qt.Checked if hasGeomCol: geomFieldName = self.geomCombo.currentText() else: geomFieldName = None query = self._getSqlQuery() if query == "": return None # remove a trailing ';' from query if present if query.strip().endswith(';'): query = query.strip()[:-1] from qgis.core import QgsMapLayer, QgsMapLayerRegistry layerType = QgsMapLayer.VectorLayer if self.vectorRadio.isChecked() else QgsMapLayer.RasterLayer # get a new layer name names = [] for layer in list(QgsMapLayerRegistry.instance().mapLayers().values()): names.append(layer.name()) layerName = self.layerNameEdit.text() if layerName == "": layerName = self.defaultLayerName newLayerName = layerName index = 1 while newLayerName in names: index += 1 newLayerName = u"%s_%d" % (layerName, index) # create the layer layer = self.db.toSqlLayer(query, geomFieldName, uniqueFieldName, newLayerName, layerType, self.avoidSelectById.isChecked(), _filter) if layer.isValid(): return layer else: return None def loadSqlLayer(self): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: layer = self._getSqlLayer(self.filter) if layer == None: return from qgis.core import QgsMapLayerRegistry QgsMapLayerRegistry.instance().addMapLayers([layer], True) finally: QApplication.restoreOverrideCursor() def updateSqlLayer(self): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: layer = self._getSqlLayer(self.filter) if layer == None: return #self.layer.dataProvider().setDataSourceUri(layer.dataProvider().dataSourceUri()) #self.layer.dataProvider().reloadData() XMLDocument = QDomDocument("style") XMLMapLayers = XMLDocument.createElement("maplayers") XMLMapLayer = XMLDocument.createElement("maplayer") self.layer.writeLayerXML(XMLMapLayer, XMLDocument) XMLMapLayer.firstChildElement("datasource").firstChild().setNodeValue(layer.source()) XMLMapLayers.appendChild(XMLMapLayer) XMLDocument.appendChild(XMLMapLayers) self.layer.readLayerXML(XMLMapLayer) self.layer.reload() self.iface.actionDraw().trigger() self.iface.mapCanvas().refresh() finally: QApplication.restoreOverrideCursor() def fillColumnCombos(self): query = self._getSqlQuery() if query == "": return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # remove a trailing ';' from query if present if query.strip().endswith(';'): query = query.strip()[:-1] # get all the columns cols = [] quotedCols = [] connector = self.db.connector if self.aliasSubQuery: # get a new alias aliasIndex = 0 while True: alias = "_subQuery__%d" % aliasIndex escaped = re.compile('\\b("?)' + re.escape(alias) + '\\1\\b') if not escaped.search(query): break aliasIndex += 1 sql = u"SELECT * FROM (%s\n) AS %s LIMIT 0" % (str(query), connector.quoteId(alias)) else: sql = u"SELECT * FROM (%s\n) WHERE 1=0" % str(query) c = None try: c = connector._execute(None, sql) cols = connector._get_cursor_columns(c) for col in cols: quotedCols.append(connector.quoteId(col)) except BaseError as e: QApplication.restoreOverrideCursor() DlgDbError.showError(e, self) self.uniqueModel.clear() self.geomCombo.clear() return finally: if c: c.close() del c self.setColumnCombos(cols, quotedCols) QApplication.restoreOverrideCursor() def setColumnCombos(self, cols, quotedCols): # get sensible default columns. do this before sorting in case there's hints in the column order (eg, id is more likely to be first) try: defaultGeomCol = next(col for col in cols if col in ['geom', 'geometry', 'the_geom', 'way']) except: defaultGeomCol = None try: defaultUniqueCol = [col for col in cols if 'id' in col][0] except: defaultUniqueCol = None colNames = sorted(zip(cols, quotedCols)) newItems = [] uniqueIsFilled = False for (col, quotedCol) in colNames: item = QStandardItem(col) item.setData(quotedCol) item.setEnabled(True) item.setCheckable(self.allowMultiColumnPk) item.setSelectable(not self.allowMultiColumnPk) if self.allowMultiColumnPk: matchingItems = self.uniqueModel.findItems(col) if matchingItems: item.setCheckState(matchingItems[0].checkState()) uniqueIsFilled = uniqueIsFilled or matchingItems[0].checkState() == Qt.Checked else: item.setCheckState(Qt.Unchecked) newItems.append(item) if self.allowMultiColumnPk: self.uniqueModel.clear() self.uniqueModel.appendColumn(newItems) self.uniqueChanged() else: previousUniqueColumn = self.uniqueCombo.currentText() self.uniqueModel.clear() self.uniqueModel.appendColumn(newItems) if self.uniqueModel.findItems(previousUniqueColumn): self.uniqueCombo.setEditText(previousUniqueColumn) uniqueIsFilled = True oldGeometryColumn = self.geomCombo.currentText() self.geomCombo.clear() self.geomCombo.addItems(cols) self.geomCombo.setCurrentIndex(self.geomCombo.findText(oldGeometryColumn, Qt.MatchExactly)) # set sensible default columns if the columns are not already set try: if self.geomCombo.currentIndex() == -1: self.geomCombo.setCurrentIndex(cols.index(defaultGeomCol)) except: pass items = self.uniqueModel.findItems(defaultUniqueCol) if items and not uniqueIsFilled: if self.allowMultiColumnPk: items[0].setCheckState(Qt.Checked) else: self.uniqueCombo.setEditText(defaultUniqueCol) try: pass except: pass def copySelectedResults(self): if len(self.viewResult.selectedIndexes()) <= 0: return model = self.viewResult.model() # convert to string using tab as separator text = model.headerToString("\t") for idx in self.viewResult.selectionModel().selectedRows(): text += "\n" + model.rowToString(idx.row(), "\t") QApplication.clipboard().setText(text, QClipboard.Selection) QApplication.clipboard().setText(text, QClipboard.Clipboard) def initCompleter(self): dictionary = None if self.db: dictionary = self.db.connector.getSqlDictionary() if not dictionary: # use the generic sql dictionary from .sql_dictionary import getSqlDictionary dictionary = getSqlDictionary() wordlist = [] for name, value in dictionary.items(): wordlist += value # concat lists wordlist = list(set(wordlist)) # remove duplicates api = QsciAPIs(self.editSql.lexer()) for word in wordlist: api.add(word) api.prepare() self.editSql.lexer().setAPIs(api) def displayQueryBuilder(self): dlg = QueryBuilderDlg(self.iface, self.db, self, reset=self.queryBuilderFirst) self.queryBuilderFirst = False r = dlg.exec_() if r == QDialog.Accepted: self.editSql.setText(dlg.query) def _getSqlQuery(self): sql = self.editSql.selectedText() if len(sql) == 0: sql = self.editSql.text() return sql def uniqueChanged(self): # when an item is (un)checked, simply trigger an update of the combobox text self.uniqueTextChanged(None) def uniqueTextChanged(self, text): # Whenever there is new text displayed in the combobox, check if it is the correct one and if not, display the correct one. checkedItems = [] for item in self.uniqueModel.findItems("*", Qt.MatchWildcard): if item.checkState() == Qt.Checked: checkedItems.append(item.text()) label = ", ".join(checkedItems) if text != label: self.uniqueCombo.setEditText(label) def setFilter(self): from qgis.gui import QgsQueryBuilder layer = self._getSqlLayer("") if not layer: return dlg = QgsQueryBuilder(layer) dlg.setSql(self.filter) if dlg.exec_(): self.filter = dlg.sql() layer.deleteLater()
class QQuakeDialog(QDialog, FORM_CLASS): """ The main plugin dialog """ def __init__(self, iface, parent=None): # pylint:disable=too-many-statements """Constructor.""" super().__init__(parent) self.setupUi(self) self.setObjectName('QQuakeDialog') QgsGui.enableAutoGeometryRestore(self) self.scrollArea.setStyleSheet(""" QScrollArea { background: transparent; } QScrollArea > QWidget > QWidget { background: transparent; } QScrollArea > QWidget > QScrollBar { background: 1; } """) self.scrollArea_2.setStyleSheet(self.scrollArea.styleSheet()) self.scrollArea_3.setStyleSheet(self.scrollArea.styleSheet()) self.scrollArea_4.setStyleSheet(self.scrollArea.styleSheet()) self.splitter.setStretchFactor(0, 0) self.splitter_2.setStretchFactor(0, 0) self.splitter_3.setStretchFactor(0, 0) self.splitter_4.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.splitter_2.setStretchFactor(1, 1) self.splitter_3.setStretchFactor(1, 1) self.splitter_4.setStretchFactor(1, 1) self.fdsn_event_filter = FilterParameterWidget( iface, SERVICE_MANAGER.FDSNEVENT) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.fdsn_event_filter) self.fdsn_event_filter_container.setLayout(vl) self.earthquake_service_info_widget = ServiceInformationWidget(iface) self.fdsn_by_id_filter = FilterByIdWidget(iface, SERVICE_MANAGER.FDSNEVENT) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.fdsn_by_id_filter) self.fdsn_by_id_container.setLayout(vl) self.fdsn_by_url_widget = FetchByUrlWidget(iface, SERVICE_MANAGER.FDSNEVENT) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.fdsn_by_url_widget) self.fdsn_by_url_container.setLayout(vl) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.earthquake_service_info_widget) self.earthquake_service_info_container.setLayout(vl) self.macro_filter = FilterParameterWidget(iface, SERVICE_MANAGER.MACROSEISMIC) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_filter) self.macro_filter_container.setLayout(vl) self.macro_by_id_filter = FilterByIdWidget( iface, SERVICE_MANAGER.MACROSEISMIC) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_by_id_filter) self.macro_by_id_container.setLayout(vl) self.macro_by_url_widget = FetchByUrlWidget( iface, SERVICE_MANAGER.MACROSEISMIC) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_by_url_widget) self.macro_by_url_container.setLayout(vl) self.macro_service_info_widget = ServiceInformationWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_service_info_widget) self.macro_service_info_container.setLayout(vl) self.station_filter = FilterParameterWidget( iface, SERVICE_MANAGER.FDSNSTATION) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_filter) self.station_filter_container.setLayout(vl) self.station_by_id_filter = FilterStationByIdWidget( iface, SERVICE_MANAGER.FDSNSTATION) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_by_id_filter) self.station_by_id_container.setLayout(vl) self.station_service_info_widget = ServiceInformationWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_service_info_widget) self.station_service_info_container.setLayout(vl) self.station_by_url_widget = FetchByUrlWidget( iface, SERVICE_MANAGER.FDSNSTATION) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_by_url_widget) self.station_by_url_container.setLayout(vl) self.ogc_service_widget = OgcServiceWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.ogc_service_widget) self.ogc_widget_container.setLayout(vl) self.ogc_service_info_widget = ServiceInformationWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.ogc_service_info_widget) self.ogc_service_info_container.setLayout(vl) self.message_bar = QgsMessageBar() self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.verticalLayout.insertWidget(0, self.message_bar) self.fdsn_event_url_text_browser.viewport().setAutoFillBackground( False) self.fdsn_macro_url_text_browser.viewport().setAutoFillBackground( False) self.fdsn_station_url_text_browser.viewport().setAutoFillBackground( False) self.button_box.button(QDialogButtonBox.Ok).setText( self.tr('Fetch Data')) self.button_box.rejected.connect(self._save_settings) self.iface = iface # OGC self.ogc_combo.addItem(self.tr('Web Map Services (WMS)'), SERVICE_MANAGER.WMS) self.ogc_combo.addItem(self.tr('Web Feature Services (WFS)'), SERVICE_MANAGER.WFS) self.ogc_combo.currentIndexChanged.connect(self.refreshOgcWidgets) self.ogc_list_model = QStandardItemModel(self.ogc_list) self.ogc_list.setModel(self.ogc_list_model) self.ogc_list.selectionModel().selectionChanged.connect( self._ogc_service_changed) self._refresh_services() SERVICE_MANAGER.refreshed.connect(self._refresh_services) # connect to refreshing function to refresh the UI depending on the WS self._refresh_fdsnevent_widgets() self.refreshFdsnMacroseismicWidgets() self.refreshFdsnStationWidgets() # change the UI parameter according to the web service chosen self.fdsn_event_list.currentRowChanged.connect( self._refresh_fdsnevent_widgets) self.fdsn_macro_list.currentRowChanged.connect( self.refreshFdsnMacroseismicWidgets) self.fdsn_station_list.currentRowChanged.connect( self.refreshFdsnStationWidgets) self.fdsn_event_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_by_id_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_by_url_widget.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_event_list.currentRowChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.macro_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.macro_by_id_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.macro_by_url_widget.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.fdsn_macro_list.currentRowChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.station_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.station_by_id_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.fdsn_station_list.currentRowChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.station_by_url_widget.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.button_box.accepted.connect(self._getEventList) self.service_tab_widget.currentChanged.connect( lambda: self._refresh_url(None)) self.fetcher = None QgsGui.enableAutoGeometryRestore(self) self.fdsn_tab_widget.currentChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.macro_tab_widget.currentChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.fdsnstation_tab_widget.currentChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) for b in [ self.button_fdsn_new_service, self.button_macro_new_service, self.button_station_new_service, self.button_ogc_new_service ]: self._build_add_service_menu(b) for b in [ self.button_fdsn_edit_service, self.button_macro_edit_service, self.button_station_edit_service, self.button_ogc_edit_service ]: b.clicked.connect(self._edit_service) for b in [ self.button_fdsn_rename_service, self.button_macro_rename_service, self.button_station_rename_service, self.button_ogc_rename_service ]: b.clicked.connect(self._rename_service) for b in [ self.button_fdsn_remove_service, self.button_macro_remove_service, self.button_station_remove_service, self.button_ogc_remove_service ]: b.clicked.connect(self._remove_service) for b in [ self.button_fdsn_export_service, self.button_macro_export_service, self.button_station_export_service, self.button_ogc_export_service ]: b.clicked.connect(self._export_service) self._restore_settings() self._refresh_url(SERVICE_MANAGER.FDSNEVENT) self._refresh_url(SERVICE_MANAGER.MACROSEISMIC) self._refresh_url(SERVICE_MANAGER.FDSNSTATION) def closeEvent(self, e): # pylint: disable=missing-function-docstring self._save_settings() super().closeEvent(e) def _build_add_service_menu(self, widget): """ Builds the add service menu for a specific widget """ menu = QMenu() save_action = QAction(self.tr('Save Current Configuration As…'), parent=menu) save_action.setObjectName('save_action') menu.addAction(save_action) save_action.triggered.connect(self._save_configuration) import_action = QAction(self.tr('Import from File…'), parent=menu) menu.addAction(import_action) import_action.triggered.connect(self._import_configuration) create_new_action = QAction(self.tr('Create New Service…'), parent=menu) menu.addAction(create_new_action) create_new_action.triggered.connect(self._create_configuration) menu.aboutToShow.connect(lambda: self._menu_about_to_show(menu)) widget.setMenu(menu) def _menu_about_to_show(self, menu): """ Triggered when the Add Service menu is about to show """ save_current_action = menu.findChild(QAction, 'save_action') service_type = self.get_current_service_type() filter_widget = self.get_service_filter_widget(service_type) save_current_action.setEnabled( hasattr(filter_widget, 'to_service_definition')) def _refresh_services(self): """ Refreshes the list of available services """ # fill the FDSN listWidget with the dictionary keys self.fdsn_event_list.clear() self.fdsn_event_list.addItems( SERVICE_MANAGER.available_services(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_event_list.setCurrentRow(0) # fill the FDSN listWidget with the dictionary keys self.fdsn_macro_list.clear() self.fdsn_macro_list.addItems( SERVICE_MANAGER.available_services(SERVICE_MANAGER.MACROSEISMIC)) self.fdsn_macro_list.setCurrentRow(0) # fill the FDSN listWidget with the dictionary keys self.fdsn_station_list.clear() self.fdsn_station_list.addItems( SERVICE_MANAGER.available_services(SERVICE_MANAGER.FDSNSTATION)) self.fdsn_station_list.setCurrentRow(0) self.refreshOgcWidgets() def _save_configuration(self): """ Triggers saving the current service configuration """ service_type = self.get_current_service_type() name, ok = QInputDialog.getText( self, self.tr('Save Service Configuration'), self.tr('Save the current service configuration as')) if not name or not ok: return filter_widget = self.get_service_filter_widget(service_type) SERVICE_MANAGER.save_service(service_type, name, filter_widget.to_service_definition()) self.set_current_service(service_type, name) def _save_settings(self): """ Saves all settings currently defined in the dialog """ s = QgsSettings() if self.service_tab_widget.currentIndex( ) != self.service_tab_widget.count() - 1: s.setValue('/plugins/qquake/last_tab', self.service_tab_widget.currentIndex()) s.setValue('/plugins/qquake/fdsn_event_last_event_service', self.fdsn_event_list.currentItem().text()) s.setValue('/plugins/qquake/macro_last_event_service', self.fdsn_macro_list.currentItem().text()) s.setValue('/plugins/qquake/fdsnevent_last_tab', self.fdsn_tab_widget.currentIndex()) s.setValue('/plugins/qquake/macro_last_tab', self.macro_tab_widget.currentIndex()) s.setValue('/plugins/qquake/station_last_tab', self.fdsnstation_tab_widget.currentIndex()) self.fdsn_event_filter.save_settings('fdsn_event') self.fdsn_by_id_filter.save_settings('fdsn_event') self.fdsn_by_url_widget.save_settings('fdsn_event') self.macro_filter.save_settings('macro') self.macro_by_id_filter.save_settings('macro') self.macro_by_url_widget.save_settings('macro') self.station_filter.save_settings('stations') self.station_by_id_filter.save_settings('stations') self.station_by_url_widget.save_settings('stations') def _restore_settings(self): """ Restores dialog settings """ s = QgsSettings() last_tab = s.value('/plugins/qquake/last_tab') if last_tab is not None: self.service_tab_widget.setCurrentIndex(int(last_tab)) last_service = s.value('/plugins/qquake/fdsn_event_last_event_service') if last_service is not None: try: self.fdsn_event_list.setCurrentItem( self.fdsn_event_list.findItems(last_service, Qt.MatchContains)[0]) except IndexError: pass last_service = s.value('/plugins/qquake/macro_last_event_service') if last_service is not None: self.fdsn_macro_list.setCurrentItem( self.fdsn_macro_list.findItems(last_service, Qt.MatchContains)[0]) self.fdsn_event_filter.restore_settings('fdsn_event') self.fdsn_by_id_filter.restore_settings('fdsn_event') self.fdsn_by_url_widget.restore_settings('fdsn_event') self.macro_filter.restore_settings('macro') self.macro_by_id_filter.restore_settings('macro') self.macro_by_url_widget.restore_settings('fdsn_event') self.station_filter.restore_settings('stations') self.station_by_id_filter.restore_settings('stations') self.station_by_url_widget.restore_settings('stations') self.fdsn_tab_widget.setCurrentIndex( s.value('/plugins/qquake/fdsnevent_last_tab', 0, int)) self.macro_tab_widget.setCurrentIndex( s.value('/plugins/qquake/macro_last_tab', 0, int)) self.fdsnstation_tab_widget.setCurrentIndex( s.value('/plugins/qquake/station_last_tab', 0, int)) def get_current_service_id(self, service_type: str) -> Optional[str]: """ Returns the current selected service id """ if service_type == SERVICE_MANAGER.FDSNEVENT: service_id = self.fdsn_event_list.currentItem().text( ) if self.fdsn_event_list.currentItem() else None elif service_type == SERVICE_MANAGER.MACROSEISMIC: service_id = self.fdsn_macro_list.currentItem().text( ) if self.fdsn_macro_list.currentItem() else None elif service_type == SERVICE_MANAGER.FDSNSTATION: service_id = self.fdsn_station_list.currentItem().text( ) if self.fdsn_station_list.currentItem() else None elif service_type in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): service_id = self.ogc_list.selectionModel().selectedIndexes( )[0].data() if self.ogc_list.selectionModel().selectedIndexes( ) else None else: service_id = None return service_id def get_current_service_type(self) -> Optional[str]: """ Returns the current service type """ if self.service_tab_widget.currentIndex() == 0: service_type = SERVICE_MANAGER.FDSNEVENT elif self.service_tab_widget.currentIndex() == 1: service_type = SERVICE_MANAGER.MACROSEISMIC elif self.service_tab_widget.currentIndex() == 2: service_type = SERVICE_MANAGER.FDSNSTATION elif self.service_tab_widget.currentIndex() == 3: return self.ogc_combo.currentData() else: service_type = None return service_type def get_service_filter_widget( self, # pylint:disable=too-many-branches service_type: str ) -> Optional[ Union[FilterParameterWidget, FilterByIdWidget, FetchByUrlWidget, FilterStationByIdWidget, OgcServiceWidget]]: """ Returns the service filter widget for a specific service type """ widget = None if service_type == SERVICE_MANAGER.FDSNEVENT: if self.fdsn_tab_widget.currentIndex() in (0, 3): widget = self.fdsn_event_filter elif self.fdsn_tab_widget.currentIndex() == 1: widget = self.fdsn_by_id_filter elif self.fdsn_tab_widget.currentIndex() == 2: widget = self.fdsn_by_url_widget elif service_type == SERVICE_MANAGER.MACROSEISMIC: if self.macro_tab_widget.currentIndex() in (0, 3): widget = self.macro_filter elif self.macro_tab_widget.currentIndex() == 1: widget = self.macro_by_id_filter elif self.macro_tab_widget.currentIndex() == 2: widget = self.macro_by_url_widget elif service_type == SERVICE_MANAGER.FDSNSTATION: if self.fdsnstation_tab_widget.currentIndex() in (0, 3): widget = self.station_filter elif self.fdsnstation_tab_widget.currentIndex() == 1: widget = self.station_by_id_filter elif self.fdsnstation_tab_widget.currentIndex() == 2: widget = self.station_by_url_widget elif service_type in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): widget = self.ogc_service_widget return widget def get_fetcher(self, service_type: Optional[str] = None): """ Returns a quake fetcher corresponding to the current dialog settings """ if service_type is None: service_type = self.get_current_service_type() service = self.get_current_service_id(service_type) if not service: return None filter_widget = self.get_service_filter_widget(service_type) service_config = SERVICE_MANAGER.service_details(service_type, service) if isinstance(filter_widget, FilterParameterWidget): fetcher = Fetcher( service_type=service_type, event_service=service, event_start_date=filter_widget.start_date(), event_end_date=filter_widget.end_date(), event_min_magnitude=filter_widget.min_magnitude(), event_max_magnitude=filter_widget.max_magnitude(), limit_extent_rect=filter_widget.extent_rect(), min_latitude=filter_widget.min_latitude(), max_latitude=filter_widget.max_latitude(), min_longitude=filter_widget.min_longitude(), max_longitude=filter_widget.max_longitude(), limit_extent_circle=filter_widget.limit_extent_circle(), circle_latitude=filter_widget.circle_latitude(), circle_longitude=filter_widget.circle_longitude(), circle_min_radius=filter_widget.circle_min_radius(), circle_max_radius=filter_widget.circle_max_radius(), circle_radius_unit=filter_widget.circle_radius_unit(), earthquake_number_mdps_greater=filter_widget. earthquake_number_mdps_greater(), earthquake_max_intensity_greater=filter_widget. earthquake_max_intensity_greater(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget.convert_negative_depths( ), depth_unit=filter_widget.depth_unit(), event_type=filter_widget.event_type(), updated_after=filter_widget.updated_after()) elif isinstance(filter_widget, FilterByIdWidget): if not service_config['settings'].get('queryeventid'): fetcher = None else: fetcher = Fetcher( service_type=service_type, event_service=service, event_ids=filter_widget.ids(), contributor_id=filter_widget.contributor_id(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget. convert_negative_depths(), depth_unit=filter_widget.depth_unit()) elif isinstance(filter_widget, FetchByUrlWidget): fetcher = Fetcher(service_type=service_type, event_service=service, url=filter_widget.url(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget. convert_negative_depths(), depth_unit=filter_widget.depth_unit()) elif isinstance(filter_widget, FilterStationByIdWidget): fetcher = Fetcher(service_type=service_type, event_service=service, network_codes=filter_widget.network_codes(), station_codes=filter_widget.station_codes(), locations=filter_widget.locations(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget. convert_negative_depths(), depth_unit=filter_widget.depth_unit()) return fetcher def _refresh_url(self, service_type: Optional[str] = None): """ Updates the service URL """ if not service_type: service_type = self.get_current_service_type() if service_type is None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) return if service_type not in (SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC, SERVICE_MANAGER.FDSNSTATION): self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) return fetcher = self.get_fetcher(service_type) if not fetcher: self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) return self._valid_changed() if service_type == SERVICE_MANAGER.FDSNEVENT: self.fdsn_event_url_text_browser.setText( '<a href="{0}">{0}</a>'.format(fetcher.generate_url())) elif service_type == SERVICE_MANAGER.MACROSEISMIC: self.fdsn_macro_url_text_browser.setText( '<a href="{0}">{0}</a>'.format(fetcher.generate_url())) elif service_type == SERVICE_MANAGER.FDSNSTATION: self.fdsn_station_url_text_browser.setText( '<a href="{0}">{0}</a>'.format(fetcher.generate_url())) def _valid_changed(self): """ Called when dialog entry validation should occur """ service_type = self.get_current_service_type() if service_type not in (SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC, SERVICE_MANAGER.FDSNSTATION): self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) return filter_widget = self.get_service_filter_widget(service_type) self.button_box.button(QDialogButtonBox.Ok).setEnabled( filter_widget.is_valid()) def _update_service_widgets( self, # pylint: disable=too-many-locals,too-many-branches service_type, service_id, filter_widget, filter_by_id_widget, fetch_by_url_widget, info_widget, remove_service_button, edit_service_button, rename_service_button, tab_widget): """ Updates all widgets to reflect the current service details """ service_config = SERVICE_MANAGER.service_details( service_type, service_id) date_start = QDateTime.fromString(service_config['datestart'], Qt.ISODate) default_date_start = QDateTime.fromString( service_config['default']['datestart'], Qt.ISODate) if service_config['default'].get('datestart') else None # if the dateend is not set in the config.json set the date to NOW date_end = QDateTime.fromString( service_config['dateend'], Qt.ISODate) if 'dateend' in service_config and service_config[ 'dateend'] else None default_date_end = QDateTime.fromString( service_config['default']['dateend'], Qt.ISODate) if service_config['default'].get('dateend') else None filter_widget.set_date_range_limits(date_start, date_end) filter_widget.set_current_date_range(default_date_start, default_date_end) if service_config['default'].get('boundingboxpredefined'): filter_widget.set_predefined_bounding_box( service_config['default'].get('boundingboxpredefined')) if service_config['default'].get('minimumlatitude'): filter_widget.set_min_latitude( service_config['default'].get('minimumlatitude')) if service_config['default'].get('maximumlatitude'): filter_widget.set_max_latitude( service_config['default'].get('maximumlatitude')) if service_config['default'].get('minimumlongitude'): filter_widget.set_min_longitude( service_config['default'].get('minimumlongitude')) if service_config['default'].get('maximumlongitude'): filter_widget.set_max_longitude( service_config['default'].get('maximumlongitude')) if service_config['default'].get('circlelatitude'): filter_widget.set_circle_latitude( service_config['default'].get('circlelatitude')) if service_config['default'].get('circlelongitude'): filter_widget.set_circle_longitude( service_config['default'].get('circlelongitude')) if service_config['default'].get('minimumcircleradius'): filter_widget.set_min_circle_radius( service_config['default'].get('minimumcircleradius')) if service_config['default'].get('maximumcircleradius'): filter_widget.set_max_circle_radius( service_config['default'].get('maximumcircleradius')) if service_config['default'].get('minimummagnitude'): filter_widget.set_min_magnitude( service_config['default'].get('minimummagnitude')) if service_config['default'].get('maximummagnitude'): filter_widget.set_max_magnitude( service_config['default'].get('maximummagnitude')) if service_config['default'].get('macromaxintensitygreater'): filter_widget.set_max_intensity_greater( service_config['default'].get('macromaxintensitygreater')) if service_config['default'].get('macromdpsgreaterthan'): filter_widget.set_mdps_greater_than( service_config['default'].get('macromdpsgreaterthan')) if service_config['default'].get('eventtype'): filter_widget.set_event_type( service_config['default'].get('eventtype')) updated_after = QDateTime.fromString( service_config['default']['updatedafter'], Qt.ISODate ) if service_config['default'].get('updatedafter') else None if updated_after: filter_widget.set_updated_after(updated_after) filter_widget.set_extent_limit( service_config.get('boundingbox', [-180, -90, 180, 90])) if service_type in [ SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC ]: tab_widget.widget(1).setEnabled(service_config['settings'].get( 'queryeventid', False)) info_widget.set_service(service_type=service_type, service_id=service_id) filter_widget.set_service_id(service_id) filter_by_id_widget.set_service_id(service_id) if fetch_by_url_widget is not None: fetch_by_url_widget.set_service_id(service_id) remove_service_button.setEnabled(not service_config['read_only']) edit_service_button.setEnabled(not service_config['read_only']) rename_service_button.setEnabled(not service_config['read_only']) def _refresh_fdsnevent_widgets(self): """ Refreshing the FDSN-Event UI depending on the WS chosen """ if not self.fdsn_event_list.currentItem(): return service_id = self.fdsn_event_list.currentItem().text() self._update_service_widgets( service_type=SERVICE_MANAGER.FDSNEVENT, service_id=service_id, filter_widget=self.fdsn_event_filter, filter_by_id_widget=self.fdsn_by_id_filter, fetch_by_url_widget=self.fdsn_by_url_widget, info_widget=self.earthquake_service_info_widget, remove_service_button=self.button_fdsn_remove_service, edit_service_button=self.button_fdsn_edit_service, rename_service_button=self.button_fdsn_rename_service, tab_widget=self.fdsn_tab_widget) def refreshFdsnMacroseismicWidgets(self): """ Refreshing the FDSN-Macroseismic UI depending on the WS chosen """ if not self.fdsn_macro_list.currentItem(): return service_id = self.fdsn_macro_list.currentItem().text() self._update_service_widgets( service_type=SERVICE_MANAGER.MACROSEISMIC, service_id=service_id, filter_widget=self.macro_filter, filter_by_id_widget=self.macro_by_id_filter, fetch_by_url_widget=self.macro_by_url_widget, info_widget=self.macro_service_info_widget, remove_service_button=self.button_macro_remove_service, edit_service_button=self.button_macro_edit_service, rename_service_button=self.button_macro_rename_service, tab_widget=self.macro_tab_widget) def refreshFdsnStationWidgets(self): """ Refreshing the FDSN-Macroseismic UI depending on the WS chosen """ if not self.fdsn_station_list.currentItem(): return service_id = self.fdsn_station_list.currentItem().text() self._update_service_widgets( service_type=SERVICE_MANAGER.FDSNSTATION, service_id=service_id, filter_by_id_widget=self.station_by_id_filter, fetch_by_url_widget=self.station_by_url_widget, filter_widget=self.station_filter, info_widget=self.station_service_info_widget, remove_service_button=self.button_station_remove_service, edit_service_button=self.button_station_edit_service, rename_service_button=self.button_station_rename_service, tab_widget=self.fdsnstation_tab_widget) def refreshOgcWidgets(self): """ read the ogc_combo and fill it with the services """ self.ogc_list_model.clear() ogc_selection = self.ogc_combo.currentData() services = SERVICE_MANAGER.available_services(ogc_selection) group_items = {} for service in services: service_config = SERVICE_MANAGER.service_details( ogc_selection, service) group = service_config.get('group') if not group or group in group_items: continue group_item = QStandardItem(group) group_item.setFlags(Qt.ItemIsEnabled) self.ogc_list_model.appendRow([group_item]) group_items[group] = group_item first_item = None for service in services: item = QStandardItem(service) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) item.setData(service, role=Qt.UserRole) if not first_item: first_item = item service_config = SERVICE_MANAGER.service_details( ogc_selection, service) group = service_config.get('group') if group: parent = group_items[group] parent.appendRow([item]) else: self.ogc_list_model.appendRow([item]) self.ogc_list.expandAll() first_item_index = self.ogc_list_model.indexFromItem(first_item) self.ogc_list.selectionModel().select( first_item_index, QItemSelectionModel.ClearAndSelect) service_config = SERVICE_MANAGER.service_details( ogc_selection, self.get_current_service_id(ogc_selection)) self.button_ogc_edit_service.setEnabled( not service_config['read_only']) self.button_ogc_rename_service.setEnabled( not service_config['read_only']) self.button_ogc_remove_service.setEnabled( not service_config['read_only']) def _ogc_service_changed(self, _, __): """ Triggered when the current OGC service changes """ if not self.ogc_list.selectionModel().selectedIndexes(): return current_service = self.ogc_list.selectionModel().selectedIndexes( )[0].data(Qt.UserRole) if not current_service: return self.ogc_service_widget.set_service( service_type=self.ogc_combo.currentData(), service_id=current_service) self.ogc_service_info_widget.set_service( service_type=self.ogc_combo.currentData(), service_id=current_service) service_config = SERVICE_MANAGER.service_details( self.ogc_combo.currentData(), current_service) self.button_ogc_edit_service.setEnabled( not service_config['read_only']) self.button_ogc_rename_service.setEnabled( not service_config['read_only']) self.button_ogc_remove_service.setEnabled( not service_config['read_only']) def _remove_service(self): """ Removes the current service """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) if QMessageBox.question( self, self.tr('Remove Service'), self.tr('Are you sure you want to remove "{}"?'.format( service_id))) != QMessageBox.Yes: return SERVICE_MANAGER.remove_service(service_type, service_id) def _edit_service(self): """ Edits the current service """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) config_dialog = ServiceConfigurationDialog(self.iface, service_type, service_id, self) if config_dialog.exec_(): self.set_current_service(service_type, service_id) def _rename_service(self): """ Renames the current service """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) dlg = QgsNewNameDialog( service_id, service_id, [], existing=SERVICE_MANAGER.available_services(service_type)) dlg.setHintString(self.tr('Rename service configuration to')) dlg.setWindowTitle(self.tr('Rename Service Configuration')) dlg.setOverwriteEnabled(False) dlg.setConflictingNameWarning( self.tr('A configuration with this name already exists')) if not dlg.exec_(): return new_name = dlg.name() SERVICE_MANAGER.rename_service(service_type, service_id, new_name) self.set_current_service(service_type, new_name) def set_current_service(self, service_type: str, service_id: str): """ Sets the current service """ if service_type == SERVICE_MANAGER.FDSNEVENT: self.fdsn_event_list.setCurrentItem( self.fdsn_event_list.findItems(service_id, Qt.MatchContains)[0]) elif service_type == SERVICE_MANAGER.MACROSEISMIC: self.fdsn_macro_list.setCurrentItem( self.fdsn_macro_list.findItems(service_id, Qt.MatchContains)[0]) elif service_type == SERVICE_MANAGER.FDSNSTATION: self.fdsn_station_list.setCurrentItem( self.fdsn_station_list.findItems(service_id, Qt.MatchContains)[0]) elif service_type in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): self.ogc_combo.setCurrentIndex( self.ogc_combo.findData(service_type)) indexes = self.ogc_list_model.match( self.ogc_list_model.index(0, 0), Qt.UserRole, service_id, flags=Qt.MatchExactly | Qt.MatchRecursive) if len(indexes) > 0: self.ogc_list.selectionModel().select( indexes[0], QItemSelectionModel.ClearAndSelect) def _create_configuration(self): """ Creates a new service configuration """ service_type = self.get_current_service_type() dlg = QgsNewNameDialog( '', '', [], existing=SERVICE_MANAGER.available_services(service_type)) dlg.setHintString(self.tr('Create a new service configuration named')) dlg.setWindowTitle(self.tr('New Service Configuration')) dlg.setOverwriteEnabled(False) dlg.setConflictingNameWarning( self.tr('A configuration with this name already exists')) if not dlg.exec_(): return name = dlg.name() config_dialog = ServiceConfigurationDialog(self.iface, service_type, name, self) if config_dialog.exec_(): self.set_current_service(service_type, name) def _export_service(self): """ Triggers exporting a service configuration """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) file, _ = QFileDialog.getSaveFileName( self, self.tr('Export Service'), QDir.homePath() + '/{}.json'.format(service_id), 'JSON Files (*.json)') if not file: return file = QgsFileUtils.ensureFileNameHasExtension(file, ['json']) if SERVICE_MANAGER.export_service(service_type, service_id, file): self.message_bar.pushMessage(self.tr("Service exported"), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr("An error occurred while exporting service"), Qgis.Critical, 5) def _import_configuration(self): """ Triggers importing a configuration """ file, _ = QFileDialog.getOpenFileName(self, self.tr('Import Service'), QDir.homePath(), 'JSON Files (*.json)') if not file: return res, err = SERVICE_MANAGER.import_service(file) if res: self.message_bar.pushMessage(self.tr("Service imported"), Qgis.Success, 5) else: self.message_bar.pushMessage(err, Qgis.Critical, 5) def _getEventList(self): """ read the event URL and convert the response in a list """ if self.get_current_service_type() in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): self.ogc_service_widget.add_selected_layers() return if self.fetcher: # TODO - cancel current request return self.fetcher = self.get_fetcher() def on_started(): self.progressBar.setValue(0) self.progressBar.setRange(0, 0) def on_progress(progress: float): self.progressBar.setRange(0, 100) self.progressBar.setValue(progress) self.fetcher.started.connect(on_started) self.fetcher.progress.connect(on_progress) self.fetcher.finished.connect(self._fetcher_finished) self.fetcher.message.connect(self._fetcher_message) self.button_box.button(QDialogButtonBox.Ok).setText( self.tr('Fetching')) self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.fetcher.fetch_data() def _fetcher_message(self, message, level): """ Handles message feedback from a fetcher """ self.message_bar.clearWidgets() self.message_bar.pushMessage(message, level, 0) def _fetcher_finished(self, res): # pylint: disable=too-many-branches """ Triggered when a fetcher is finished """ self.progressBar.setRange(0, 100) self.progressBar.reset() self.button_box.button(QDialogButtonBox.Ok).setText( self.tr('Fetch Data')) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) if not res: self.fetcher.deleteLater() self.fetcher = None return found_results = False layers = [] if self.fetcher.service_type in (SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC): layer = self.fetcher.create_event_layer() if layer: layers.append(layer) if self.fetcher.service_type == SERVICE_MANAGER.MACROSEISMIC: layer = self.fetcher.create_mdp_layer() if layer: layers.append(layer) if layers: events_count = layers[0].featureCount() found_results = bool(events_count) service_limit = self.fetcher.service_config['settings'].get( 'querylimitmaxentries', None) self.message_bar.clearWidgets() if service_limit is not None and events_count >= service_limit: self.message_bar.pushMessage( self.tr("Query exceeded the service's result limit"), Qgis.Critical, 0) elif events_count > 500: self.message_bar.pushMessage( self.tr( "Query returned a large number of results ({})". format(events_count)), Qgis.Warning, 0) elif events_count == 0: self.message_bar.pushMessage( self. tr("Query returned no results - possibly parameters are invalid for this service" ), Qgis.Critical, 0) else: self.message_bar.pushMessage( self.tr("Query returned {} records").format( events_count), Qgis.Success, 0) elif self.fetcher.service_type == SERVICE_MANAGER.FDSNSTATION: layers.append(self.fetcher.create_stations_layer()) stations_count = layers[0].featureCount() found_results = bool(stations_count) if stations_count == 0: self.message_bar.pushMessage( self. tr("Query returned no results - possibly parameters are invalid for this service" ), Qgis.Critical, 0) else: self.message_bar.pushMessage( self.tr("Query returned {} stations").format( stations_count), Qgis.Info, 0) else: assert False self.fetcher.deleteLater() self.fetcher = None if found_results: QgsProject.instance().addMapLayers(layers)
class HistoryDialog(QDialog, Ui_HistoryDialogPythonConsole): def __init__(self, parent): QDialog.__init__(self, parent) self.setupUi(self) self.parent = parent self.setWindowTitle( QCoreApplication.translate("PythonConsole", "Python Console - Command History")) self.listView.setToolTip( QCoreApplication.translate("PythonConsole", "Double-click on item to execute")) self.listView.setFont(QgsCodeEditorPython.getMonospaceFont()) self.model = QStandardItemModel(self.listView) self._reloadHistory() self.deleteScut = QShortcut(QKeySequence(Qt.Key_Delete), self) self.deleteScut.activated.connect(self._deleteItem) self.listView.doubleClicked.connect(self._runHistory) self.reloadHistory.clicked.connect(self._reloadHistory) self.saveHistory.clicked.connect(self._saveHistory) self.runHistoryButton.clicked.connect(self._executeSelectedHistory) def _executeSelectedHistory(self): items = self.listView.selectionModel().selectedIndexes() items.sort() for item in items: self.parent.runCommand(item.data(Qt.DisplayRole)) def _runHistory(self, item): cmd = item.data(Qt.DisplayRole) self.parent.runCommand(cmd) def _saveHistory(self): self.parent.writeHistoryFile(True) def _reloadHistory(self): self.model.clear() item = None for i in self.parent.history: item = QStandardItem(i) if sys.platform.startswith('win'): item.setSizeHint(QSize(18, 18)) self.model.appendRow(item) self.listView.setModel(self.model) self.listView.scrollToBottom() if item: self.listView.setCurrentIndex(self.model.indexFromItem(item)) def _deleteItem(self): itemsSelected = self.listView.selectionModel().selectedIndexes() if itemsSelected: item = itemsSelected[0].row() # Remove item from the command history (just for the current session) self.parent.history.pop(item) self.parent.softHistory.pop(item) if item < self.parent.softHistoryIndex: self.parent.softHistoryIndex -= 1 self.parent.setText( self.parent.softHistory[self.parent.softHistoryIndex]) self.parent.move_cursor_to_end() # Remove row from the command history dialog self.model.removeRow(item)
def _populate_trv(self, trv_widget, result, expand=False): model = QStandardItemModel() trv_widget.setModel(model) trv_widget.setUniformRowHeights(False) icon_folder = self.plugin_dir + os.sep + 'icons' + os.sep + 'dialogs' + os.sep + '20x20' + os.sep path_icon_blue = icon_folder + os.sep + '36.png' path_icon_red = icon_folder + os.sep + '100.png' # Section Processes section_processes = QStandardItem('{}'.format('Processes')) for group, functions in result['processes']['fields'].items(): parent1 = QStandardItem(f'{group} [{len(functions)} Giswater algorithm]') self.no_clickable_items.append(f'{group} [{len(functions)} Giswater algorithm]') functions.sort(key=self._sort_list, reverse=False) for function in functions: func_name = QStandardItem(str(function['functionname'])) label = QStandardItem(str(function['alias'])) font = label.font() font.setPointSize(8) label.setFont(font) row = tools_db.check_function(function['functionname']) if not row: if os.path.exists(path_icon_red): icon = QIcon(path_icon_red) label.setIcon(icon) label.setForeground(QColor(255, 0, 0)) msg = f"Function {function['functionname']}" \ f" configured on the table config_toolbox, but not found in the database" label.setToolTip(msg) self.no_clickable_items.append(str(function['alias'])) else: if os.path.exists(path_icon_blue): icon = QIcon(path_icon_blue) label.setIcon(icon) label.setToolTip(function['functionname']) parent1.appendRow([label, func_name]) section_processes.appendRow(parent1) # Section Reports reports_processes = QStandardItem('{}'.format('Reports')) for group, functions in result['reports']['fields'].items(): parent1 = QStandardItem(f'{group} [{len(functions)} Reports functions]') self.no_clickable_items.append(f'{group} [{len(functions)} Reports functions]') functions.sort(key=self._sort_list, reverse=False) for function in functions: func_name = QStandardItem(str(function['listname'])) label = QStandardItem(str(function['alias'])) font = label.font() font.setPointSize(8) label.setFont(font) parent1.appendRow([label, func_name]) if os.path.exists(path_icon_blue): icon = QIcon(path_icon_blue) label.setIcon(icon) reports_processes.appendRow(parent1) if os.path.exists(path_icon_blue): icon = QIcon(path_icon_blue) section_processes.setIcon(icon) reports_processes.setIcon(icon) model.appendRow(section_processes) model.appendRow(reports_processes) section_index = model.indexFromItem(section_processes) reports_index = model.indexFromItem(reports_processes) trv_widget.expand(section_index) trv_widget.expand(reports_index) if expand: trv_widget.expandAll()
class QRAVEDockWidget(QDockWidget, Ui_QRAVEDockWidgetBase): closingPlugin = pyqtSignal() dataChange = pyqtSignal() showMeta = pyqtSignal() metaChange = pyqtSignal(str, str, dict, bool) def __init__(self, parent=None): """Constructor.""" super(QRAVEDockWidget, self).__init__(parent) self.setupUi(self) self.menu = ContextMenu() self.qproject = QgsProject.instance() self.qproject.cleared.connect(self.close_all) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.customContextMenuRequested.connect(self.open_menu) self.treeView.doubleClicked.connect(self.default_tree_action) self.treeView.clicked.connect(self.item_change) self.treeView.expanded.connect(self.expand_tree_item) self.settings = Settings() self.model = QStandardItemModel() self.loaded_projects: List(Project) = [] # Initialize our classes self.basemaps = BaseMaps() self.treeView.setModel(self.model) self.dataChange.connect(self.reload_tree) self.reload_tree() def expand_tree_item(self, idx: QModelIndex): item = self.model.itemFromIndex(idx) item_data = item.data(Qt.UserRole) if item_data and item_data.data and isinstance(item_data.data, QRaveBaseMap): item_data.data.load_layers() def _get_project(self, xml_path): try: return next(iter(self.loaded_projects)) except Exception: return None @pyqtSlot() def reload_tree(self): # re-initialize our model and reload the projects from file # Try not to do this too often if you can self.model.clear() self.loaded_projects = [] qrave_projects = self.get_project_settings() for project_path in qrave_projects: project = Project(project_path) project.load() if project is not None and project.exists is True and project.qproject is not None: self.model.appendRow(project.qproject) self.expand_children_recursive(self.model.indexFromItem(project.qproject)) self.loaded_projects.append(project) # Load the tree objects self.basemaps.load() # Now load the basemaps region = self.settings.getValue('basemapRegion') if self.settings.getValue('basemapsInclude') is True \ and region is not None and len(region) > 0 \ and region in self.basemaps.regions.keys(): self.model.appendRow(self.basemaps.regions[region]) self.expand_children_recursive(self.model.indexFromItem(self.basemaps.regions[region])) def get_project_settings(self): try: qrave_projects_raw, type_conversion_ok = self.qproject.readEntry( CONSTANTS['settingsCategory'], 'qrave_projects' ) if type_conversion_ok is False: qrave_projects = [] else: qrave_projects = json.loads(qrave_projects_raw) if qrave_projects is None or not isinstance(qrave_projects, list): qrave_projects = [] except Exception as e: self.settings.log('Error loading project settings: {}'.format(e), Qgis.Warning) qrave_projects = [] filtered = [pf for pf in qrave_projects if os.path.isfile(pf)] filtered.reverse() # We Treat this like a stack where the last project in goes on the top. # Element 0 should be the top item return filtered def set_project_settings(self, projects: List[str]): self.qproject.writeEntry(CONSTANTS['settingsCategory'], 'qrave_projects', json.dumps(projects)) @pyqtSlot() def add_project(self, xml_path: str): qrave_projects = self.get_project_settings() # If this project is not already in if xml_path not in qrave_projects: qrave_projects.append(xml_path) self.set_project_settings(qrave_projects) self.reload_tree() new_project = self._get_project(xml_path) # If this is a fresh load and the setting is set we load the default view load_default_setting = self.settings.getValue('loadDefaultView') if load_default_setting is True \ and new_project.default_view is not None \ and new_project.default_view in new_project.views: self.add_children_to_map(new_project.qproject, new_project.views[new_project.default_view]) def closeEvent(self, event): """ When the user clicks the "X" in the dockwidget titlebar """ self.hide() self.qproject.removeEntry(CONSTANTS['settingsCategory'], 'enabled') self.closingPlugin.emit() event.accept() def expand_children_recursive(self, idx: QModelIndex = None, force=False): """Expand all the children of a QTreeView node. Do it recursively TODO: Recursion might not be the best for large trees here. Args: idx (QModelIndex, optional): [description]. Defaults to None. force: ignore the "collapsed" business logic attribute """ if idx is None: idx = self.treeView.rootIndex() for idy in range(self.model.rowCount(idx)): child = self.model.index(idy, 0, idx) self.expand_children_recursive(child, force) item = self.model.itemFromIndex(idx) item_data = item.data(Qt.UserRole) if item is not None else None # NOTE: This is pretty verbose on purpose # This thing needs to have data or it defaults to being expanded if item_data is None or item_data.data is None: collapsed = False # Collapsed is an attribute set in the business logic # Never expand the QRaveBaseMap object becsause there's a network call involved elif isinstance(item_data.data, QRaveBaseMap) \ or (isinstance(item_data.data, dict) and 'collapsed' in item_data.data and item_data.data['collapsed'] == 'true'): collapsed = True else: collapsed = False if not self.treeView.isExpanded(idx) and not collapsed: self.treeView.setExpanded(idx, True) def default_tree_action(self, idx: QModelIndex): if not idx.isValid(): return item = self.model.itemFromIndex(idx) item_data: ProjectTreeData = item.data(Qt.UserRole) # This is the default action for all add-able layers including basemaps if isinstance(item_data.data, QRaveMapLayer): if item_data.data.layer_type in [QRaveMapLayer.LayerTypes.FILE, QRaveMapLayer.LayerTypes.REPORT]: self.file_system_open(item_data.data.layer_uri) else: QRaveMapLayer.add_layer_to_map(item) # Expand is the default option for wms because we might need to load the layers elif isinstance(item_data.data, QRaveBaseMap): if item_data.data.tile_type == 'wms': pass # All the XYZ layers can be added normally. else: QRaveMapLayer.add_layer_to_map(item) elif item_data.type in [QRaveTreeTypes.PROJECT_ROOT]: self.change_meta(item, item_data, True) # For folder-y types we want Expand and contract is already implemented as a default elif item_data.type in [ QRaveTreeTypes.PROJECT_FOLDER, QRaveTreeTypes.PROJECT_REPEATER_FOLDER, QRaveTreeTypes.PROJECT_VIEW_FOLDER, QRaveTreeTypes.BASEMAP_ROOT, QRaveTreeTypes.BASEMAP_SUPER_FOLDER, QRaveTreeTypes.BASEMAP_SUB_FOLDER ]: # print("Default Folder Action") pass elif item_data.type == QRaveTreeTypes.PROJECT_VIEW: print("Default View Action") self.add_view_to_map(item_data) def item_change(self, pos): """Triggered when the user selects a new item in the tree Args:pos pos ([type]): [description] """ indexes = self.treeView.selectedIndexes() # No multiselect so there is only ever one item item = self.model.itemFromIndex(indexes[0]) data_item: ProjectTreeData = item.data(Qt.UserRole) if len(indexes) < 1 or data_item.project is None or not data_item.project.exists: return # Update the metadata if we need to self.change_meta(item, data_item) def change_meta(self, item: QStandardItem, item_data: ProjectTreeData, show=False): """Update the MetaData dock widget with new information Args: item (QStandardItem): [description] data ([type]): [description] show (bool, optional): [description]. Defaults to False. """ data = item_data.data if isinstance(data, QRaveMapLayer): meta = data.meta if data.meta is not None else {} self.metaChange.emit(item.text(), MetaType.LAYER, meta, show) elif isinstance(data, QRaveBaseMap): self.metaChange.emit(item.text(), MetaType.NONE, {}, show) elif item_data.type == QRaveTreeTypes.PROJECT_ROOT: self.metaChange.emit(item.text(), MetaType.PROJECT, { 'project': item_data.project.meta, 'warehouse': item_data.project.warehouse_meta }, show) elif item_data.type in [ QRaveTreeTypes.PROJECT_FOLDER, QRaveTreeTypes.PROJECT_REPEATER_FOLDER, QRaveTreeTypes.PROJECT_VIEW_FOLDER, QRaveTreeTypes.BASEMAP_ROOT, QRaveTreeTypes.BASEMAP_SUPER_FOLDER, QRaveTreeTypes.BASEMAP_SUB_FOLDER ]: self.metaChange.emit(item.text(), MetaType.FOLDER, data or {}, show) elif isinstance(data, dict): # this is just the generic case for any kind of metadata self.metaChange.emit(item.text(), MetaType.NONE, data or {}, show) else: # Do not update the metadata if we have nothing to show self.metaChange.emit(item.text(), MetaType.NONE, {}, show) def get_warehouse_url(self, wh_meta: Dict[str, str]): if wh_meta is not None: if 'program' in wh_meta and 'id' in wh_meta: return '/'.join([CONSTANTS['warehouseUrl'], wh_meta['program'], wh_meta['id']]) elif '_rs_wh_id' in wh_meta and '_rs_wh_program' in wh_meta: return '/'.join([CONSTANTS['warehouseUrl'], wh_meta['_rs_wh_program'], wh_meta['_rs_wh_id']]) return None def project_warehouse_view(self, project: Project): """Open this project in the warehouse if the warehouse meta entries exist """ url = self.get_warehouse_url(project.warehouse_meta) if url is not None: QDesktopServices.openUrl(QUrl(url)) def layer_warehouse_view(self, data: QRaveMapLayer): """Open this project in the warehouse if the warehouse meta entries exist """ url = self.get_warehouse_url(data.meta) if url is not None: QDesktopServices.openUrl(QUrl(url)) def close_all(self): for p in range(len(self.loaded_projects)): self.close_project(self.loaded_projects[0]) def close_project(self, project: Project): """ Close the project """ try: qrave_projects_raw, type_conversion_ok = self.qproject.readEntry( CONSTANTS['settingsCategory'], 'qrave_projects' ) qrave_projects = json.loads(qrave_projects_raw) if not type_conversion_ok or qrave_projects is None: qrave_projects = [] except Exception as e: self.settings.log('Error closing project: {}'.format(e), Qgis.Warning) qrave_projects = [] # Filter out the project we want to close and reload the tree qrave_projects = [x.project_xml_path for x in self.loaded_projects if x != project] # Write the settings back to the project self.qproject.writeEntry(CONSTANTS['settingsCategory'], 'qrave_projects', json.dumps(qrave_projects)) self.loaded_projects = qrave_projects self.reload_tree() def file_system_open(self, fpath: str): """Open a file on the operating system using the default action Args: fpath (str): [description] """ qurl = QUrl.fromLocalFile(fpath) QDesktopServices.openUrl(QUrl(qurl)) def file_system_locate(self, fpath: str): """This the OS-agnostic "show in Finder" or "show in explorer" equivalent It should open the folder of the item in question Args: fpath (str): [description] """ final_path = os.path.dirname(fpath) while not os.path.isdir(final_path): final_path = os.path.dirname(final_path) qurl = QUrl.fromLocalFile(final_path) QDesktopServices.openUrl(qurl) def toggleSubtree(self, item: QStandardItem = None, expand=True): def _recurse(curritem): idx = self.model.indexFromItem(item) if expand is not self.treeView.isExpanded(idx): self.treeView.setExpanded(idx, expand) for row in range(curritem.rowCount()): _recurse(curritem.child(row)) if item is None: if expand is True: self.treeView.expandAll() else: self.treeView.collapseAll() else: _recurse(item) def add_view_to_map(self, item_data: ProjectTreeData): """Add a view and all its layers to the map Args: item (QStandardItem): [description] """ self.add_children_to_map(item_data.project.qproject, item_data.data) def add_children_to_map(self, item: QStandardItem, bl_ids: List[str] = None): """Iteratively add all children to the map Args: item (QStandardItem): [description] bl_ids (List[str], optional): List of ids to filter by so we don't load everything. this is used for loading views """ for child in self._get_children(item): # Is this something we can add to the map? project_tree_data = child.data(Qt.UserRole) if project_tree_data is not None and isinstance(project_tree_data.data, QRaveMapLayer): data = project_tree_data.data loadme = False # If this layer matches the businesslogic id filter if bl_ids is not None and len(bl_ids) > 0: if 'id' in data.bl_attr and data.bl_attr['id'] in bl_ids: loadme = True else: loadme = True if loadme is True: data.add_layer_to_map(child) def _get_children(self, root_item: QStandardItem): """Recursion is going to kill us here so do an iterative solution instead https://stackoverflow.com/questions/41949370/collect-all-items-in-qtreeview-recursively Yields: [type]: [description] """ stack = [root_item] while stack: parent = stack.pop(0) for row in range(parent.rowCount()): for column in range(parent.columnCount()): child = parent.child(row, column) yield child if child.hasChildren(): stack.append(child) def _get_parents(self, start_item: QStandardItem): stack = [] placeholder = start_item.parent() while placeholder is not None and placeholder != self.model.invisibleRootItem(): stack.append(placeholder) placeholder = start_item.parent() return stack.reverse() def open_menu(self, position): indexes = self.treeView.selectedIndexes() if len(indexes) < 1: return # No multiselect so there is only ever one item idx = indexes[0] if not idx.isValid(): return item = self.model.itemFromIndex(indexes[0]) project_tree_data = item.data(Qt.UserRole) # ProjectTreeData object data = project_tree_data.data # Could be a QRaveBaseMap, a QRaveMapLayer or just some random data # This is the layer context menu if isinstance(data, QRaveMapLayer): if data.layer_type == QRaveMapLayer.LayerTypes.WEBTILE: self.basemap_context_menu(idx, item, project_tree_data) elif data.layer_type in [QRaveMapLayer.LayerTypes.FILE, QRaveMapLayer.LayerTypes.REPORT]: self.file_layer_context_menu(idx, item, project_tree_data) else: self.map_layer_context_menu(idx, item, project_tree_data) elif isinstance(data, QRaveBaseMap): # A WMS QARaveBaseMap is just a container for layers if data.tile_type == 'wms': self.folder_dumb_context_menu(idx, item, project_tree_data) # Every other kind of basemap is an add-able layer else: self.basemap_context_menu(idx, item, project_tree_data) elif project_tree_data.type == QRaveTreeTypes.PROJECT_ROOT: self.project_context_menu(idx, item, project_tree_data) elif project_tree_data.type in [ QRaveTreeTypes.PROJECT_VIEW_FOLDER, QRaveTreeTypes.BASEMAP_ROOT, QRaveTreeTypes.BASEMAP_SUPER_FOLDER ]: self.folder_dumb_context_menu(idx, item, project_tree_data) elif project_tree_data.type in [ QRaveTreeTypes.PROJECT_FOLDER, QRaveTreeTypes.PROJECT_REPEATER_FOLDER, QRaveTreeTypes.BASEMAP_SUB_FOLDER ]: self.folder_context_menu(idx, item, project_tree_data) elif project_tree_data.type == QRaveTreeTypes.PROJECT_VIEW: self.view_context_menu(idx, item, project_tree_data) self.menu.exec_(self.treeView.viewport().mapToGlobal(position)) def map_layer_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_TO_MAP', lambda: QRaveMapLayer.add_layer_to_map(item), enabled=item_data.data.exists) self.menu.addAction('VIEW_LAYER_META', lambda: self.change_meta(item, item_data, True)) if bool(self.get_warehouse_url(item_data.data.meta)): self.menu.addAction('VIEW_WEB_SOURCE', lambda: self.layer_warehouse_view(item_data)) self.menu.addAction('BROWSE_FOLDER', lambda: self.file_system_locate(item_data.data.layer_uri)) def file_layer_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData): self.menu.clear() self.menu.addAction('OPEN_FILE', lambda: self.file_system_open(item_data.data.layer_uri)) self.menu.addAction('BROWSE_FOLDER', lambda: self.file_system_locate(item_data.data.layer_uri)) # Basemap context items def basemap_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_TO_MAP', lambda: QRaveMapLayer.add_layer_to_map(item)) # Folder-level context menu def folder_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_children_to_map(item)) self.menu.addSeparator() self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(item, False)) self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(item, True)) # Some folders don't have the 'ADD_ALL_TO_MAP' functionality enabled def folder_dumb_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(item, False)) self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(item, True)) # View context items def view_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_view_to_map(item_data)) # Project-level context menu def project_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(None, False)) self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(None, True)) self.menu.addSeparator() self.menu.addAction('BROWSE_PROJECT_FOLDER', lambda: self.file_system_locate(data.project.project_xml_path)) self.menu.addAction('VIEW_PROJECT_META', lambda: self.change_meta(item, data, True)) self.menu.addAction('WAREHOUSE_VIEW', lambda: self.project_warehouse_view(data.project), enabled=bool(self.get_warehouse_url(data.project.warehouse_meta))) self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_children_to_map(item)) self.menu.addSeparator() self.menu.addAction('REFRESH_PROJECT_HIERARCHY', self.reload_tree) self.menu.addAction('CUSTOMIZE_PROJECT_HIERARCHY', enabled=False) self.menu.addSeparator() self.menu.addAction('CLOSE_PROJECT', lambda: self.close_project(data.project), enabled=bool(data.project))