class ModelerNumberInputPanel(BASE, WIDGET): """ Number input panel for use inside the modeler - this input panel is based off the base input panel and includes a text based line input for entering values. This allows expressions and other non-numeric values to be set, which are later evalauted to numbers when the model is run. """ hasChanged = pyqtSignal() def __init__(self, param, modelParametersDialog): super().__init__(None) self.setupUi(self) self.param = param self.modelParametersDialog = modelParametersDialog if param.defaultValue(): self.setValue(param.defaultValue()) self.btnSelect.clicked.connect(self.showExpressionsBuilder) self.leText.textChanged.connect(lambda: self.hasChanged.emit()) def showExpressionsBuilder(self): context = createExpressionContext() processing_context = createContext() scope = self.modelParametersDialog.model.createExpressionContextScopeForChildAlgorithm( self.modelParametersDialog.childId, processing_context) context.appendScope(scope) highlighted = scope.variableNames() context.setHighlightedVariables(highlighted) dlg = QgsExpressionBuilderDialog(None, str(self.leText.text()), self, 'generic', context) dlg.setWindowTitle(self.tr('Expression Based Input')) if dlg.exec_() == QDialog.Accepted: exp = QgsExpression(dlg.expressionText()) if not exp.hasParserError(): self.setValue(dlg.expressionText()) def getValue(self): value = self.leText.text() for param in self.modelParametersDialog.model.parameterDefinitions(): if isinstance(param, QgsProcessingParameterNumber): if "@" + param.name() == value.strip(): return QgsProcessingModelChildParameterSource.fromModelParameter( param.name()) for alg in list( self.modelParametersDialog.model.childAlgorithms().values()): for out in alg.algorithm().outputDefinitions(): if isinstance(out, QgsProcessingOutputNumber) and "@%s_%s" % ( alg.childId(), out.name()) == value.strip(): return QgsProcessingModelChildParameterSource.fromChildOutput( alg.childId(), out.outputName()) try: return float(value.strip()) except: return QgsProcessingModelChildParameterSource.fromExpression( self.leText.text()) def setValue(self, value): if isinstance(value, QgsProcessingModelChildParameterSource): if value.source( ) == QgsProcessingModelChildParameterSource.ModelParameter: self.leText.setText('@' + value.parameterName()) elif value.source( ) == QgsProcessingModelChildParameterSource.ChildOutput: name = "%s_%s" % (value.outputChildId(), value.outputName()) self.leText.setText(name) elif value.source( ) == QgsProcessingModelChildParameterSource.Expression: self.leText.setText(value.expression()) else: self.leText.setText(str(value.staticValue())) else: self.leText.setText(str(value))
class MyCanvas(QgsMapCanvas): closed = pyqtSignal() def closeEvent(self,event): super().closeEvent(event) print("closing") self.closed.emit()
class Project(QObject): layer_added = pyqtSignal(str) def __init__(self, auto_transaction=True, evaluate_default_values=True): QObject.__init__(self) self.crs = None self.name = 'Not set' self.layers = List[Layer] self.legend = LegendGroup() self.auto_transaction = auto_transaction self.evaluate_default_values = evaluate_default_values self.relations = List[Relation] # {Layer_class_name: {dbattribute: {Layer_class, cardinality, Layer_domain, key_field, value_field]} self.bags_of_enum = dict() def add_layer(self, layer): self.layers.append(layer) def dump(self): definition = dict() definition['crs'] = self.crs.toWkt() definition['auto_transaction'] = self.auto_transaction definition['evaluate_default_values'] = self.evaluate_default_values legend = list() for layer in self.layers: legend.append(layer.dump()) relations = list() for relation in self.relations: relations.append(relation.dump()) definition['legend'] = legend definition['relations'] = relations return definition def load(self, definition): self.crs = definition['crs'] self.auto_transaction = definition['auto_transaction'] self.evaluate_default_values = definition['evaluate_default_values'] self.layers = list() for layer_definition in definition['layers']: layer = Layer() layer.load(layer_definition) self.layers.append(layer) def create(self, path: str, qgis_project: QgsProject): qgis_project.setAutoTransaction(self.auto_transaction) qgis_project.setEvaluateDefaultValues(self.evaluate_default_values) qgis_layers = list() for layer in self.layers: qgis_layer = layer.create() self.layer_added.emit(qgis_layer.id()) if not self.crs and qgis_layer.isSpatial(): self.crs = qgis_layer.crs() qgis_layers.append(qgis_layer) qgis_project.addMapLayers(qgis_layers, not self.legend) if self.crs: if isinstance(self.crs, QgsCoordinateReferenceSystem): qgis_project.setCrs(self.crs) else: qgis_project.setCrs( QgsCoordinateReferenceSystem.fromEpsgId(self.crs)) qgis_relations = list( qgis_project.relationManager().relations().values()) dict_domains = { layer.layer.id(): layer.is_domain for layer in self.layers } for relation in self.relations: rel = relation.create(qgis_project, qgis_relations) assert rel.isValid() qgis_relations.append(rel) if rel.referencedLayerId() in dict_domains and dict_domains[ rel.referencedLayerId()]: editor_widget_setup = QgsEditorWidgetSetup( 'RelationReference', { 'Relation': rel.id(), 'ShowForm': False, 'OrderByValue': True, 'ShowOpenFormButton': False }) else: editor_widget_setup = QgsEditorWidgetSetup( 'RelationReference', { 'Relation': rel.id(), 'ShowForm': False, 'OrderByValue': True, 'ShowOpenFormButton': False, 'AllowAddFeatures': True }) referencing_layer = rel.referencingLayer() referencing_layer.setEditorWidgetSetup(rel.referencingFields()[0], editor_widget_setup) qgis_project.relationManager().setRelations(qgis_relations) # Set Bag of Enum widget for layer_name, bag_of_enum in self.bags_of_enum.items(): for attribute, bag_of_enum_info in bag_of_enum.items(): layer_obj = bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] key_field = bag_of_enum_info[3] value_field = bag_of_enum_info[4] allow_null = cardinality.startswith('0') allow_multi = cardinality.endswith('*') current_layer = layer_obj.create() field_widget = 'ValueRelation' field_widget_config = { 'AllowMulti': allow_multi, 'UseCompleter': False, 'Value': value_field, 'OrderByValue': False, 'AllowNull': allow_null, 'Layer': domain_table.create().id(), 'FilterExpression': '', 'Key': key_field, 'NofColumns': 1 } field_idx = current_layer.fields().indexOf(attribute) setup = QgsEditorWidgetSetup(field_widget, field_widget_config) current_layer.setEditorWidgetSetup(field_idx, setup) for layer in self.layers: layer.create_form(self) if self.legend: self.legend.create(qgis_project) if path: qgis_project.write(path) def post_generate(self): for layer in self.layers: layer.post_generate(self)
class BuildingsDockwidget(QDockWidget, FORM_CLASS): closed = pyqtSignal() in_focus = pyqtSignal() frames = {} current_frame = None def __init__(self, parent=None): """Constructor.""" super(BuildingsDockwidget, self).__init__(parent) # Set up the user interface from Designer. self.setupUi(self) # Set focus policy so can track when user clicks back onto dock widget self.setFocusPolicy(Qt.StrongFocus) self.prev_width = self.width() # Change look of options list widget self.lst_options.setStyleSheet(""" QListWidget { background-color: rgb(69, 69, 69, 0); outline: 0; } QListWidget::item { color: white; padding-top: 3px; padding-bottom: 3px; } QListWidget::item::selected { color: black; background-color:palette(Window); padding-right: 0px; }; """) # Change look of sub menu list widget self.lst_sub_menu.setStyleSheet(""" QListWidget { background-color: rgb(69, 69, 69, 0); outline: 0; } QListWidget::item { color: white; padding-top: 3px; padding-bottom: 3px; } QListWidget::item::selected { color: black; background-color:palette(Window); padding-right: 0px; }; """) self.frm_options.setStyleSheet(""" QFrame { background-color: rgb(69, 69, 69, 220); }; """) # Signals for clicking on list widgets self.lst_options.itemClicked.connect(self.show_selected_option) self.lst_sub_menu.itemSelectionChanged.connect(self.show_frame) self.splitter.splitterMoved.connect(self.resize_dockwidget) from buildings.utilities import database as db from buildings.gui.new_capture_source import NewCaptureSource from buildings.gui.bulk_load_frame import BulkLoadFrame from buildings.gui.alter_building_relationships import AlterRelationships from buildings.gui.production_frame import ProductionFrame from buildings.gui.new_entry import NewEntry from buildings.gui.new_capture_source_area import NewCaptureSourceArea from buildings.gui.reference_data import UpdateReferenceData self.db = db self.new_capture_source = NewCaptureSource self.bulk_load_frame = BulkLoadFrame self.alter_relationships = AlterRelationships self.production_frame = ProductionFrame self.new_entry = NewEntry self.new_capture_source_area = NewCaptureSourceArea self.reference_data = UpdateReferenceData @pyqtSlot() def show_selected_option(self): if self.lst_options.selectedItems(): current = self.lst_options.selectedItems()[0] if current.text() == "Buildings": if isinstance(self.current_frame, self.alter_relationships): self.current_frame.close_frame() if isinstance(self.current_frame, self.new_capture_source_area): self.current_frame.close_frame() try: self.current_frame.close_frame() except AttributeError: pass project.SRID = 2193 project.set_crs() self.stk_options.removeWidget(self.stk_options.currentWidget()) self.stk_options.addWidget(self.frames["menu_frame"]) self.current_frame = self.frames["menu_frame"] self.lst_sub_menu.clearSelection() @pyqtSlot() def show_frame(self): if self.lst_sub_menu.selectedItems(): current = self.lst_sub_menu.selectedItems()[0] # Remove the current widget and run its exit method # If it has no exit method, just remove the current widget if isinstance(self.current_frame, self.alter_relationships): self.current_frame.close_frame() if isinstance(self.current_frame, self.new_capture_source_area): self.current_frame.close_frame() try: self.current_frame.close_frame() except AttributeError: pass self.stk_options.removeWidget(self.stk_options.currentWidget()) if current.text() == "Capture Sources": self.new_widget(self.new_capture_source(self)) elif current.text() == "Bulk Load": self.new_widget(self.bulk_load_frame(self)) elif current.text() == "Edit Outlines": self.new_widget(self.production_frame(self)) elif current.text() == "Settings": self.new_widget(self.new_entry(self)) elif current.text() == "Reference Data": self.new_widget(self.reference_data(self)) def new_widget(self, frame): self.stk_options.addWidget(frame) self.stk_options.setCurrentIndex(1) self.current_frame = frame # insert into dictionary def insert_into_frames(self, text, object): self.frames[text] = object @pyqtSlot(int, int) def resize_dockwidget(self, pos, index): self.prev_width = self.width() if pos < 175: new_pos = 175 - pos new_dock_width = 600 - new_pos if new_dock_width > self.prev_width: if (new_dock_width + 5) > 600: self.setFixedWidth(600) else: self.setFixedWidth(new_dock_width + 5) else: self.setFixedWidth(new_dock_width) def closeEvent(self, event): self.closed.emit() event.accept() def defaultStyle(self): """default tab Widget style""" return """ QTabWidget::pane { /* The tab widget frame */ border-top: 2px solid #C2C7CB; } QTabWidget::tab-bar { /* move to the right by 5px */ left: 5px; } QTabBar::tab { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #E1E1E1, stop: 0.4 #e7e7e7, stop: 0.5 #e7e7e7, stop: 1.0 #D3D3D3 ); border: 2px solid #C4C4C3; border-bottom-color: #C2C7CB; border-top-left-radius: 4px; border-top-right-radius: 4px; padding: 3px; } QTabBar::tab:selected, QTabBar::tab:hover { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ececec, stop: 0.4 #f4f4f4, stop: 0.5 #e7e7e7, stop: 1.0 #ececec ); } QTabBar::tab:selected { border-color: #9B9B9B; border-bottom-color: #f0f0f0; /* same as pane color */ } QTabBar::tab:!selected { /* make non-selected tabs look smaller */ margin-top: 2px; } QTabBar::tab:selected { /* expand to the left and right by 4px */ margin-left: 0px; margin-right: 0px; } QTabBar::tab:first:selected { /* the first selected tab has nothing to overlap with on the left */ margin-left: 0; } QTabBar::tab:last:selected { /* the last selected tab has nothing to overlap with on the right */ margin-right: 0; } QTabBar::tab:only-one { /* if there is only one tab, we don't want overlapping margins */ margin: 1; } """ def focusInEvent(self, event): self.in_focus.emit()
class DatabaseParameterWidget(QtWidgets.QWidget, FORM_CLASS): filesSelected = pyqtSignal() changeSize = pyqtSignal() def __init__(self, parent = None): """Constructor.""" super(self.__class__, self).__init__(parent) self.setupUi(self) self.serverAbstractDb = None self.selectedAbstractDb = None self.setInitialState() self.useFrame = True @pyqtSlot(AbstractDb, name = 'on_comboBoxPostgis_dbChanged') #check this out luiz! hahahahaha def populateSelectedAbstractDb(self, abstractDb): self.selectedAbstractDb = abstractDb if self.useFrame: self.populateFrameComboBox() def populateFrameComboBox(self): if self.selectedAbstractDb: areaDict = self.selectedAbstractDb.getGeomColumnDictV2(primitiveFilter=['a'], excludeValidation = True) self.frameComboBox.clear() self.frameComboBox.addItem(self.tr('Select a table from database')) self.tableDict = dict() sortedKeys = list(areaDict.keys()) sortedKeys.sort() for key in sortedKeys: tableKey = '{0}.{1}:{2}'.format(areaDict[key]['tableSchema'], areaDict[key]['tableName'], areaDict[key]['geom']) self.tableDict[tableKey] = areaDict[key] self.frameComboBox.addItem(tableKey) @pyqtSlot(int, name = 'on_frameComboBox_currentIndexChanged') def populateCombos(self, idx): if self.selectedAbstractDb: if idx > 0: self.indexComboBox.clear() self.inomComboBox.clear() self.indexComboBox.addItem(self.tr('Select an attribute from selected table')) self.inomComboBox.addItem(self.tr('Select an attribute from selected table')) selected = self.tableDict[self.frameComboBox.currentText()] attributeList = self.selectedAbstractDb.getAttributesFromTable(selected['tableSchema'], selected['tableName'], typeFilter = ['character', 'character varying', 'text']) for attr in attributeList: self.indexComboBox.addItem(attr) self.inomComboBox.addItem(attr) def setServerDb(self, abstractDb): self.serverAbstractDb = abstractDb self.dbTemplateRadioButton.setEnabled(True) self.comboBoxPostgis.setServerDb(self.serverAbstractDb) def setInitialState(self): """ Sets the initial state """ self.prefixVisible = True self.sufixVisible = True self.dbNameVisible = True self.frameGroupBox.hide() if not self.serverAbstractDb: self.dbTemplateRadioButton.setEnabled(False) def setPrefixVisible(self, visible): """ Sets if the database prefix should be visible """ if isinstance(visible,bool): self.prefixLineEdit.setVisible(visible) self.prefixLabel.setVisible(visible) self.prefixVisible = visible def setSufixVisible(self, visible): """ Sets if the database sufix should be visible """ if isinstance(visible,bool): self.sufixLineEdit.setVisible(visible) self.sufixLabel.setVisible(visible) self.sufixVisible = visible def setDbNameVisible(self, visible): """ Sets if the database name should be visible """ if isinstance(visible,bool): self.dbNameLineEdit.setVisible(visible) self.dbNameLabel.setVisible(visible) self.dbNameVisible = visible def getVersion(self): """ Get the database version """ return self.versionComboBox.currentText() def validate(self): """ Validate database name """ errorMsg = '' if self.dbNameVisible: if self.dbNameLineEdit.text() == '': errorMsg += self.tr('Enter a database name!\n') if self.mQgsProjectionSelectionWidget.crs().authid() == '': errorMsg += self.tr('Select a coordinate reference system!\n') if not self.edgvTemplateRadioButton.isChecked(): if not self.comboBoxPostgis.currentDb(): errorMsg += self.tr('Select a template database!\n') if self.useFrame: if self.frameComboBox.currentIndex() == 0: errorMsg += self.tr('Select a frame layer!\n') if self.indexComboBox.currentIndex() == 0: errorMsg += self.tr('Select an index attribute!\n') if self.inomComboBox.currentIndex() == 0: errorMsg += self.tr('Select an INOM attribute!\n') if errorMsg != '': QMessageBox.critical(self, self.tr('Critical!'), errorMsg) return False else: return True @pyqtSlot(bool, name = 'on_edgvTemplateRadioButton_toggled') def changeInterfaceState(self, edgvTemplateToggled, hideInterface = True): if edgvTemplateToggled: self.comboBoxPostgis.setEnabled(False) self.frameComboBox.setEnabled(False) self.versionComboBox.setEnabled(True) self.frameGroupBox.hide() else: self.comboBoxPostgis.show() if self.useFrame: self.frameGroupBox.show() self.comboBoxPostgis.setEnabled(True) self.versionComboBox.setEnabled(False) if not isinstance(self.sender(), QRadioButton): if hideInterface: self.frameGroupBox.hide() self.comboBoxPostgis.hide() self.dbTemplateRadioButton.hide() else: self.comboBoxPostgis.show() self.dbTemplateRadioButton.show() def getTemplateName(self): if self.edgvTemplateRadioButton.isChecked(): return None else: return self.comboBoxPostgis.currentDb() def getTemplateParameters(self): if self.edgvTemplateRadioButton.isChecked(): paramDict = dict() if self.serverAbstractDb: paramDict['templateName'] = self.serverAbstractDb.getTemplateName(self.versionComboBox.currentText()) paramDict['version'] = self.versionComboBox.currentText() paramDict['isTemplateEdgv'] = True return paramDict else: paramDict = dict() paramDict['templateName'] = self.comboBoxPostgis.currentDb() paramDict['isTemplateEdgv'] = False if self.useFrame: selected = self.tableDict[self.frameComboBox.currentText()] paramDict['tableSchema'] = selected['tableSchema'] paramDict['tableName'] = selected['tableName'] paramDict['geom'] = selected['geom'] paramDict['miAttr'] = self.indexComboBox.currentText() paramDict['inomAttr'] = self.inomComboBox.currentText() paramDict['geomType'] = selected['geomType'] return paramDict
class LayerRegistry(QObject): layersChanged = pyqtSignal() _instance = None _iface = None @staticmethod def instance(): if LayerRegistry._instance is None: LayerRegistry._instance = LayerRegistry() return LayerRegistry._instance @staticmethod def setIface(iface): LayerRegistry._iface = iface layers = [] def __init__(self): QObject.__init__(self) if LayerRegistry._instance is not None: return LayerRegistry.layers = self.getAllLayers() LayerRegistry._instance = self QgsMapLayerRegistry.instance().removeAll.connect(self.removeAllLayers) QgsMapLayerRegistry.instance().layerWasAdded.connect(self.layerAdded) QgsMapLayerRegistry.instance().layerWillBeRemoved.connect( self.removeLayer) def getAllLayers(self): if LayerRegistry._iface and hasattr(LayerRegistry._iface, 'legendInterface'): return LayerRegistry._iface.legendInterface().layers() return list(QgsMapLayerRegistry.instance().mapLayers().values()) def layerAdded(self, layer): LayerRegistry.layers.append(layer) self.layersChanged.emit() def removeLayer(self, layerId): LayerRegistry.layers = [ x for x in LayerRegistry.layers if x.id() != layerId ] self.layersChanged.emit() def removeAllLayers(self): LayerRegistry.layers = [] self.layersChanged.emit() @classmethod def isRaster(self, layer): # only gdal raster layers if layer.type() != layer.RasterLayer: return False if layer.providerType() != 'gdal': return False return True def getRasterLayers(self): return list(filter(self.isRaster, LayerRegistry.layers)) @classmethod def isVector(self, layer): if layer.type() != layer.VectorLayer: return False if layer.providerType() != 'ogr': return False return True def getVectorLayers(self): return list(filter(self.isVector, LayerRegistry.layers))
class OfflineConverter(QObject): progressStopped = pyqtSignal() task_progress_updated = pyqtSignal(int, int) total_progress_updated = pyqtSignal(int, int, str) def __init__(self, project, export_folder, extent, offline_editing): super(OfflineConverter, self).__init__(parent=None) self.__max_task_progress = 0 self.__offline_layers = list() self.__convertor_progress = None # for processing feedback self.__layers = list() self.export_folder = export_folder self.extent = extent self.offline_editing = offline_editing self.project_configuration = ProjectConfiguration(project) offline_editing.layerProgressUpdated.connect( self.on_offline_editing_next_layer) offline_editing.progressModeSet.connect( self.on_offline_editing_max_changed) offline_editing.progressUpdated.connect( self.offline_editing_task_progress) def convert(self): """ Convert the project to a portable project. :param offline_editing: The offline editing instance :param export_folder: The folder to export to """ project = QgsProject.instance() original_project_path = project.fileName() project_filename, _ = os.path.splitext( os.path.basename(original_project_path)) # Write a backup of the current project to a temporary file project_backup_folder = tempfile.mkdtemp() backup_project_path = os.path.join(project_backup_folder, project_filename + '.qgs') QgsProject.instance().write(backup_project_path) try: if not os.path.exists(self.export_folder): os.makedirs(self.export_folder) QApplication.setOverrideCursor(Qt.WaitCursor) self.__offline_layers = list() self.__layers = list(project.mapLayers().values()) self.total_progress_updated.emit(0, 1, self.tr('Creating base map')) # Create the base map before layers are removed if self.project_configuration.create_base_map: if 'processing' not in qgis.utils.plugins: QMessageBox.warning( None, self.tr('QFieldSync requires processing'), self. tr('Creating a basemap with QFieldSync requires the processing plugin to be enabled. Processing is not enabled on your system. Please go to Plugins > Manage and Install Plugins and enable processing.' )) return if self.project_configuration.base_map_type == ProjectProperties.BaseMapType.SINGLE_LAYER: self.createBaseMapLayer( None, self.project_configuration.base_map_layer, self.project_configuration.base_map_tile_size, self.project_configuration.base_map_mupp) else: self.createBaseMapLayer( self.project_configuration.base_map_theme, None, self.project_configuration.base_map_tile_size, self.project_configuration.base_map_mupp) # Loop through all layers and copy/remove/offline them copied_files = list() for current_layer_index, layer in enumerate(self.__layers): self.total_progress_updated.emit( current_layer_index - len(self.__offline_layers), len(self.__layers), self.tr('Copying layers')) layer_source = LayerSource(layer) if layer_source.action == SyncAction.OFFLINE: if self.project_configuration.offline_copy_only_aoi: layer.selectByRect(self.extent) self.__offline_layers.append(layer) elif layer_source.action == SyncAction.NO_ACTION: copied_files = layer_source.copy(self.export_folder, copied_files) elif layer_source.action == SyncAction.KEEP_EXISTENT: layer_source.copy(self.export_folder, copied_files, True) elif layer_source.action == SyncAction.REMOVE: project.removeMapLayer(layer) project_path = os.path.join(self.export_folder, project_filename + "_qfield.qgs") # save the original project path ProjectConfiguration( project).original_project_path = original_project_path # save the offline project twice so that the offline plugin can "know" that it's a relative path QgsProject.instance().write(project_path) try: # Run the offline plugin for gpkg gpkg_filename = "data.gpkg" if self.__offline_layers: offline_layer_ids = [l.id() for l in self.__offline_layers] if not self.offline_editing.convertToOfflineProject( self.export_folder, gpkg_filename, offline_layer_ids, self.project_configuration.offline_copy_only_aoi, self.offline_editing.GPKG): raise Exception( self. tr("Error trying to convert layers to offline layers" )) except AttributeError: # Run the offline plugin for spatialite spatialite_filename = "data.sqlite" if self.__offline_layers: offline_layer_ids = [l.id() for l in self.__offline_layers] if not self.offline_editing.convertToOfflineProject( self.export_folder, spatialite_filename, offline_layer_ids, self.project_configuration.offline_copy_only_aoi): raise Exception( self. tr("Error trying to convert layers to offline layers" )) # Now we have a project state which can be saved as offline project QgsProject.instance().write(project_path) finally: # We need to let the app handle events before loading the next project or QGIS will crash with rasters QCoreApplication.processEvents() QgsProject.instance().clear() QCoreApplication.processEvents() QgsProject.instance().read(backup_project_path) QgsProject.instance().setFileName(original_project_path) QApplication.restoreOverrideCursor() self.total_progress_updated.emit(100, 100, self.tr('Finished')) def createBaseMapLayer(self, map_theme, layer, tile_size, map_units_per_pixel): """ Create a basemap from map layer(s) :param dataPath: The path where the basemap should be writtent to :param extent: The extent rectangle in which data shall be fetched :param map_theme: The name of the map theme to be rendered :param layer: A layer id to be rendered. Will only be used if map_theme is None. :param tile_size: The extent rectangle in which data shall be fetched :param map_units_per_pixel: Number of map units per pixel (1: 1 m per pixel, 10: 10 m per pixel...) """ extent_string = '{},{},{},{}'.format(self.extent.xMinimum(), self.extent.xMaximum(), self.extent.yMinimum(), self.extent.yMaximum()) alg = QgsApplication.instance().processingRegistry( ).createAlgorithmById('qgis:rasterize') params = { 'EXTENT': extent_string, 'MAP_THEME': map_theme, 'LAYER': layer, 'MAP_UNITS_PER_PIXEL': map_units_per_pixel, 'TILE_SIZE': tile_size, 'MAKE_BACKGROUND_TRANSPARENT': False, 'OUTPUT': os.path.join(self.export_folder, 'basemap.gpkg') } feedback = QgsProcessingFeedback() context = QgsProcessingContext() context.setProject(QgsProject.instance()) results, ok = alg.run(params, context, feedback) new_layer = QgsRasterLayer(results['OUTPUT'], self.tr('Basemap')) resample_filter = new_layer.resampleFilter() resample_filter.setZoomedInResampler(QgsCubicRasterResampler()) resample_filter.setZoomedOutResampler(QgsBilinearRasterResampler()) self.project_configuration.project.addMapLayer(new_layer, False) layer_tree = QgsProject.instance().layerTreeRoot() layer_tree.insertLayer(len(layer_tree.children()), new_layer) @pyqtSlot(int, int) def on_offline_editing_next_layer(self, layer_index, layer_count): msg = self.tr(u'Packaging layer {layer_name}').format( layer_name=self.__offline_layers[layer_index - 1].name()) self.total_progress_updated.emit(layer_index, layer_count, msg) @pyqtSlot('QgsOfflineEditing::ProgressMode', int) def on_offline_editing_max_changed(self, _, mode_count): self.__max_task_progress = mode_count @pyqtSlot(int) def offline_editing_task_progress(self, progress): self.task_progress_updated.emit(progress, self.__max_task_progress) def convertorProcessingProgress(self): """ Will create a new progress object for processing to get feedback from the basemap algorithm. """ class ConverterProgress(QObject): progress_updated = pyqtSignal(int, int) def __init__(self): QObject.__init__(self) def error(self, msg): pass def setText(self, msg): pass def setPercentage(self, i): self.progress_updated.emit(i, 100) QCoreApplication.processEvents() def setInfo(self, msg): pass def setCommand(self, msg): pass def setDebugInfo(self, msg): pass def setConsoleInfo(self, msg): pass def close(self): pass if not self.__convertor_progress: self.__convertor_progress = ConverterProgress() self.__convertor_progress.progress_updated.connect( self.task_progress_updated) return self.__convertor_progress
class Generator(QObject): """Builds Model Baker objects from data extracted from databases.""" stdout = pyqtSignal(str) new_message = pyqtSignal(int, str) def __init__(self, tool, uri, inheritance, schema=None, pg_estimated_metadata=False, parent=None, mgmt_uri=None): """ Creates a new Generator objects. :param uri: The uri that should be used in the resulting project. If authcfg is used, make sure the mgmt_uri is set as well. :param mgmt_uri: The uri that should be used to create schemas, tables and query meta information. Does not support authcfg. """ QObject.__init__(self, parent) self.tool = tool self.uri = uri self.mgmt_uri = mgmt_uri self.inheritance = inheritance self.schema = schema or None self.pg_estimated_metadata = pg_estimated_metadata self.db_simple_factory = DbSimpleFactory() db_factory = self.db_simple_factory.create_factory(self.tool) self._db_connector = db_factory.get_db_connector( mgmt_uri or uri, schema) self._db_connector.stdout.connect(self.print_info) self._db_connector.new_message.connect(self.append_print_message) self._additional_ignored_layers = [ ] # List of layers to ignore set by 3rd parties self.collected_print_messages = [] def print_info(self, text): self.stdout.emit(text) def print_messages(self): for message in self.collected_print_messages: self.new_message.emit(message["level"], message["text"]) self.collected_print_messages.clear() def append_print_message(self, level, text): message = {'level': level, 'text': text} if message not in self.collected_print_messages: self.collected_print_messages.append(message) def layers(self, filter_layer_list=[]): ignored_layers = self.get_ignored_layers() tables_info = self.get_tables_info() layers = list() db_factory = self.db_simple_factory.create_factory(self.tool) layer_uri = db_factory.get_layer_uri(self.uri) layer_uri.pg_estimated_metadata = self.pg_estimated_metadata for record in tables_info: # When in PostGIS mode, leaving schema blank should load tables from # all schemas, except the ignored ones if self.schema: if record['schemaname'] != self.schema: continue if ignored_layers and record['tablename'] in ignored_layers: continue if filter_layer_list and record[ 'tablename'] not in filter_layer_list: continue is_domain = record['kind_settings'] == 'ENUM' or record[ 'kind_settings'] == 'CATALOGUE' if 'kind_settings' in record else False is_attribute = bool(record['attribute_name'] ) if 'attribute_name' in record else False is_structure = record[ 'kind_settings'] == 'STRUCTURE' if 'kind_settings' in record else False is_nmrel = record[ 'kind_settings'] == 'ASSOCIATION' if 'kind_settings' in record else False alias = record['table_alias'] if 'table_alias' in record else None if not alias: if is_domain and is_attribute: short_name = record['ili_name'].split( '.')[-2] + '_' + record['ili_name'].split( '.')[-1] if 'ili_name' in record else '' else: short_name = record['ili_name'].split( '.')[-1] if 'ili_name' in record else '' alias = short_name display_expression = '' if 'ili_name' in record: meta_attrs = self.get_meta_attrs(record['ili_name']) for attr_record in meta_attrs: if attr_record['attr_name'] == 'dispExpression': display_expression = attr_record['attr_value'] coord_decimals = record[ 'coord_decimals'] if 'coord_decimals' in record else None coordinate_precision = None if coord_decimals: coordinate_precision = 1 / (10**coord_decimals) layer = Layer( layer_uri.provider, layer_uri.get_data_source_uri(record), record['tablename'], record['srid'], record['extent'] if 'extent' in record else None, record['geometry_column'], QgsWkbTypes.parseType(record['type']) or QgsWkbTypes.Unknown, alias, is_domain, is_structure, is_nmrel, display_expression, coordinate_precision) # Configure fields for current table fields_info = self.get_fields_info(record['tablename']) min_max_info = self.get_min_max_info(record['tablename']) value_map_info = self.get_value_map_info(record['tablename']) re_iliname = re.compile(r'.*\.(.*)$') for fielddef in fields_info: column_name = fielddef['column_name'] fully_qualified_name = fielddef[ 'fully_qualified_name'] if 'fully_qualified_name' in fielddef else None m = re_iliname.match( fully_qualified_name) if fully_qualified_name else None alias = None if 'column_alias' in fielddef: alias = fielddef['column_alias'] if m and not alias: alias = m.group(1) field = Field(column_name) field.alias = alias # Should we hide the field? hide_attribute = False if 'fully_qualified_name' in fielddef: fully_qualified_name = fielddef['fully_qualified_name'] if fully_qualified_name: meta_attrs_column = self.get_meta_attrs( fully_qualified_name) for attr_record in meta_attrs_column: if attr_record['attr_name'] == 'hidden': if attr_record['attr_value'] == 'True': hide_attribute = True break if column_name in IGNORED_FIELDNAMES: hide_attribute = True field.hidden = hide_attribute if column_name in READONLY_FIELDNAMES: field.read_only = True if column_name in min_max_info: field.widget = 'Range' field.widget_config['Min'] = min_max_info[column_name][0] field.widget_config['Max'] = min_max_info[column_name][1] if 'numeric_scale' in fielddef: field.widget_config['Step'] = pow( 10, -1 * fielddef['numeric_scale']) # field.widget_config['Suffix'] = fielddef['unit'] if 'unit' in fielddef else '' if 'unit' in fielddef and fielddef['unit'] is not None: field.alias = '{alias} [{unit}]'.format( alias=alias or column_name, unit=fielddef['unit']) if column_name in value_map_info: field.widget = 'ValueMap' field.widget_config['map'] = [{ val: val } for val in value_map_info[column_name]] if 'texttype' in fielddef and fielddef['texttype'] == 'MTEXT': field.widget = 'TextEdit' field.widget_config['IsMultiline'] = True data_type = self._db_connector.map_data_types( fielddef['data_type']) if 'time' in data_type or 'date' in data_type: field.widget = 'DateTime' field.widget_config['calendar_popup'] = True dateFormat = QLocale( QgsApplication.instance().locale()).dateFormat( QLocale.ShortFormat) timeFormat = QLocale( QgsApplication.instance().locale()).timeFormat( QLocale.ShortFormat) dateTimeFormat = QLocale( QgsApplication.instance().locale()).dateTimeFormat( QLocale.ShortFormat) if data_type == self._db_connector.QGIS_TIME_TYPE: field.widget_config['display_format'] = timeFormat elif data_type == self._db_connector.QGIS_DATE_TIME_TYPE: field.widget_config['display_format'] = dateTimeFormat elif data_type == self._db_connector.QGIS_DATE_TYPE: field.widget_config['display_format'] = dateFormat db_factory.customize_widget_editor(field, data_type) if 'default_value_expression' in fielddef: field.default_value_expression = fielddef[ 'default_value_expression'] if 'enum_domain' in fielddef and fielddef['enum_domain']: field.enum_domain = fielddef['enum_domain'] layer.fields.append(field) layers.append(layer) self.print_messages() return layers def relations(self, layers, filter_layer_list=[]): relations_info = self.get_relations_info(filter_layer_list) layer_map = dict() for layer in layers: if layer.name not in layer_map.keys(): layer_map[layer.name] = list() layer_map[layer.name].append(layer) relations = list() classname_info = [ record['iliname'] for record in self.get_iliname_dbname_mapping() ] for record in relations_info: if record['referencing_table'] in layer_map.keys( ) and record['referenced_table'] in layer_map.keys(): for referencing_layer in layer_map[ record['referencing_table']]: for referenced_layer in layer_map[ record['referenced_table']]: relation = Relation() relation.referencing_layer = referencing_layer relation.referenced_layer = referenced_layer relation.referencing_field = record[ 'referencing_column'] relation.referenced_field = record['referenced_column'] relation.name = record['constraint_name'] relation.strength = QgsRelation.Composition if 'strength' in record and record[ 'strength'] == 'COMPOSITE' else QgsRelation.Association # For domain-class relations, if we have an extended domain, get its child name child_name = None if referenced_layer.is_domain: # Get child name (if domain is extended) fields = [ field for field in referencing_layer.fields if field.name == record['referencing_column'] ] if fields: field = fields[0] if field.enum_domain and field.enum_domain not in classname_info: child_name = field.enum_domain relation.child_domain_name = child_name relations.append(relation) # Create the bags_of_enum structure bags_of_info = self.get_bags_of_info() bags_of_enum = {} for record in bags_of_info: for layer in layers: if record['current_layer_name'] == layer.name: new_item_list = [ layer, record['cardinality_min'] + '..' + record['cardinality_max'], layer_map[record['target_layer_name']][0], self._db_connector.tid, self._db_connector.dispName ] unique_current_layer_name = '{}_{}'.format( record['current_layer_name'], layer.geometry_column) if unique_current_layer_name in bags_of_enum.keys(): bags_of_enum[unique_current_layer_name][ record['attribute']] = new_item_list else: bags_of_enum[unique_current_layer_name] = { record['attribute']: new_item_list } return (relations, bags_of_enum) def legend(self, layers, ignore_node_names=None): legend = LegendGroup(QCoreApplication.translate('LegendGroup', 'root'), ignore_node_names=ignore_node_names) tables = LegendGroup( QCoreApplication.translate('LegendGroup', 'tables')) domains = LegendGroup( QCoreApplication.translate('LegendGroup', 'domains'), False) point_layers = [] line_layers = [] polygon_layers = [] for layer in layers: if layer.geometry_column: geometry_type = QgsWkbTypes.geometryType(layer.wkb_type) if geometry_type == QgsWkbTypes.PointGeometry: point_layers.append(layer) elif geometry_type == QgsWkbTypes.LineGeometry: line_layers.append(layer) elif geometry_type == QgsWkbTypes.PolygonGeometry: polygon_layers.append(layer) else: if layer.is_domain: domains.append(layer) else: tables.append(layer) for l in polygon_layers: legend.append(l) for l in line_layers: legend.append(l) for l in point_layers: legend.append(l) if not tables.is_empty(): legend.append(tables) if not domains.is_empty(): legend.append(domains) return legend def db_or_schema_exists(self): return self._db_connector.db_or_schema_exists() def metadata_exists(self): return self._db_connector.metadata_exists() def set_additional_ignored_layers(self, layer_list): self._additional_ignored_layers = layer_list def get_ignored_layers(self): return self._db_connector.get_ignored_layers( ) + self._additional_ignored_layers def get_tables_info(self): return self._db_connector.get_tables_info() def get_meta_attrs_info(self): return self._db_connector.get_meta_attrs_info() def get_meta_attrs(self, ili_name): return self._db_connector.get_meta_attrs(ili_name) def get_fields_info(self, table_name): return self._db_connector.get_fields_info(table_name) def get_tables_info_without_ignored_tables(self): tables_info = self.get_tables_info() ignored_layers = self.get_ignored_layers() new_tables_info = [] for record in tables_info: if self.schema: if record['schemaname'] != self.schema: continue if ignored_layers and record['tablename'] in ignored_layers: continue new_tables_info.append(record) return new_tables_info def get_min_max_info(self, table_name): return self._db_connector.get_min_max_info(table_name) def get_value_map_info(self, table_name): return self._db_connector.get_value_map_info(table_name) def get_relations_info(self, filter_layer_list=[]): return self._db_connector.get_relations_info(filter_layer_list) def get_bags_of_info(self): return self._db_connector.get_bags_of_info() def get_iliname_dbname_mapping(self): return self._db_connector.get_iliname_dbname_mapping()
class FieldDataCapture(QObject): total_progress_updated = pyqtSignal(int) # percentage def __init__(self): QObject.__init__(self) self.logger = Logger() self.app = AppInterface() def convert_to_offline(self, db, surveyor_expression_dict, export_dir): sys.path.append(PLUGINS_DIR) from qfieldsync.core.layer import LayerSource, SyncAction from qfieldsync.core.offline_converter import OfflineConverter from qfieldsync.core.project import ProjectConfiguration project = QgsProject.instance() extent = QgsRectangle() offline_editing = QgsOfflineEditing() # Configure project project_configuration = ProjectConfiguration(project) project_configuration.create_base_map = False project_configuration.offline_copy_only_aoi = False project_configuration.use_layer_selection = True # Layer config layer_sync_action = LayerConfig.get_field_data_capture_layer_config( db.names) total_projects = len(surveyor_expression_dict) current_progress = 0 for surveyor, layer_config in surveyor_expression_dict.items(): export_folder = os.path.join(export_dir, surveyor) # Get layers (cannot be done out of this for loop because the project is closed and layers are deleted) layers = { layer_name: None for layer_name, _ in layer_sync_action.items() } self.app.core.get_layers(db, layers, True) if not layers: return False, QCoreApplication.translate( "FieldDataCapture", "At least one layer could not be found.") # Configure layers for layer_name, layer in layers.items(): layer_source = LayerSource(layer) layer_source.action = layer_sync_action[layer_name] if layer_name in layer_config: layer_source.select_expression = layer_config[layer_name] layer_source.apply() offline_converter = OfflineConverter(project, export_folder, extent, offline_editing) offline_converter.convert() offline_editing.layerProgressUpdated.disconnect( offline_converter.on_offline_editing_next_layer) offline_editing.progressModeSet.disconnect( offline_converter.on_offline_editing_max_changed) offline_editing.progressUpdated.disconnect( offline_converter.offline_editing_task_progress) current_progress += 1 self.total_progress_updated.emit( int(100 * current_progress / total_projects)) return True, QCoreApplication.translate( "FieldDataCapture", "{count} offline projects have been successfully created in <a href='file:///{normalized_path}'>{path}</a>!" ).format(count=total_projects, normalized_path=normalize_local_url(export_dir), path=export_dir)
class NumberInputPanel(NUMBER_BASE, NUMBER_WIDGET): """ Number input panel for use outside the modeller - this input panel contains a user friendly spin box for entering values. It also allows expressions to be evaluated, but these expressions are evaluated immediately after entry and are not stored anywhere. """ hasChanged = pyqtSignal() def __init__(self, param): super(NumberInputPanel, self).__init__(None) self.setupUi(self) self.spnValue.setExpressionsEnabled(True) self.param = param if self.param.dataType() == QgsProcessingParameterNumber.Integer: self.spnValue.setDecimals(0) else: # Guess reasonable step value if self.param.maximum() is not None and self.param.minimum( ) is not None: try: self.spnValue.setSingleStep( self.calculateStep(float(self.param.minimum()), float(self.param.maximum()))) except: pass if self.param.maximum() is not None: self.spnValue.setMaximum(self.param.maximum()) else: self.spnValue.setMaximum(999999999) if self.param.minimum() is not None: self.spnValue.setMinimum(self.param.minimum()) else: self.spnValue.setMinimum(-999999999) # set default value if param.defaultValue() is not None: self.setValue(param.defaultValue()) try: self.spnValue.setClearValue(float(param.defaultValue())) except: pass elif self.param.minimum() is not None: try: self.setValue(float(self.param.minimum())) self.spnValue.setClearValue(float(self.param.minimum())) except: pass else: self.setValue(0) self.spnValue.setClearValue(0) self.btnSelect.setFixedHeight(self.spnValue.height()) self.btnSelect.clicked.connect(self.showExpressionsBuilder) self.spnValue.valueChanged.connect(lambda: self.hasChanged.emit()) def showExpressionsBuilder(self): context = createExpressionContext() dlg = QgsExpressionBuilderDialog(None, str(self.spnValue.value()), self, 'generic', context) dlg.setWindowTitle(self.tr('Expression based input')) if dlg.exec_() == QDialog.Accepted: exp = QgsExpression(dlg.expressionText()) if not exp.hasParserError(): try: val = float(exp.evaluate(context)) self.setValue(val) except: return def getValue(self): return self.spnValue.value() def setValue(self, value): try: self.spnValue.setValue(float(value)) except: return def calculateStep(self, minimum, maximum): value_range = maximum - minimum if value_range <= 1.0: step = value_range / 10.0 # round to 1 significant figrue return round(step, -int(math.floor(math.log10(step)))) else: return 1.0
class ModellerNumberInputPanel(BASE, WIDGET): """ Number input panel for use inside the modeller - this input panel is based off the base input panel and includes a text based line input for entering values. This allows expressions and other non-numeric values to be set, which are later evalauted to numbers when the model is run. """ hasChanged = pyqtSignal() def __init__(self, param, modelParametersDialog): super(ModellerNumberInputPanel, self).__init__(None) self.setupUi(self) self.param = param self.modelParametersDialog = modelParametersDialog if param.defaultValue(): self.setValue(param.defaultValue()) self.btnSelect.clicked.connect(self.showExpressionsBuilder) self.leText.textChanged.connect(lambda: self.hasChanged.emit()) def showExpressionsBuilder(self): context = createExpressionContext() dlg = QgsExpressionBuilderDialog(None, str(self.leText.text()), self, 'generic', context) context.popScope() values = self.modelParametersDialog.getAvailableValuesOfType( ParameterNumber, OutputNumber) variables = {} for value in values: if isinstance(value, ValueFromInput): name = value.name element = self.modelParametersDialog.model.inputs[name].param desc = element.description else: name = "%s_%s" % (value.alg, value.output) alg = self.modelParametersDialog.model.algs[value.alg] out = alg.algorithm.getOutputFromName(value.output) desc = self.tr("Output '{0}' from algorithm '{1}'").format( out.description(), alg.description) variables[name] = desc values = self.modelParametersDialog.getAvailableValuesOfType( ParameterVector, OutputVector) values.extend( self.modelParametersDialog.getAvailableValuesOfType( ParameterRaster, OutputRaster)) for value in values: if isinstance(value, ValueFromInput): name = value.name element = self.modelParametersDialog.model.inputs[name].param desc = element.description else: name = "%s_%s" % (value.alg, value.output) alg = self.modelParametersDialog.model.algs[value.alg] element = alg.algorithm.getOutputFromName(value.output) desc = self.tr("Output '{0}' from algorithm '{1}'").format( element.description(), alg.description) variables['%s_minx' % name] = self.tr("Minimum X of {0}").format(desc) variables['%s_miny' % name] = self.tr("Minimum Y of {0}").format(desc) variables['%s_maxx' % name] = self.tr("Maximum X of {0}").format(desc) variables['%s_maxy' % name] = self.tr("Maximum Y of {0}").format(desc) if isinstance(element, (ParameterRaster, OutputRaster)): variables['%s_min' % name] = self.tr("Minimum value of {0}").format(desc) variables['%s_max' % name] = self.tr("Maximum value of {0}").format(desc) variables['%s_avg' % name] = self.tr("Mean value of {0}").format(desc) variables['%s_stddev' % name] = self.tr( "Standard deviation of {0}").format(desc) for variable, desc in variables.items(): dlg.expressionBuilder().registerItem("Modeler", variable, "@" + variable, desc, highlightedItem=True) dlg.setWindowTitle(self.tr('Expression based input')) if dlg.exec_() == QDialog.Accepted: exp = QgsExpression(dlg.expressionText()) if not exp.hasParserError(): self.setValue(dlg.expressionText()) def getValue(self): value = self.leText.text() values = [] for param in self.modelParametersDialog.model.parameters: if isinstance(param, ParameterNumber): if "@" + param.name() in value: values.append(ValueFromInput(param.name())) for alg in list(self.modelParametersDialog.model.algs.values()): for out in alg.algorithm.outputs: if isinstance(out, OutputNumber) and "@%s_%s" % (alg.name(), out.name) in value: values.append(ValueFromOutput(alg.name(), out.name)) if values: return CompoundValue(values, value) else: return value def setValue(self, value): self.leText.setText(str(value))
class DlgSqlWindow(QWidget, Ui_Dialog): nameChanged = pyqtSignal(str) QUERY_HISTORY_LIMIT = 20 def __init__(self, iface, db, parent=None): QWidget.__init__(self, parent) self.mainWindow = parent self.iface = iface self.db = db self.dbType = db.connection().typeNameString() self.connectionName = db.connection().connectionName() self.filter = "" self.modelAsync = None 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( self.tr(u"{0} - {1} [{2}]").format(self.windowTitle(), self.connectionName, self.dbType)) self.defaultLayerName = self.tr('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() settings = QgsSettings() self.history = settings.value('DB_Manager/queryHistory/' + self.dbType, {self.connectionName: []}) if self.connectionName not in self.history: self.history[self.connectionName] = [] self.queryHistoryWidget.setVisible(False) self.queryHistoryTableWidget.verticalHeader().hide() self.queryHistoryTableWidget.doubleClicked.connect( self.insertQueryInEditor) self.populateQueryHistory() self.btnQueryHistory.toggled.connect(self.showHideQueryHistory) self.btnCancel.setEnabled(False) self.btnCancel.clicked.connect(self.executeSqlCanceled) self.btnCancel.setShortcut(QKeySequence.Cancel) self.progressBar.setEnabled(False) self.progressBar.setRange(0, 100) self.progressBar.setValue(0) self.progressBar.setFormat("") self.progressBar.setAlignment(Qt.AlignCenter) # 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.presetSaveAsFile.clicked.connect(self.saveAsFilePreset) self.presetLoadFile.clicked.connect(self.loadFilePreset) 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 # hide the load query as layer if feature is not supported self._loadAsLayerAvailable = self.db.connector.hasCustomQuerySupport() self.loadAsLayerGroup.setVisible(self._loadAsLayerAvailable) if self._loadAsLayerAvailable: self.layerTypeWidget.hide() # show if load as raster is supported self.loadLayerBtn.clicked.connect(self.loadSqlLayer) self.getColumnsBtn.clicked.connect(self.fillColumnCombos) self.loadAsLayerGroup.toggled.connect(self.loadAsLayerToggled) self.loadAsLayerToggled(False) self._createViewAvailable = self.db.connector.hasCreateSpatialViewSupport( ) self.btnCreateView.setVisible(self._createViewAvailable) if self._createViewAvailable: self.btnCreateView.clicked.connect(self.createView) self.queryBuilderFirst = True self.queryBuilderBtn.setIcon(QIcon(":/db_manager/icons/sql.gif")) self.queryBuilderBtn.clicked.connect(self.displayQueryBuilder) self.presetName.textChanged.connect(self.nameChanged) def insertQueryInEditor(self, item): sql = item.data(Qt.DisplayRole) self.editSql.insertText(sql) def showHideQueryHistory(self, visible): self.queryHistoryWidget.setVisible(visible) def populateQueryHistory(self): self.queryHistoryTableWidget.clearContents() self.queryHistoryTableWidget.setRowCount(0) dictlist = self.history[self.connectionName] if not dictlist: return for i in range(len(dictlist)): self.queryHistoryTableWidget.insertRow(0) queryItem = QTableWidgetItem(dictlist[i]['query']) rowsItem = QTableWidgetItem(str(dictlist[i]['rows'])) durationItem = QTableWidgetItem(str(dictlist[i]['secs'])) self.queryHistoryTableWidget.setItem(0, 0, queryItem) self.queryHistoryTableWidget.setItem(0, 1, rowsItem) self.queryHistoryTableWidget.setItem(0, 2, durationItem) self.queryHistoryTableWidget.resizeColumnsToContents() self.queryHistoryTableWidget.resizeRowsToContents() def writeQueryHistory(self, sql, affectedRows, secs): if len(self.history[self.connectionName]) >= self.QUERY_HISTORY_LIMIT: self.history[self.connectionName].pop(0) settings = QgsSettings() self.history[self.connectionName].append({ 'query': sql, 'rows': affectedRows, 'secs': secs }) settings.setValue('DB_Manager/queryHistory/' + self.dbType, self.history) self.populateQueryHistory() def getQueryHash(self, name): return 'q%s' % md5(name.encode('utf8')).hexdigest() 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 = str(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 saveAsFilePreset(self): settings = QgsSettings() lastDir = settings.value('DB_Manager/lastDirSQLFIle', "") query = self._getSqlQuery() if query == "": return filename, _ = QFileDialog.getSaveFileName( self, self.tr('Save SQL Query'), lastDir, self.tr("SQL File (*.sql, *.SQL)")) if filename: if not filename.lower().endswith('.sql'): filename += ".sql" with open(filename, 'w') as f: f.write(query) lastDir = os.path.dirname(filename) settings.setValue('DB_Manager/lastDirSQLFile', lastDir) def loadFilePreset(self): settings = QgsSettings() lastDir = settings.value('DB_Manager/lastDirSQLFIle', "") filename, _ = QFileDialog.getOpenFileName( self, self.tr("Load SQL Query"), lastDir, self.tr("SQL File (*.sql, *.SQL)")) if filename: with open(filename, 'r') as f: self.editSql.clear() for line in f: self.editSql.insertText(line) lastDir = os.path.dirname(filename) settings.setValue('DB_Manager/lastDirSQLFile', lastDir) def deletePreset(self): name = self.presetCombo.currentText() QgsProject.instance().removeEntry( 'DBManager', 'savedQueries/' + 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] self.editSql.setText(query) def loadAsLayerToggled(self, checked): self.loadAsLayerGroup.setChecked(checked) self.loadAsLayerWidget.setVisible(checked) if checked: self.fillColumnCombos() def clearSql(self): self.editSql.clear() self.editSql.setFocus() self.filter = "" def updateUiWhileSqlExecution(self, status): if status: for i in range(0, self.mainWindow.tabs.count()): if i != self.mainWindow.tabs.currentIndex(): self.mainWindow.tabs.setTabEnabled(i, False) self.mainWindow.menuBar.setEnabled(False) self.mainWindow.toolBar.setEnabled(False) self.mainWindow.tree.setEnabled(False) for w in self.findChildren(QWidget): w.setEnabled(False) self.btnCancel.setEnabled(True) self.progressBar.setEnabled(True) self.progressBar.setRange(0, 0) else: for i in range(0, self.mainWindow.tabs.count()): if i != self.mainWindow.tabs.currentIndex(): self.mainWindow.tabs.setTabEnabled(i, True) self.mainWindow.refreshTabs() self.mainWindow.menuBar.setEnabled(True) self.mainWindow.toolBar.setEnabled(True) self.mainWindow.tree.setEnabled(True) for w in self.findChildren(QWidget): w.setEnabled(True) self.btnCancel.setEnabled(False) self.progressBar.setRange(0, 100) self.progressBar.setEnabled(False) def executeSqlCanceled(self): self.btnCancel.setEnabled(False) self.modelAsync.cancel() def executeSqlCompleted(self): self.updateUiWhileSqlExecution(False) with OverrideCursor(Qt.WaitCursor): if self.modelAsync.task.status() == QgsTask.Complete: model = self.modelAsync.model quotedCols = [] self.viewResult.setModel(model) self.lblResult.setText( self.tr("{0} rows, {1:.3f} seconds").format( model.affectedRows(), model.secs())) cols = self.viewResult.model().columnNames() for col in cols: quotedCols.append(self.db.connector.quoteId(col)) self.setColumnCombos(cols, quotedCols) self.writeQueryHistory(self.modelAsync.task.sql, model.affectedRows(), model.secs()) self.update() elif not self.modelAsync.canceled: DlgDbError.showError(self.modelAsync.error, self) self.uniqueModel.clear() self.geomCombo.clear() def executeSql(self): sql = self._getExecutableSqlQuery() if sql == "": return # delete the old model old_model = self.viewResult.model() self.viewResult.setModel(None) if old_model: old_model.deleteLater() try: self.modelAsync = self.db.sqlResultModelAsync(sql, self) self.modelAsync.done.connect(self.executeSqlCompleted) self.updateUiWhileSqlExecution(True) QgsApplication.taskManager().addTask(self.modelAsync.task) except Exception as e: DlgDbError.showError(e, self) self.uniqueModel.clear() self.geomCombo.clear() return 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._getSqlExecutableQuery() 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: e = BaseError( self. tr("There was an error creating the SQL layer, please check the logs for further information." )) DlgDbError.showError(e, self) 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 fillColumnCombos(self): query = self._getExecutableSqlQuery() 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 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) 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 value in dictionary.values(): 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 createView(self): name, ok = QInputDialog.getText(None, self.tr("View Name"), self.tr("View name")) if ok: try: self.db.connector.createSpatialView( name, self._getExecutableSqlQuery()) except BaseError as e: DlgDbError.showError(e, self) def _getSqlQuery(self): sql = self.editSql.selectedText() if len(sql) == 0: sql = self.editSql.text() return sql def _getExecutableSqlQuery(self): sql = self._getSqlQuery() # Clean it up! lines = [] for line in sql.split('\n'): if not line.strip().startswith('--'): lines.append(line) sql = ' '.join(lines) return sql.strip() 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 ModelerDialog(BASE, WIDGET): ALG_ITEM = 'ALG_ITEM' PROVIDER_ITEM = 'PROVIDER_ITEM' GROUP_ITEM = 'GROUP_ITEM' NAME_ROLE = Qt.UserRole TAG_ROLE = Qt.UserRole + 1 TYPE_ROLE = Qt.UserRole + 2 CANVAS_SIZE = 4000 update_model = pyqtSignal() def __init__(self, model=None): super().__init__(None) self.setAttribute(Qt.WA_DeleteOnClose) self.setupUi(self) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.centralWidget().layout().insertWidget(0, self.bar) try: self.setDockOptions(self.dockOptions() | QMainWindow.GroupedDragging) except: pass self.mToolbar.setIconSize(iface.iconSize()) self.mActionOpen.setIcon( QgsApplication.getThemeIcon('/mActionFileOpen.svg')) self.mActionSave.setIcon( QgsApplication.getThemeIcon('/mActionFileSave.svg')) self.mActionSaveAs.setIcon( QgsApplication.getThemeIcon('/mActionFileSaveAs.svg')) self.mActionZoomActual.setIcon( QgsApplication.getThemeIcon('/mActionZoomActual.svg')) self.mActionZoomIn.setIcon( QgsApplication.getThemeIcon('/mActionZoomIn.svg')) self.mActionZoomOut.setIcon( QgsApplication.getThemeIcon('/mActionZoomOut.svg')) self.mActionExportImage.setIcon( QgsApplication.getThemeIcon('/mActionSaveMapAsImage.svg')) self.mActionZoomToItems.setIcon( QgsApplication.getThemeIcon('/mActionZoomFullExtent.svg')) self.mActionExportPdf.setIcon( QgsApplication.getThemeIcon('/mActionSaveAsPDF.svg')) self.mActionExportSvg.setIcon( QgsApplication.getThemeIcon('/mActionSaveAsSVG.svg')) #self.mActionExportPython.setIcon( # QgsApplication.getThemeIcon('/mActionSaveAsPython.svg')) self.mActionEditHelp.setIcon( QgsApplication.getThemeIcon('/mActionEditHelpContent.svg')) self.mActionRun.setIcon( QgsApplication.getThemeIcon('/mActionStart.svg')) self.addDockWidget(Qt.LeftDockWidgetArea, self.propertiesDock) self.addDockWidget(Qt.LeftDockWidgetArea, self.inputsDock) self.addDockWidget(Qt.LeftDockWidgetArea, self.algorithmsDock) self.tabifyDockWidget(self.inputsDock, self.algorithmsDock) self.inputsDock.raise_() self.zoom = 1 self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint) settings = QgsSettings() self.restoreState( settings.value("/Processing/stateModeler", QByteArray())) self.restoreGeometry( settings.value("/Processing/geometryModeler", QByteArray())) self.scene = ModelerScene(self, dialog=self) self.scene.setSceneRect( QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE)) self.view.setScene(self.scene) self.view.setAcceptDrops(True) self.view.ensureVisible(0, 0, 10, 10) def _dragEnterEvent(event): if event.mimeData().hasText(): event.acceptProposedAction() else: event.ignore() def _dropEvent(event): if event.mimeData().hasText(): itemId = event.mimeData().text() if itemId in [ param.id() for param in QgsApplication.instance(). processingRegistry().parameterTypes() ]: self.addInputOfType(itemId, event.pos()) else: alg = QgsApplication.processingRegistry( ).createAlgorithmById(itemId) if alg is not None: self._addAlgorithm(alg, event.pos()) event.accept() else: event.ignore() def _dragMoveEvent(event): if event.mimeData().hasText(): event.accept() else: event.ignore() def _wheelEvent(event): self.view.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) settings = QgsSettings() factor = settings.value('/qgis/zoom_favor', 2.0) # "Normal" mouse has an angle delta of 120, precision mouses provide data # faster, in smaller steps factor = 1.0 + (factor - 1.0) / 120.0 * abs(event.angleDelta().y()) if (event.modifiers() == Qt.ControlModifier): factor = 1.0 + (factor - 1.0) / 20.0 if event.angleDelta().y() < 0: factor = 1 / factor self.view.scale(factor, factor) def _enterEvent(e): QGraphicsView.enterEvent(self.view, e) self.view.viewport().setCursor(Qt.ArrowCursor) def _mouseReleaseEvent(e): QGraphicsView.mouseReleaseEvent(self.view, e) self.view.viewport().setCursor(Qt.ArrowCursor) def _mousePressEvent(e): if e.button() == Qt.MidButton: self.previousMousePos = e.pos() else: QGraphicsView.mousePressEvent(self.view, e) def _mouseMoveEvent(e): if e.buttons() == Qt.MidButton: offset = self.previousMousePos - e.pos() self.previousMousePos = e.pos() self.view.verticalScrollBar().setValue( self.view.verticalScrollBar().value() + offset.y()) self.view.horizontalScrollBar().setValue( self.view.horizontalScrollBar().value() + offset.x()) else: QGraphicsView.mouseMoveEvent(self.view, e) self.view.setDragMode(QGraphicsView.ScrollHandDrag) self.view.dragEnterEvent = _dragEnterEvent self.view.dropEvent = _dropEvent self.view.dragMoveEvent = _dragMoveEvent self.view.wheelEvent = _wheelEvent self.view.enterEvent = _enterEvent self.view.mousePressEvent = _mousePressEvent self.view.mouseMoveEvent = _mouseMoveEvent def _mimeDataInput(items): mimeData = QMimeData() text = items[0].data(0, Qt.UserRole) mimeData.setText(text) return mimeData self.inputsTree.mimeData = _mimeDataInput self.inputsTree.setDragDropMode(QTreeWidget.DragOnly) self.inputsTree.setDropIndicatorShown(True) def _mimeDataAlgorithm(items): item = items[0] mimeData = None if isinstance(item, TreeAlgorithmItem): mimeData = QMimeData() mimeData.setText(item.alg.id()) return mimeData self.algorithmTree.mimeData = _mimeDataAlgorithm self.algorithmTree.setDragDropMode(QTreeWidget.DragOnly) self.algorithmTree.setDropIndicatorShown(True) if hasattr(self.searchBox, 'setPlaceholderText'): self.searchBox.setPlaceholderText( QCoreApplication.translate('ModelerDialog', 'Search…')) if hasattr(self.textName, 'setPlaceholderText'): self.textName.setPlaceholderText(self.tr('Enter model name here')) if hasattr(self.textGroup, 'setPlaceholderText'): self.textGroup.setPlaceholderText(self.tr('Enter group name here')) # Connect signals and slots self.inputsTree.doubleClicked.connect(self.addInput) self.searchBox.textChanged.connect(self.textChanged) self.algorithmTree.doubleClicked.connect(self.addAlgorithm) # Ctrl+= should also trigger a zoom in action ctrlEquals = QShortcut(QKeySequence("Ctrl+="), self) ctrlEquals.activated.connect(self.zoomIn) self.mActionOpen.triggered.connect(self.openModel) self.mActionSave.triggered.connect(self.save) self.mActionSaveAs.triggered.connect(self.saveAs) self.mActionZoomIn.triggered.connect(self.zoomIn) self.mActionZoomOut.triggered.connect(self.zoomOut) self.mActionZoomActual.triggered.connect(self.zoomActual) self.mActionZoomToItems.triggered.connect(self.zoomToItems) self.mActionExportImage.triggered.connect(self.exportAsImage) self.mActionExportPdf.triggered.connect(self.exportAsPdf) self.mActionExportSvg.triggered.connect(self.exportAsSvg) #self.mActionExportPython.triggered.connect(self.exportAsPython) self.mActionEditHelp.triggered.connect(self.editHelp) self.mActionRun.triggered.connect(self.runModel) if model is not None: self.model = model.create() self.model.setSourceFilePath(model.sourceFilePath()) self.textGroup.setText(self.model.group()) self.textName.setText(self.model.displayName()) self.repaintModel() else: self.model = QgsProcessingModelAlgorithm() self.model.setProvider( QgsApplication.processingRegistry().providerById('model')) self.fillInputsTree() self.fillTreeUsingProviders() self.view.centerOn(0, 0) self.help = None self.hasChanged = False def closeEvent(self, evt): settings = QgsSettings() settings.setValue("/Processing/stateModeler", self.saveState()) settings.setValue("/Processing/geometryModeler", self.saveGeometry()) if self.hasChanged: ret = QMessageBox.question( self, self.tr('Save Model?'), self. tr('There are unsaved changes in this model. Do you want to keep those?' ), QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard, QMessageBox.Cancel) if ret == QMessageBox.Save: self.saveModel(False) evt.accept() elif ret == QMessageBox.Discard: evt.accept() else: evt.ignore() else: evt.accept() def editHelp(self): alg = self.model dlg = HelpEditionDialog(alg) dlg.exec_() if dlg.descriptions: self.model.setHelpContent(dlg.descriptions) self.hasChanged = True def runModel(self): if len(self.model.childAlgorithms()) == 0: self.bar.pushMessage( "", self. tr("Model doesn't contain any algorithm and/or parameter and can't be executed" ), level=Qgis.Warning, duration=5) return dlg = AlgorithmDialog(self.model) dlg.exec_() def save(self): self.saveModel(False) def saveAs(self): self.saveModel(True) def zoomIn(self): self.view.setTransformationAnchor(QGraphicsView.NoAnchor) point = self.view.mapToScene( QPoint(self.view.viewport().width() / 2, self.view.viewport().height() / 2)) settings = QgsSettings() factor = settings.value('/qgis/zoom_favor', 2.0) self.view.scale(factor, factor) self.view.centerOn(point) self.repaintModel() def zoomOut(self): self.view.setTransformationAnchor(QGraphicsView.NoAnchor) point = self.view.mapToScene( QPoint(self.view.viewport().width() / 2, self.view.viewport().height() / 2)) settings = QgsSettings() factor = settings.value('/qgis/zoom_favor', 2.0) factor = 1 / factor self.view.scale(factor, factor) self.view.centerOn(point) self.repaintModel() def zoomActual(self): point = self.view.mapToScene( QPoint(self.view.viewport().width() / 2, self.view.viewport().height() / 2)) self.view.resetTransform() self.view.centerOn(point) def zoomToItems(self): totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) self.view.fitInView(totalRect, Qt.KeepAspectRatio) def exportAsImage(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName( self, self.tr('Save Model As Image'), '', self.tr('PNG files (*.png *.PNG)')) if not filename: return if not filename.lower().endswith('.png'): filename += '.png' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) imgRect = QRectF(0, 0, totalRect.width(), totalRect.height()) img = QImage(totalRect.width(), totalRect.height(), QImage.Format_ARGB32_Premultiplied) img.fill(Qt.white) painter = QPainter() painter.setRenderHint(QPainter.Antialiasing) painter.begin(img) self.scene.render(painter, imgRect, totalRect) painter.end() img.save(filename) self.bar.pushMessage("", self.tr("Model was correctly exported as image"), level=Qgis.Success, duration=5) self.repaintModel(controls=True) def exportAsPdf(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName( self, self.tr('Save Model As PDF'), '', self.tr('PDF files (*.pdf *.PDF)')) if not filename: return if not filename.lower().endswith('.pdf'): filename += '.pdf' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) printerRect = QRectF(0, 0, totalRect.width(), totalRect.height()) printer = QPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(filename) printer.setPaperSize(QSizeF(printerRect.width(), printerRect.height()), QPrinter.DevicePixel) printer.setFullPage(True) painter = QPainter(printer) self.scene.render(painter, printerRect, totalRect) painter.end() self.bar.pushMessage("", self.tr("Model was correctly exported as PDF"), level=Qgis.Success, duration=5) self.repaintModel(controls=True) def exportAsSvg(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName( self, self.tr('Save Model As SVG'), '', self.tr('SVG files (*.svg *.SVG)')) if not filename: return if not filename.lower().endswith('.svg'): filename += '.svg' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) svgRect = QRectF(0, 0, totalRect.width(), totalRect.height()) svg = QSvgGenerator() svg.setFileName(filename) svg.setSize(QSize(totalRect.width(), totalRect.height())) svg.setViewBox(svgRect) svg.setTitle(self.model.displayName()) painter = QPainter(svg) self.scene.render(painter, svgRect, totalRect) painter.end() self.bar.pushMessage("", self.tr("Model was correctly exported as SVG"), level=Qgis.Success, duration=5) self.repaintModel(controls=True) def exportAsPython(self): filename, filter = QFileDialog.getSaveFileName( self, self.tr('Save Model As Python Script'), '', self.tr('Python files (*.py *.PY)')) if not filename: return if not filename.lower().endswith('.py'): filename += '.py' text = self.model.asPythonCode() with codecs.open(filename, 'w', encoding='utf-8') as fout: fout.write(text) self.bar.pushMessage( "", self.tr("Model was correctly exported as python script"), level=Qgis.Success, duration=5) def saveModel(self, saveAs): if str(self.textGroup.text()).strip() == '' \ or str(self.textName.text()).strip() == '': QMessageBox.warning( self, self.tr('Warning'), self.tr('Please enter group and model names before saving')) return self.model.setName(str(self.textName.text())) self.model.setGroup(str(self.textGroup.text())) if self.model.sourceFilePath() and not saveAs: filename = self.model.sourceFilePath() else: filename, filter = QFileDialog.getSaveFileName( self, self.tr('Save Model'), ModelerUtils.modelsFolders()[0], self.tr('Processing models (*.model3)')) if filename: if not filename.endswith('.model3'): filename += '.model3' self.model.setSourceFilePath(filename) if filename: if not self.model.toFile(filename): if saveAs: QMessageBox.warning( self, self.tr('I/O error'), self.tr('Unable to save edits. Reason:\n {0}').format( str(sys.exc_info()[1]))) else: QMessageBox.warning( self, self.tr("Can't save model"), QCoreApplication. translate('QgsPluginInstallerInstallingDialog', ( "This model can't be saved in its original location (probably you do not " "have permission to do it). Please, use the 'Save as…' option." ))) return self.update_model.emit() self.bar.pushMessage("", self.tr("Model was correctly saved"), level=Qgis.Success, duration=5) self.hasChanged = False def openModel(self): filename, selected_filter = QFileDialog.getOpenFileName( self, self.tr('Open Model'), ModelerUtils.modelsFolders()[0], self.tr('Processing models (*.model3 *.MODEL3)')) if filename: self.loadModel(filename) def loadModel(self, filename): alg = QgsProcessingModelAlgorithm() if alg.fromFile(filename): self.model = alg self.model.setProvider( QgsApplication.processingRegistry().providerById('model')) self.textGroup.setText(alg.group()) self.textName.setText(alg.name()) self.repaintModel() self.view.centerOn(0, 0) self.hasChanged = False else: QgsMessageLog.logMessage( self.tr('Could not load model {0}').format(filename), self.tr('Processing'), Qgis.Critical) QMessageBox.critical( self, self.tr('Open Model'), self.tr('The selected model could not be loaded.\n' 'See the log for more information.')) def repaintModel(self, controls=True): self.scene = ModelerScene(self, dialog=self) self.scene.setSceneRect( QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE)) self.scene.paintModel(self.model, controls) self.view.setScene(self.scene) def addInput(self): item = self.inputsTree.currentItem() param = item.data(0, Qt.UserRole) self.addInputOfType(param) def addInputOfType(self, paramType, pos=None): dlg = ModelerParameterDefinitionDialog(self.model, paramType) dlg.exec_() if dlg.param is not None: if pos is None: pos = self.getPositionForParameterItem() if isinstance(pos, QPoint): pos = QPointF(pos) component = QgsProcessingModelParameter(dlg.param.name()) component.setDescription(dlg.param.name()) component.setPosition(pos) self.model.addModelParameter(dlg.param, component) self.repaintModel() # self.view.ensureVisible(self.scene.getLastParameterItem()) self.hasChanged = True def getPositionForParameterItem(self): MARGIN = 20 BOX_WIDTH = 200 BOX_HEIGHT = 80 if len(self.model.parameterComponents()) > 0: maxX = max([ i.position().x() for i in list(self.model.parameterComponents().values()) ]) newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) else: newX = MARGIN + BOX_WIDTH / 2 return QPointF(newX, MARGIN + BOX_HEIGHT / 2) def textChanged(self): text = self.searchBox.text().strip(' ').lower() for item in list(self.disabledProviderItems.values()): item.setHidden(True) self._filterItem(self.algorithmTree.invisibleRootItem(), [t for t in text.split(' ') if t]) if text: self.algorithmTree.expandAll() self.disabledWithMatchingAlgs = [] for provider in QgsApplication.processingRegistry().providers(): if not provider.isActive(): for alg in provider.algorithms(): if text in alg.name(): self.disabledWithMatchingAlgs.append(provider.id()) break else: self.algorithmTree.collapseAll() def _filterItem(self, item, text): if (item.childCount() > 0): show = False for i in range(item.childCount()): child = item.child(i) showChild = self._filterItem(child, text) show = (showChild or show) and item not in list( self.disabledProviderItems.values()) item.setHidden(not show) return show elif isinstance(item, (TreeAlgorithmItem, TreeActionItem)): # hide if every part of text is not contained somewhere in either the item text or item user role item_text = [ item.text(0).lower(), item.data(0, ModelerDialog.NAME_ROLE).lower() ] if isinstance(item, TreeAlgorithmItem): item_text.append(item.alg.id().lower()) if item.alg.shortDescription(): item_text.append(item.alg.shortDescription().lower()) item_text.extend( [t.lower() for t in item.data(0, ModelerDialog.TAG_ROLE)]) hide = bool(text) and not all( any(part in t for t in item_text) for part in text) item.setHidden(hide) return not hide else: item.setHidden(True) return False def fillInputsTree(self): icon = QIcon(os.path.join(pluginPath, 'images', 'input.svg')) parametersItem = QTreeWidgetItem() parametersItem.setText(0, self.tr('Parameters')) sortedParams = sorted( QgsApplication.instance().processingRegistry().parameterTypes(), key=lambda pt: pt.name()) for param in sortedParams: if param.flags() & QgsProcessingParameterType.ExposeToModeler: paramItem = QTreeWidgetItem() paramItem.setText(0, param.name()) paramItem.setData(0, Qt.UserRole, param.id()) paramItem.setIcon(0, icon) paramItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) paramItem.setToolTip(0, param.description()) parametersItem.addChild(paramItem) self.inputsTree.addTopLevelItem(parametersItem) parametersItem.setExpanded(True) def addAlgorithm(self): item = self.algorithmTree.currentItem() if isinstance(item, TreeAlgorithmItem): alg = QgsApplication.processingRegistry().createAlgorithmById( item.alg.id()) self._addAlgorithm(alg) def _addAlgorithm(self, alg, pos=None): dlg = ModelerParametersDialog(alg, self.model) if dlg.exec_(): alg = dlg.createAlgorithm() if pos is None: alg.setPosition(self.getPositionForAlgorithmItem()) else: alg.setPosition(pos) from processing.modeler.ModelerGraphicItem import ModelerGraphicItem for i, out in enumerate(alg.modelOutputs()): alg.modelOutput(out).setPosition( alg.position() + QPointF(ModelerGraphicItem.BOX_WIDTH, (i + 1.5) * ModelerGraphicItem.BOX_HEIGHT)) self.model.addChildAlgorithm(alg) self.repaintModel() self.hasChanged = True def getPositionForAlgorithmItem(self): MARGIN = 20 BOX_WIDTH = 200 BOX_HEIGHT = 80 if self.model.childAlgorithms(): maxX = max([ alg.position().x() for alg in list(self.model.childAlgorithms().values()) ]) maxY = max([ alg.position().y() for alg in list(self.model.childAlgorithms().values()) ]) newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) newY = min(MARGIN + BOX_HEIGHT + maxY, self.CANVAS_SIZE - BOX_HEIGHT) else: newX = MARGIN + BOX_WIDTH / 2 newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2 return QPointF(newX, newY) def fillTreeUsingProviders(self): self.algorithmTree.clear() self.disabledProviderItems = {} # TODO - replace with proper model for toolbox! # first add qgis/native providers, since they create top level groups for provider in QgsApplication.processingRegistry().providers(): if provider.id() in ('qgis', 'native', '3d'): self.addAlgorithmsFromProvider( provider, self.algorithmTree.invisibleRootItem()) else: continue self.algorithmTree.sortItems(0, Qt.AscendingOrder) for provider in QgsApplication.processingRegistry().providers(): if provider.id() in ('qgis', 'native', '3d'): # already added continue else: providerItem = TreeProviderItem(provider, self.algorithmTree, self) # insert non-native providers at end of tree, alphabetically for i in range( self.algorithmTree.invisibleRootItem().childCount()): child = self.algorithmTree.invisibleRootItem().child(i) if isinstance(child, TreeProviderItem): if child.text(0) > providerItem.text(0): break self.algorithmTree.insertTopLevelItem(i + 1, providerItem) if not provider.isActive(): providerItem.setHidden(True) self.disabledProviderItems[provider.id()] = providerItem def addAlgorithmsFromProvider(self, provider, parent): groups = {} count = 0 algs = provider.algorithms() active = provider.isActive() # Add algorithms for alg in algs: if alg.flags() & QgsProcessingAlgorithm.FlagHideFromModeler: continue groupItem = None if alg.group() in groups: groupItem = groups[alg.group()] else: # check if group already exists for i in range(parent.childCount()): if parent.child(i).text(0) == alg.group(): groupItem = parent.child(i) groups[alg.group()] = groupItem break if not groupItem: groupItem = TreeGroupItem(alg.group()) if not active: groupItem.setInactive() if provider.id() in ('qgis', 'native', '3d'): groupItem.setIcon(0, provider.icon()) groups[alg.group()] = groupItem algItem = TreeAlgorithmItem(alg) if not active: algItem.setForeground(0, Qt.darkGray) groupItem.addChild(algItem) count += 1 text = provider.name() if not provider.id() in ('qgis', 'native', '3d'): if not active: def activateProvider(): self.activateProvider(provider.id()) label = QLabel( text + " <a href='%s'>Activate</a>") label.setStyleSheet( "QLabel {background-color: white; color: grey;}") label.linkActivated.connect(activateProvider) self.algorithmTree.setItemWidget(parent, 0, label) else: parent.setText(0, text) for group, groupItem in sorted(groups.items(), key=operator.itemgetter(1)): parent.addChild(groupItem) if not provider.id() in ('qgis', 'native', '3d'): parent.setHidden(parent.childCount() == 0)
class SettingsWatcher(QObject): settingsChanged = pyqtSignal()
class ModelerDialog(BASE, WIDGET): ALG_ITEM = 'ALG_ITEM' PROVIDER_ITEM = 'PROVIDER_ITEM' GROUP_ITEM = 'GROUP_ITEM' NAME_ROLE = Qt.UserRole TAG_ROLE = Qt.UserRole + 1 TYPE_ROLE = Qt.UserRole + 2 CANVAS_SIZE = 4000 update_model = pyqtSignal() def __init__(self, model=None): super().__init__(None) self.setAttribute(Qt.WA_DeleteOnClose) self.setupUi(self) self._variables_scope = None # LOTS of bug reports when we include the dock creation in the UI file # see e.g. #16428, #19068 # So just roll it all by hand......! self.propertiesDock = QgsDockWidget(self) self.propertiesDock.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.propertiesDock.setObjectName("propertiesDock") propertiesDockContents = QWidget() self.verticalDockLayout_1 = QVBoxLayout(propertiesDockContents) self.verticalDockLayout_1.setContentsMargins(0, 0, 0, 0) self.verticalDockLayout_1.setSpacing(0) self.scrollArea_1 = QgsScrollArea(propertiesDockContents) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.scrollArea_1.sizePolicy().hasHeightForWidth()) self.scrollArea_1.setSizePolicy(sizePolicy) self.scrollArea_1.setFocusPolicy(Qt.WheelFocus) self.scrollArea_1.setFrameShape(QFrame.NoFrame) self.scrollArea_1.setFrameShadow(QFrame.Plain) self.scrollArea_1.setWidgetResizable(True) self.scrollAreaWidgetContents_1 = QWidget() self.gridLayout = QGridLayout(self.scrollAreaWidgetContents_1) self.gridLayout.setContentsMargins(6, 6, 6, 6) self.gridLayout.setSpacing(4) self.label_1 = QLabel(self.scrollAreaWidgetContents_1) self.gridLayout.addWidget(self.label_1, 0, 0, 1, 1) self.textName = QLineEdit(self.scrollAreaWidgetContents_1) self.gridLayout.addWidget(self.textName, 0, 1, 1, 1) self.label_2 = QLabel(self.scrollAreaWidgetContents_1) self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) self.textGroup = QLineEdit(self.scrollAreaWidgetContents_1) self.gridLayout.addWidget(self.textGroup, 1, 1, 1, 1) self.label_1.setText(self.tr("Name")) self.textName.setToolTip(self.tr("Enter model name here")) self.label_2.setText(self.tr("Group")) self.textGroup.setToolTip(self.tr("Enter group name here")) self.scrollArea_1.setWidget(self.scrollAreaWidgetContents_1) self.verticalDockLayout_1.addWidget(self.scrollArea_1) self.propertiesDock.setWidget(propertiesDockContents) self.propertiesDock.setWindowTitle(self.tr("Model Properties")) self.inputsDock = QgsDockWidget(self) self.inputsDock.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.inputsDock.setObjectName("inputsDock") self.inputsDockContents = QWidget() self.verticalLayout_3 = QVBoxLayout(self.inputsDockContents) self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.scrollArea_2 = QgsScrollArea(self.inputsDockContents) sizePolicy.setHeightForWidth( self.scrollArea_2.sizePolicy().hasHeightForWidth()) self.scrollArea_2.setSizePolicy(sizePolicy) self.scrollArea_2.setFocusPolicy(Qt.WheelFocus) self.scrollArea_2.setFrameShape(QFrame.NoFrame) self.scrollArea_2.setFrameShadow(QFrame.Plain) self.scrollArea_2.setWidgetResizable(True) self.scrollAreaWidgetContents_2 = QWidget() self.verticalLayout = QVBoxLayout(self.scrollAreaWidgetContents_2) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.inputsTree = QTreeWidget(self.scrollAreaWidgetContents_2) self.inputsTree.setAlternatingRowColors(True) self.inputsTree.header().setVisible(False) self.verticalLayout.addWidget(self.inputsTree) self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2) self.verticalLayout_3.addWidget(self.scrollArea_2) self.inputsDock.setWidget(self.inputsDockContents) self.addDockWidget(Qt.DockWidgetArea(1), self.inputsDock) self.inputsDock.setWindowTitle(self.tr("Inputs")) self.algorithmsDock = QgsDockWidget(self) self.algorithmsDock.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.algorithmsDock.setObjectName("algorithmsDock") self.algorithmsDockContents = QWidget() self.verticalLayout_4 = QVBoxLayout(self.algorithmsDockContents) self.verticalLayout_4.setContentsMargins(0, 0, 0, 0) self.scrollArea_3 = QgsScrollArea(self.algorithmsDockContents) sizePolicy.setHeightForWidth( self.scrollArea_3.sizePolicy().hasHeightForWidth()) self.scrollArea_3.setSizePolicy(sizePolicy) self.scrollArea_3.setFocusPolicy(Qt.WheelFocus) self.scrollArea_3.setFrameShape(QFrame.NoFrame) self.scrollArea_3.setFrameShadow(QFrame.Plain) self.scrollArea_3.setWidgetResizable(True) self.scrollAreaWidgetContents_3 = QWidget() self.verticalLayout_2 = QVBoxLayout(self.scrollAreaWidgetContents_3) self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(4) self.searchBox = QgsFilterLineEdit(self.scrollAreaWidgetContents_3) self.verticalLayout_2.addWidget(self.searchBox) self.algorithmTree = QgsProcessingToolboxTreeView( None, QgsApplication.processingRegistry()) self.algorithmTree.setAlternatingRowColors(True) self.algorithmTree.header().setVisible(False) self.verticalLayout_2.addWidget(self.algorithmTree) self.scrollArea_3.setWidget(self.scrollAreaWidgetContents_3) self.verticalLayout_4.addWidget(self.scrollArea_3) self.algorithmsDock.setWidget(self.algorithmsDockContents) self.addDockWidget(Qt.DockWidgetArea(1), self.algorithmsDock) self.algorithmsDock.setWindowTitle(self.tr("Algorithms")) self.searchBox.setToolTip( self.tr("Enter algorithm name to filter list")) self.searchBox.setShowSearchIcon(True) self.variables_dock = QgsDockWidget(self) self.variables_dock.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.variables_dock.setObjectName("variablesDock") self.variables_dock_contents = QWidget() vl_v = QVBoxLayout() vl_v.setContentsMargins(0, 0, 0, 0) self.variables_editor = QgsVariableEditorWidget() vl_v.addWidget(self.variables_editor) self.variables_dock_contents.setLayout(vl_v) self.variables_dock.setWidget(self.variables_dock_contents) self.addDockWidget(Qt.DockWidgetArea(1), self.variables_dock) self.variables_dock.setWindowTitle(self.tr("Variables")) self.addDockWidget(Qt.DockWidgetArea(1), self.propertiesDock) self.tabifyDockWidget(self.propertiesDock, self.variables_dock) self.variables_editor.scopeChanged.connect(self.variables_changed) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.centralWidget().layout().insertWidget(0, self.bar) try: self.setDockOptions(self.dockOptions() | QMainWindow.GroupedDragging) except: pass if iface is not None: self.mToolbar.setIconSize(iface.iconSize()) self.setStyleSheet(iface.mainWindow().styleSheet()) self.toolbutton_export_to_script = QToolButton() self.toolbutton_export_to_script.setPopupMode(QToolButton.InstantPopup) self.export_to_script_algorithm_action = QAction( QCoreApplication.translate('ModelerDialog', 'Export as Script Algorithm…')) self.toolbutton_export_to_script.addActions( [self.export_to_script_algorithm_action]) self.mToolbar.insertWidget(self.mActionExportImage, self.toolbutton_export_to_script) self.export_to_script_algorithm_action.triggered.connect( self.export_as_script_algorithm) self.mActionOpen.setIcon( QgsApplication.getThemeIcon('/mActionFileOpen.svg')) self.mActionSave.setIcon( QgsApplication.getThemeIcon('/mActionFileSave.svg')) self.mActionSaveAs.setIcon( QgsApplication.getThemeIcon('/mActionFileSaveAs.svg')) self.mActionSaveInProject.setIcon( QgsApplication.getThemeIcon('/mAddToProject.svg')) self.mActionZoomActual.setIcon( QgsApplication.getThemeIcon('/mActionZoomActual.svg')) self.mActionZoomIn.setIcon( QgsApplication.getThemeIcon('/mActionZoomIn.svg')) self.mActionZoomOut.setIcon( QgsApplication.getThemeIcon('/mActionZoomOut.svg')) self.mActionExportImage.setIcon( QgsApplication.getThemeIcon('/mActionSaveMapAsImage.svg')) self.mActionZoomToItems.setIcon( QgsApplication.getThemeIcon('/mActionZoomFullExtent.svg')) self.mActionExportPdf.setIcon( QgsApplication.getThemeIcon('/mActionSaveAsPDF.svg')) self.mActionExportSvg.setIcon( QgsApplication.getThemeIcon('/mActionSaveAsSVG.svg')) self.toolbutton_export_to_script.setIcon( QgsApplication.getThemeIcon('/mActionSaveAsPython.svg')) self.mActionEditHelp.setIcon( QgsApplication.getThemeIcon('/mActionEditHelpContent.svg')) self.mActionRun.setIcon( QgsApplication.getThemeIcon('/mActionStart.svg')) self.addDockWidget(Qt.LeftDockWidgetArea, self.propertiesDock) self.addDockWidget(Qt.LeftDockWidgetArea, self.inputsDock) self.addDockWidget(Qt.LeftDockWidgetArea, self.algorithmsDock) self.tabifyDockWidget(self.inputsDock, self.algorithmsDock) self.inputsDock.raise_() self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint) settings = QgsSettings() self.restoreState( settings.value("/Processing/stateModeler", QByteArray())) self.restoreGeometry( settings.value("/Processing/geometryModeler", QByteArray())) self.scene = ModelerScene(self, dialog=self) self.scene.setSceneRect( QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE)) self.view.setScene(self.scene) self.view.setAcceptDrops(True) self.view.ensureVisible(0, 0, 10, 10) self.view.scale(QgsApplication.desktop().logicalDpiX() / 96, QgsApplication.desktop().logicalDpiX() / 96) def _dragEnterEvent(event): if event.mimeData().hasText() or event.mimeData().hasFormat( 'application/x-vnd.qgis.qgis.algorithmid'): event.acceptProposedAction() else: event.ignore() def _dropEvent(event): def alg_dropped(algorithm_id, pos): alg = QgsApplication.processingRegistry().createAlgorithmById( algorithm_id) if alg is not None: self._addAlgorithm(alg, pos) else: assert False, algorithm_id def input_dropped(id, pos): if id in [ param.id() for param in QgsApplication.instance(). processingRegistry().parameterTypes() ]: self.addInputOfType(itemId, pos) if event.mimeData().hasFormat( 'application/x-vnd.qgis.qgis.algorithmid'): data = event.mimeData().data( 'application/x-vnd.qgis.qgis.algorithmid') stream = QDataStream(data, QIODevice.ReadOnly) algorithm_id = stream.readQString() QTimer.singleShot( 0, lambda id=algorithm_id, pos=self.view.mapToScene(event.pos( )): alg_dropped(id, pos)) event.accept() elif event.mimeData().hasText(): itemId = event.mimeData().text() QTimer.singleShot(0, lambda id=itemId, pos=self.view.mapToScene( event.pos()): input_dropped(id, pos)) event.accept() else: event.ignore() def _dragMoveEvent(event): if event.mimeData().hasText() or event.mimeData().hasFormat( 'application/x-vnd.qgis.qgis.algorithmid'): event.accept() else: event.ignore() def _wheelEvent(event): self.view.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) settings = QgsSettings() factor = settings.value('/qgis/zoom_favor', 2.0) # "Normal" mouse has an angle delta of 120, precision mouses provide data # faster, in smaller steps factor = 1.0 + (factor - 1.0) / 120.0 * abs(event.angleDelta().y()) if (event.modifiers() == Qt.ControlModifier): factor = 1.0 + (factor - 1.0) / 20.0 if event.angleDelta().y() < 0: factor = 1 / factor self.view.scale(factor, factor) def _enterEvent(e): QGraphicsView.enterEvent(self.view, e) self.view.viewport().setCursor(Qt.ArrowCursor) def _mouseReleaseEvent(e): QGraphicsView.mouseReleaseEvent(self.view, e) self.view.viewport().setCursor(Qt.ArrowCursor) def _mousePressEvent(e): if e.button() == Qt.MidButton: self.previousMousePos = e.pos() else: QGraphicsView.mousePressEvent(self.view, e) def _mouseMoveEvent(e): if e.buttons() == Qt.MidButton: offset = self.previousMousePos - e.pos() self.previousMousePos = e.pos() self.view.verticalScrollBar().setValue( self.view.verticalScrollBar().value() + offset.y()) self.view.horizontalScrollBar().setValue( self.view.horizontalScrollBar().value() + offset.x()) else: QGraphicsView.mouseMoveEvent(self.view, e) self.view.setDragMode(QGraphicsView.ScrollHandDrag) self.view.dragEnterEvent = _dragEnterEvent self.view.dropEvent = _dropEvent self.view.dragMoveEvent = _dragMoveEvent self.view.wheelEvent = _wheelEvent self.view.enterEvent = _enterEvent self.view.mousePressEvent = _mousePressEvent self.view.mouseMoveEvent = _mouseMoveEvent def _mimeDataInput(items): mimeData = QMimeData() text = items[0].data(0, Qt.UserRole) mimeData.setText(text) return mimeData self.inputsTree.mimeData = _mimeDataInput self.inputsTree.setDragDropMode(QTreeWidget.DragOnly) self.inputsTree.setDropIndicatorShown(True) self.algorithms_model = ModelerToolboxModel( self, QgsApplication.processingRegistry()) self.algorithmTree.setToolboxProxyModel(self.algorithms_model) self.algorithmTree.setDragDropMode(QTreeWidget.DragOnly) self.algorithmTree.setDropIndicatorShown(True) filters = QgsProcessingToolboxProxyModel.Filters( QgsProcessingToolboxProxyModel.FilterModeler) if ProcessingConfig.getSetting( ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES): filters |= QgsProcessingToolboxProxyModel.FilterShowKnownIssues self.algorithmTree.setFilters(filters) if hasattr(self.searchBox, 'setPlaceholderText'): self.searchBox.setPlaceholderText( QCoreApplication.translate('ModelerDialog', 'Search…')) if hasattr(self.textName, 'setPlaceholderText'): self.textName.setPlaceholderText(self.tr('Enter model name here')) if hasattr(self.textGroup, 'setPlaceholderText'): self.textGroup.setPlaceholderText(self.tr('Enter group name here')) # Connect signals and slots self.inputsTree.doubleClicked.connect(self.addInput) self.searchBox.textChanged.connect(self.algorithmTree.setFilterString) self.algorithmTree.doubleClicked.connect(self.addAlgorithm) # Ctrl+= should also trigger a zoom in action ctrlEquals = QShortcut(QKeySequence("Ctrl+="), self) ctrlEquals.activated.connect(self.zoomIn) self.mActionOpen.triggered.connect(self.openModel) self.mActionSave.triggered.connect(self.save) self.mActionSaveAs.triggered.connect(self.saveAs) self.mActionSaveInProject.triggered.connect(self.saveInProject) self.mActionZoomIn.triggered.connect(self.zoomIn) self.mActionZoomOut.triggered.connect(self.zoomOut) self.mActionZoomActual.triggered.connect(self.zoomActual) self.mActionZoomToItems.triggered.connect(self.zoomToItems) self.mActionExportImage.triggered.connect(self.exportAsImage) self.mActionExportPdf.triggered.connect(self.exportAsPdf) self.mActionExportSvg.triggered.connect(self.exportAsSvg) #self.mActionExportPython.triggered.connect(self.exportAsPython) self.mActionEditHelp.triggered.connect(self.editHelp) self.mActionRun.triggered.connect(self.runModel) if model is not None: self.model = model.create() self.model.setSourceFilePath(model.sourceFilePath()) self.textGroup.setText(self.model.group()) self.textName.setText(self.model.displayName()) self.repaintModel() else: self.model = QgsProcessingModelAlgorithm() self.model.setProvider( QgsApplication.processingRegistry().providerById('model')) self.update_variables_gui() self.fillInputsTree() self.view.centerOn(0, 0) self.help = None self.hasChanged = False def closeEvent(self, evt): settings = QgsSettings() settings.setValue("/Processing/stateModeler", self.saveState()) settings.setValue("/Processing/geometryModeler", self.saveGeometry()) if self.hasChanged: ret = QMessageBox.question( self, self.tr('Save Model?'), self. tr('There are unsaved changes in this model. Do you want to keep those?' ), QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard, QMessageBox.Cancel) if ret == QMessageBox.Save: self.saveModel(False) evt.accept() elif ret == QMessageBox.Discard: evt.accept() else: evt.ignore() else: evt.accept() def editHelp(self): alg = self.model dlg = HelpEditionDialog(alg) dlg.exec_() if dlg.descriptions: self.model.setHelpContent(dlg.descriptions) self.hasChanged = True def update_variables_gui(self): variables_scope = QgsExpressionContextScope(self.tr('Model Variables')) for k, v in self.model.variables().items(): variables_scope.setVariable(k, v) variables_context = QgsExpressionContext() variables_context.appendScope(variables_scope) self.variables_editor.setContext(variables_context) self.variables_editor.setEditableScopeIndex(0) def variables_changed(self): self.model.setVariables(self.variables_editor.variablesInActiveScope()) def runModel(self): if len(self.model.childAlgorithms()) == 0: self.bar.pushMessage( "", self. tr("Model doesn't contain any algorithm and/or parameter and can't be executed" ), level=Qgis.Warning, duration=5) return dlg = AlgorithmDialog(self.model.create(), parent=iface.mainWindow()) dlg.exec_() def save(self): self.saveModel(False) def saveAs(self): self.saveModel(True) def saveInProject(self): if not self.can_save(): return self.model.setName(str(self.textName.text())) self.model.setGroup(str(self.textGroup.text())) self.model.setSourceFilePath(None) project_provider = QgsApplication.processingRegistry().providerById( PROJECT_PROVIDER_ID) project_provider.add_model(self.model) self.update_model.emit() self.bar.pushMessage("", self.tr("Model was saved inside current project"), level=Qgis.Success, duration=5) self.hasChanged = False QgsProject.instance().setDirty(True) def zoomIn(self): self.view.setTransformationAnchor(QGraphicsView.NoAnchor) point = self.view.mapToScene( QPoint(self.view.viewport().width() / 2, self.view.viewport().height() / 2)) settings = QgsSettings() factor = settings.value('/qgis/zoom_favor', 2.0) self.view.scale(factor, factor) self.view.centerOn(point) self.repaintModel() def zoomOut(self): self.view.setTransformationAnchor(QGraphicsView.NoAnchor) point = self.view.mapToScene( QPoint(self.view.viewport().width() / 2, self.view.viewport().height() / 2)) settings = QgsSettings() factor = settings.value('/qgis/zoom_favor', 2.0) factor = 1 / factor self.view.scale(factor, factor) self.view.centerOn(point) self.repaintModel() def zoomActual(self): point = self.view.mapToScene( QPoint(self.view.viewport().width() / 2, self.view.viewport().height() / 2)) self.view.resetTransform() self.view.scale(QgsApplication.desktop().logicalDpiX() / 96, QgsApplication.desktop().logicalDpiX() / 96) self.view.centerOn(point) def zoomToItems(self): totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) self.view.fitInView(totalRect, Qt.KeepAspectRatio) def exportAsImage(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName( self, self.tr('Save Model As Image'), '', self.tr('PNG files (*.png *.PNG)')) if not filename: return if not filename.lower().endswith('.png'): filename += '.png' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) imgRect = QRectF(0, 0, totalRect.width(), totalRect.height()) img = QImage(totalRect.width(), totalRect.height(), QImage.Format_ARGB32_Premultiplied) img.fill(Qt.white) painter = QPainter() painter.setRenderHint(QPainter.Antialiasing) painter.begin(img) self.scene.render(painter, imgRect, totalRect) painter.end() img.save(filename) self.bar.pushMessage( "", self.tr( "Successfully exported model as image to <a href=\"{}\">{}</a>" ).format( QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) self.repaintModel(controls=True) def exportAsPdf(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName( self, self.tr('Save Model As PDF'), '', self.tr('PDF files (*.pdf *.PDF)')) if not filename: return if not filename.lower().endswith('.pdf'): filename += '.pdf' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) printerRect = QRectF(0, 0, totalRect.width(), totalRect.height()) printer = QPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(filename) printer.setPaperSize(QSizeF(printerRect.width(), printerRect.height()), QPrinter.DevicePixel) printer.setFullPage(True) painter = QPainter(printer) self.scene.render(painter, printerRect, totalRect) painter.end() self.bar.pushMessage( "", self.tr( "Successfully exported model as PDF to <a href=\"{}\">{}</a>"). format( QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) self.repaintModel(controls=True) def exportAsSvg(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName( self, self.tr('Save Model As SVG'), '', self.tr('SVG files (*.svg *.SVG)')) if not filename: return if not filename.lower().endswith('.svg'): filename += '.svg' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) svgRect = QRectF(0, 0, totalRect.width(), totalRect.height()) svg = QSvgGenerator() svg.setFileName(filename) svg.setSize(QSize(totalRect.width(), totalRect.height())) svg.setViewBox(svgRect) svg.setTitle(self.model.displayName()) painter = QPainter(svg) self.scene.render(painter, svgRect, totalRect) painter.end() self.bar.pushMessage( "", self.tr( "Successfully exported model as SVG to <a href=\"{}\">{}</a>"). format( QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) self.repaintModel(controls=True) def exportAsPython(self): filename, filter = QFileDialog.getSaveFileName( self, self.tr('Save Model As Python Script'), '', self.tr('Processing scripts (*.py *.PY)')) if not filename: return if not filename.lower().endswith('.py'): filename += '.py' text = self.model.asPythonCode() with codecs.open(filename, 'w', encoding='utf-8') as fout: fout.write(text) self.bar.pushMessage( "", self. tr("Successfully exported model as python script to <a href=\"{}\">{}</a>" ).format( QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) def can_save(self): """ Tests whether a model can be saved, or if it is not yet valid :return: bool """ if str(self.textName.text()).strip() == '': self.bar.pushWarning( "", self.tr('Please a enter model name before saving')) return False return True def saveModel(self, saveAs): if not self.can_save(): return self.model.setName(str(self.textName.text())) self.model.setGroup(str(self.textGroup.text())) if self.model.sourceFilePath() and not saveAs: filename = self.model.sourceFilePath() else: filename, filter = QFileDialog.getSaveFileName( self, self.tr('Save Model'), ModelerUtils.modelsFolders()[0], self.tr('Processing models (*.model3 *.MODEL3)')) if filename: if not filename.endswith('.model3'): filename += '.model3' self.model.setSourceFilePath(filename) if filename: if not self.model.toFile(filename): if saveAs: QMessageBox.warning( self, self.tr('I/O error'), self.tr('Unable to save edits. Reason:\n {0}').format( str(sys.exc_info()[1]))) else: QMessageBox.warning( self, self.tr("Can't save model"), QCoreApplication. translate('QgsPluginInstallerInstallingDialog', ( "This model can't be saved in its original location (probably you do not " "have permission to do it). Please, use the 'Save as…' option." ))) return self.update_model.emit() if saveAs: self.bar.pushMessage( "", self.tr( "Model was correctly saved to <a href=\"{}\">{}</a>"). format( QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) else: self.bar.pushMessage("", self.tr("Model was correctly saved"), level=Qgis.Success, duration=5) self.hasChanged = False def openModel(self): filename, selected_filter = QFileDialog.getOpenFileName( self, self.tr('Open Model'), ModelerUtils.modelsFolders()[0], self.tr('Processing models (*.model3 *.MODEL3)')) if filename: self.loadModel(filename) def loadModel(self, filename): alg = QgsProcessingModelAlgorithm() if alg.fromFile(filename): self.model = alg self.model.setProvider( QgsApplication.processingRegistry().providerById('model')) self.textGroup.setText(alg.group()) self.textName.setText(alg.name()) self.repaintModel() self.update_variables_gui() self.view.centerOn(0, 0) self.hasChanged = False else: QgsMessageLog.logMessage( self.tr('Could not load model {0}').format(filename), self.tr('Processing'), Qgis.Critical) QMessageBox.critical( self, self.tr('Open Model'), self.tr('The selected model could not be loaded.\n' 'See the log for more information.')) def repaintModel(self, controls=True): self.scene = ModelerScene(self, dialog=self) self.scene.setSceneRect( QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE)) self.scene.paintModel(self.model, controls) self.view.setScene(self.scene) def addInput(self): item = self.inputsTree.currentItem() param = item.data(0, Qt.UserRole) self.addInputOfType(param) def addInputOfType(self, paramType, pos=None): dlg = ModelerParameterDefinitionDialog(self.model, paramType) dlg.exec_() if dlg.param is not None: if pos is None: pos = self.getPositionForParameterItem() if isinstance(pos, QPoint): pos = QPointF(pos) component = QgsProcessingModelParameter(dlg.param.name()) component.setDescription(dlg.param.name()) component.setPosition(pos) self.model.addModelParameter(dlg.param, component) self.repaintModel() # self.view.ensureVisible(self.scene.getLastParameterItem()) self.hasChanged = True def getPositionForParameterItem(self): MARGIN = 20 BOX_WIDTH = 200 BOX_HEIGHT = 80 if len(self.model.parameterComponents()) > 0: maxX = max([ i.position().x() for i in list(self.model.parameterComponents().values()) ]) newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) else: newX = MARGIN + BOX_WIDTH / 2 return QPointF(newX, MARGIN + BOX_HEIGHT / 2) def fillInputsTree(self): icon = QIcon(os.path.join(pluginPath, 'images', 'input.svg')) parametersItem = QTreeWidgetItem() parametersItem.setText(0, self.tr('Parameters')) sortedParams = sorted( QgsApplication.instance().processingRegistry().parameterTypes(), key=lambda pt: pt.name()) for param in sortedParams: if param.flags() & QgsProcessingParameterType.ExposeToModeler: paramItem = QTreeWidgetItem() paramItem.setText(0, param.name()) paramItem.setData(0, Qt.UserRole, param.id()) paramItem.setIcon(0, icon) paramItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) paramItem.setToolTip(0, param.description()) parametersItem.addChild(paramItem) self.inputsTree.addTopLevelItem(parametersItem) parametersItem.setExpanded(True) def addAlgorithm(self): algorithm = self.algorithmTree.selectedAlgorithm() if algorithm is not None: alg = QgsApplication.processingRegistry().createAlgorithmById( algorithm.id()) self._addAlgorithm(alg) def _addAlgorithm(self, alg, pos=None): dlg = ModelerParametersDialog(alg, self.model) if dlg.exec_(): alg = dlg.createAlgorithm() if pos is None: alg.setPosition(self.getPositionForAlgorithmItem()) else: alg.setPosition(pos) from processing.modeler.ModelerGraphicItem import ModelerGraphicItem for i, out in enumerate(alg.modelOutputs()): alg.modelOutput(out).setPosition( alg.position() + QPointF(ModelerGraphicItem.BOX_WIDTH, (i + 1.5) * ModelerGraphicItem.BOX_HEIGHT)) self.model.addChildAlgorithm(alg) self.repaintModel() self.hasChanged = True def getPositionForAlgorithmItem(self): MARGIN = 20 BOX_WIDTH = 200 BOX_HEIGHT = 80 if self.model.childAlgorithms(): maxX = max([ alg.position().x() for alg in list(self.model.childAlgorithms().values()) ]) maxY = max([ alg.position().y() for alg in list(self.model.childAlgorithms().values()) ]) newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) newY = min(MARGIN + BOX_HEIGHT + maxY, self.CANVAS_SIZE - BOX_HEIGHT) else: newX = MARGIN + BOX_WIDTH / 2 newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2 return QPointF(newX, newY) def export_as_script_algorithm(self): dlg = ScriptEditorDialog(None) dlg.editor.setText('\n'.join( self.model.asPythonCode( QgsProcessing.PythonQgsProcessingAlgorithmSubclass, 4))) dlg.show()
class StatsWidget(QWidget, FORM_CLASS): signalAskCloseWindow = pyqtSignal(name='signalAskCloseWindow') def __init__(self, parent=None): self.parent = parent super(StatsWidget, self).__init__() self.setupUi(self) self.label_progressStats.setText('') # Connect # noinspection PyUnresolvedReferences self.pushButton_saveTable.clicked.connect(self.save_table) # noinspection PyUnresolvedReferences self.pushButton_saveYValues.clicked.connect(self.save_y_values) self.buttonBox_stats.button(QDialogButtonBox.Ok).clicked.connect( self.run_stats) self.buttonBox_stats.button(QDialogButtonBox.Cancel).clicked.connect( self.signalAskCloseWindow.emit) # a figure instance to plot on self.figure = Figure() self.canvas = FigureCanvas(self.figure) self.canvas.setMinimumSize(QSize(300, 0)) self.toolbar = CustomNavigationToolbar(self.canvas, self) self.layout_plot.addWidget(self.toolbar) self.layout_plot.addWidget(self.canvas) self.tab = [] self.comboBox_blurredLayer.setFilters( QgsMapLayerProxyModel.PolygonLayer) self.comboBox_statsLayer.setFilters( QgsMapLayerProxyModel.PolygonLayer) def run_stats(self): self.progressBar_stats.setValue(0) self.label_progressStats.setText('') # noinspection PyArgumentList QApplication.processEvents() blurred_layer = self.comboBox_blurredLayer.currentLayer() stats_layer = self.comboBox_statsLayer.currentLayer() try: if not blurred_layer or not stats_layer: raise NoLayerProvidedException crs_blurred_layer = blurred_layer.crs() crs_stats_layer = stats_layer.crs() if crs_blurred_layer != crs_stats_layer: raise DifferentCrsException( epsg1=crs_blurred_layer.authid(), epsg2=crs_stats_layer.authid()) if blurred_layer == stats_layer: raise NoLayerProvidedException if not blurred_layer or not stats_layer: raise NoLayerProvidedException nb_feature_stats = stats_layer.featureCount() nb_feature_blurred = blurred_layer.featureCount() features_stats = {} label_preparing = tr('Preparing index on the stats layer') label_creating = tr('Creating index on the stats layer') label_calculating = tr('Calculating') if Qgis.QGIS_VERSION_INT < 20700: self.label_progressStats.setText('%s 1/3' % label_preparing) for i, feature in enumerate(stats_layer.getFeatures()): features_stats[feature.id()] = feature percent = int((i + 1) * 100 / nb_feature_stats) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() self.label_progressStats.setText('%s 2/3' % label_creating) # noinspection PyArgumentList QApplication.processEvents() index = QgsSpatialIndex() for i, f in enumerate(stats_layer.getFeatures()): index.insertFeature(f) percent = int((i + 1) * 100 / nb_feature_stats) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() self.label_progressStats.setText('%s 3/3' % label_calculating) else: # If QGIS >= 2.7, we can speed up the spatial index. # From 1 min 15 to 7 seconds on my PC. self.label_progressStats.setText('%s 1/2' % label_creating) # noinspection PyArgumentList QApplication.processEvents() index = QgsSpatialIndex(stats_layer.getFeatures()) self.label_progressStats.setText('%s 2/2' % label_calculating) # noinspection PyArgumentList QApplication.processEvents() self.tab = [] for i, feature in enumerate(blurred_layer.getFeatures()): count = 0 ids = index.intersects(feature.geometry().boundingBox()) for unique_id in ids: request = QgsFeatureRequest().setFilterFid(unique_id) f = next(stats_layer.getFeatures(request)) if f.geometry().intersects(feature.geometry()): count += 1 self.tab.append(count) percent = int((i + 1) * 100 / nb_feature_blurred) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() stats = Stats(self.tab) items_stats = [ 'Count(blurred),%d' % nb_feature_blurred, 'Count(stats),%d' % nb_feature_stats, 'Min,%d' % stats.min(), 'Average,%f' % stats.average(), 'Max,%d' % stats.max(), 'Median,%f' % stats.median(), 'Range,%d' % stats.range(), 'Variance,%f' % stats.variance(), 'Standard deviation,%f' % stats.standard_deviation() ] self.tableWidget.clear() self.tableWidget.setColumnCount(2) labels = ['Parameters', 'Values'] self.tableWidget.setHorizontalHeaderLabels(labels) self.tableWidget.setRowCount(len(items_stats)) for i, item in enumerate(items_stats): s = item.split(',') self.tableWidget.setItem(i, 0, QTableWidgetItem(s[0])) self.tableWidget.setItem(i, 1, QTableWidgetItem(s[1])) self.tableWidget.resizeRowsToContents() self.draw_plot(self.tab) except GeoPublicHealthException as e: self.label_progressStats.setText('') display_message_bar(msg=e.msg, level=e.level, duration=e.duration) def save_table(self): if not self.tableWidget.rowCount(): return False csv_string = 'parameter,values\n' for i in range(self.tableWidget.rowCount()): item_param = self.tableWidget.item(i, 0) item_value = self.tableWidget.item(i, 1) csv_string += \ str(item_param.text()) + ',' + item_value.text() + '\n' last_directory = get_last_input_path() # noinspection PyArgumentList output_file, __ = QFileDialog.getSaveFileName( parent=self, caption=tr('Select file'), directory=last_directory, filter='CSV (*.csv)') if output_file: path = dirname(output_file) set_last_input_path(path) fh = open(output_file, 'w') fh.write(csv_string) fh.close() return True def save_y_values(self): if not self.tableWidget.rowCount(): return False csv_string = 'parameter,values\n' for value in self.tab: csv_string += str(value) + '\n' last_directory = get_last_input_path() # noinspection PyArgumentList output_file, __ = QFileDialog.getSaveFileName( parent=self, caption=tr('Select file'), directory=last_directory, filter='CSV (*.csv)') if output_file: path = dirname(output_file) set_last_input_path(path) fh = open(output_file, 'w') fh.write(csv_string) fh.close() return True def draw_plot(self, data): # Creating the plot # create an axis ax = self.figure.add_subplot(111) # discards the old graph # plot data ax.plot(data, '*-') # ax.set_title('Number of intersections per entity') ax.set_xlabel('Blurred entity') ax.set_ylabel('Number of intersections') ax.grid() # refresh canvas self.canvas.draw()
class BatchInputSelectionPanel(QWidget): valueChanged = pyqtSignal() def __init__(self, param, row, col, dialog): super(BatchInputSelectionPanel, self).__init__(None) self.param = param self.dialog = dialog self.row = row self.col = col self.horizontalLayout = QHBoxLayout(self) self.horizontalLayout.setSpacing(0) self.horizontalLayout.setMargin(0) self.text = QLineEdit() self.text.setObjectName('text') self.text.setMinimumWidth(300) self.setValue('') self.text.editingFinished.connect(self.textEditingFinished) self.text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.horizontalLayout.addWidget(self.text) self.pushButton = QPushButton() self.pushButton.setText('…') self.pushButton.clicked.connect(self.showPopupMenu) self.horizontalLayout.addWidget(self.pushButton) self.setLayout(self.horizontalLayout) def _panel(self): return self.dialog.mainWidget() def _table(self): return self._panel().tblParameters def showPopupMenu(self): popupmenu = QMenu() if not (isinstance(self.param, QgsProcessingParameterMultipleLayers) and self.param.layerType == dataobjects.TYPE_FILE): selectLayerAction = QAction( QCoreApplication.translate('BatchInputSelectionPanel', 'Select from Open Layers…'), self.pushButton) selectLayerAction.triggered.connect(self.showLayerSelectionDialog) popupmenu.addAction(selectLayerAction) selectFileAction = QAction( QCoreApplication.translate('BatchInputSelectionPanel', 'Select from File System…'), self.pushButton) selectFileAction.triggered.connect(self.showFileSelectionDialog) popupmenu.addAction(selectFileAction) popupmenu.exec_(QCursor.pos()) def showLayerSelectionDialog(self): layers = [] if (isinstance(self.param, QgsProcessingParameterRasterLayer) or (isinstance(self.param, QgsProcessingParameterMultipleLayers) and self.param.layerType() == QgsProcessing.TypeRaster)): layers = QgsProcessingUtils.compatibleRasterLayers( QgsProject.instance()) elif isinstance(self.param, QgsProcessingParameterVectorLayer): layers = QgsProcessingUtils.compatibleVectorLayers( QgsProject.instance()) else: datatypes = [QgsProcessing.TypeVectorAnyGeometry] if isinstance(self.param, QgsProcessingParameterFeatureSource): datatypes = self.param.dataTypes() elif isinstance(self.param, QgsProcessingParameterMultipleLayers): datatypes = [self.param.layerType()] if QgsProcessing.TypeVectorAnyGeometry not in datatypes: layers = QgsProcessingUtils.compatibleVectorLayers( QgsProject.instance(), datatypes) else: layers = QgsProcessingUtils.compatibleVectorLayers( QgsProject.instance()) dlg = MultipleInputDialog([layer.name() for layer in layers]) dlg.exec_() def generate_layer_id(layer): # prefer layer name if unique if len([ l for l in layers if l.name().lower() == layer.name().lower() ]) == 1: return layer.name() else: # otherwise fall back to layer id return layer.id() if dlg.selectedoptions is not None: selected = dlg.selectedoptions if len(selected) == 1: self.setValue(generate_layer_id(layers[selected[0]])) else: if isinstance(self.param, QgsProcessingParameterMultipleLayers): self.text.setText(';'.join(layers[idx].id() for idx in selected)) else: rowdif = len(selected) - (self._table().rowCount() - self.row) for i in range(rowdif): self._panel().addRow() for i, layeridx in enumerate(selected): self._table().cellWidget( i + self.row, self.col).setValue( generate_layer_id(layers[layeridx])) def showFileSelectionDialog(self): settings = QgsSettings() text = str(self.text.text()) if os.path.isdir(text): path = text elif os.path.isdir(os.path.dirname(text)): path = os.path.dirname(text) elif settings.contains('/Processing/LastInputPath'): path = str(settings.value('/Processing/LastInputPath')) else: path = '' ret, selected_filter = QFileDialog.getOpenFileNames( self, self.tr('Select Files'), path, getFileFilter(self.param)) if ret: files = list(ret) settings.setValue('/Processing/LastInputPath', os.path.dirname(str(files[0]))) for i, filename in enumerate(files): files[i] = dataobjects.getRasterSublayer(filename, self.param) if len(files) == 1: self.text.setText(files[0]) self.textEditingFinished() else: if isinstance(self.param, QgsProcessingParameterMultipleLayers): self.text.setText(';'.join(str(f) for f in files)) else: rowdif = len(files) - (self._table().rowCount() - self.row) for i in range(rowdif): self._panel().addRow() for i, f in enumerate(files): self._table().cellWidget(i + self.row, self.col).setValue(f) def textEditingFinished(self): self._value = self.text.text() self.valueChanged.emit() def value(self): return self._value def setValue(self, value): self._value = value if isinstance(value, QgsMapLayer): self.text.setText(value.name()) else: # should be basestring self.text.setText(value) self.valueChanged.emit()
class DBTree(QTreeView): selectedItemChanged = pyqtSignal(object) def __init__(self, mainWindow): QTreeView.__init__(self, mainWindow) self.mainWindow = mainWindow self.setModel(DBModel(self)) self.setHeaderHidden(True) self.setEditTriggers(QTreeView.EditKeyPressed | QTreeView.SelectedClicked) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDropIndicatorShown(True) self.doubleClicked.connect(self.addLayer) self.selectionModel().currentChanged.connect(self.currentItemChanged) self.expanded.connect(self.itemChanged) self.collapsed.connect(self.itemChanged) self.model().dataChanged.connect(self.modelDataChanged) self.model().notPopulated.connect(self.collapse) def refreshItem(self, item=None): if item is None: item = self.currentItem() if item is None: return self.model().refreshItem(item) def showSystemTables(self, show): pass def currentItem(self): indexes = self.selectedIndexes() if len(indexes) <= 0: return return self.model().getItem(indexes[0]) def currentDatabase(self): item = self.currentItem() if item is None: return if isinstance(item, (DBPlugin, Schema, Table)): return item.database() return None def currentSchema(self): item = self.currentItem() if item is None: return if isinstance(item, (Schema, Table)): return item.schema() return None def currentTable(self): item = self.currentItem() if isinstance(item, Table): return item return None def newConnection(self): index = self.currentIndex() if not index.isValid() or not isinstance(index.internalPointer(), PluginItem): return item = self.currentItem() self.mainWindow.invokeCallback(item.addConnectionActionSlot, index) def itemChanged(self, index): self.setCurrentIndex(index) self.selectedItemChanged.emit(self.currentItem()) def modelDataChanged(self, indexFrom, indexTo): self.itemChanged(indexTo) def currentItemChanged(self, current, previous): self.itemChanged(current) def contextMenuEvent(self, ev): index = self.indexAt(ev.pos()) if not index.isValid(): return if index != self.currentIndex(): self.itemChanged(index) item = self.currentItem() menu = QMenu(self) if isinstance(item, (Table, Schema)): menu.addAction(QCoreApplication.translate("DBTree", "Rename…"), self.rename) menu.addAction(QCoreApplication.translate("DBTree", "Delete…"), self.delete) if isinstance(item, Table) and item.canBeAddedToCanvas(): menu.addSeparator() menu.addAction(self.tr("Add to Canvas"), self.addLayer) item.addExtraContextMenuEntries(menu) elif isinstance(item, DBPlugin): if item.database() is not None: menu.addAction(self.tr("Re-connect"), self.reconnect) menu.addAction(self.tr("Remove"), self.delete) elif not index.parent().isValid() and item.typeName() in ("spatialite", "gpkg"): menu.addAction( QCoreApplication.translate("DBTree", "New Connection…"), self.newConnection) if not menu.isEmpty(): menu.exec_(ev.globalPos()) menu.deleteLater() def rename(self): item = self.currentItem() if isinstance(item, (Table, Schema)): self.edit(self.currentIndex()) def delete(self): item = self.currentItem() if isinstance(item, (Table, Schema)): self.mainWindow.invokeCallback(item.database().deleteActionSlot) elif isinstance(item, DBPlugin): self.mainWindow.invokeCallback(item.removeActionSlot) def addLayer(self): table = self.currentTable() if table is not None: layer = table.toMapLayer() layers = QgsProject.instance().addMapLayers([layer]) if len(layers) != 1: QgsMessageLog.logMessage( self.tr("%1 is an invalid layer - not loaded").replace( "%1", layer.publicSource())) msgLabel = QLabel( self. tr("%1 is an invalid layer and cannot be loaded. Please check the <a href=\"#messageLog\">message log</a> for further info." ).replace("%1", layer.publicSource()), self.mainWindow.infoBar) msgLabel.setWordWrap(True) msgLabel.linkActivated.connect( self.mainWindow.iface.mainWindow().findChild( QWidget, "MessageLog").show) msgLabel.linkActivated.connect( self.mainWindow.iface.mainWindow().raise_) self.mainWindow.infoBar.pushItem( QgsMessageBarItem(msgLabel, Qgis.Warning)) def reconnect(self): db = self.currentDatabase() if db is not None: self.mainWindow.invokeCallback(db.reconnectActionSlot)
class RectangleMapTool(QgsMapToolEmitPoint): rectangleCreated = pyqtSignal() deactivated = pyqtSignal() def __init__(self, canvas): self.canvas = canvas QgsMapToolEmitPoint.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rubberBand.setColor(QColor(255, 0, 0, 100)) self.rubberBand.setWidth(2) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False if self.rectangle() is not None: self.rectangleCreated.emit() def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) # True to update canvas self.rubberBand.addPoint(point4, True) self.rubberBand.show() def rectangle(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or \ self.startPoint.y() == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def setRectangle(self, rect): if rect == self.rectangle(): return False if rect is None: self.reset() else: self.startPoint = QgsPointXY(rect.xMaximum(), rect.yMaximum()) self.endPoint = QgsPointXY(rect.xMinimum(), rect.yMinimum()) self.showRect(self.startPoint, self.endPoint) return True def deactivate(self): QgsMapTool.deactivate(self) self.deactivated.emit()
class IliExecutable(QObject, metaclass=AbstractQObjectMeta): SUCCESS = 0 ERROR = 1000 ILI2DB_NOT_FOUND = 1001 stdout = pyqtSignal(str) stderr = pyqtSignal(str) process_started = pyqtSignal(str) process_finished = pyqtSignal(int, int) __done_pattern = re.compile(r"Info: \.\.\.([a-z]+ )?done") _result = None def __init__(self, parent=None): QObject.__init__(self, parent) self.filename = None self.tool = None self.configuration = self._create_config() _, self.encoding = locale.getlocale() # Lets python try to determine the default locale if not self.encoding: _, self.encoding = locale.getdefaultlocale() # This might be unset # (https://stackoverflow.com/questions/1629699/locale-getlocale-problems-on-osx) if not self.encoding: self.encoding = 'UTF8' @abstractmethod def _create_config(self) -> Ili2DbCommandConfiguration: """Creates the configuration that will be used by *run* method. :return: ili2db configuration""" pass def _get_ili2db_version(self): return self.configuration.db_ili_version def _args(self, hide_password): """Gets the list of ili2db arguments from configuration. :param bool hide_password: *True* to mask the password, *False* otherwise. :return: ili2db arguments list. :rtype: list """ self.configuration.tool = self.tool return get_ili2db_args(self.configuration, hide_password) def _ili2db_jar_arg(self): ili2db_bin = get_ili2db_bin(self.tool, self._get_ili2db_version(), self.stdout, self.stderr) if not ili2db_bin: return self.ILI2DB_NOT_FOUND return ["-jar", ili2db_bin] def _escaped_arg(self, argument): if '"' in argument: argument = argument.replace('"', '"""') if ' ' in argument: argument = '"' + argument + '"' return argument def command(self, hide_password): ili2db_jar_arg = self._ili2db_jar_arg() args = self._args(hide_password) java_path = self._escaped_arg( get_java_path(self.configuration.base_configuration)) command_args = ili2db_jar_arg + args valid_args = [] for command_arg in command_args: valid_args.append(self._escaped_arg(command_arg)) command = java_path + ' ' + ' '.join(valid_args) return command def command_with_password(self, edited_command): if '--dbpwd ******' in edited_command: args = self._args(False) i = args.index('--dbpwd') edited_command = edited_command.replace('--dbpwd ******', '--dbpwd ' + args[i + 1]) return edited_command def command_without_password(self, edited_command=None): if not edited_command: return self.command(True) regex = re.compile('--dbpwd [^ ]*') match = regex.match(edited_command) if match: edited_command = edited_command.replace(match.group(1), '--dbpwd ******') return edited_command def run(self, edited_command=None): proc = QProcess() proc.readyReadStandardError.connect( functools.partial(self.stderr_ready, proc=proc)) proc.readyReadStandardOutput.connect( functools.partial(self.stdout_ready, proc=proc)) if not edited_command: ili2db_jar_arg = self._ili2db_jar_arg() if ili2db_jar_arg == self.ILI2DB_NOT_FOUND: return self.ILI2DB_NOT_FOUND args = self._args(False) java_path = get_java_path(self.configuration.base_configuration) proc.start(java_path, ili2db_jar_arg + args) else: proc.start(self.command_with_password(edited_command)) if not proc.waitForStarted(): proc = None if not proc: raise JavaNotFoundError() self.process_started.emit( self.command_without_password(edited_command)) self._result = self.ERROR loop = QEventLoop() proc.finished.connect(loop.exit) loop.exec() self.process_finished.emit(proc.exitCode(), self._result) return self._result def _search_custom_pattern(self, text): pass def stderr_ready(self, proc): text = bytes(proc.readAllStandardError()).decode(self.encoding) if self.__done_pattern.search(text): self._result = self.SUCCESS self._search_custom_pattern(text) self.stderr.emit(text) def stdout_ready(self, proc): text = bytes(proc.readAllStandardOutput()).decode(self.encoding) self.stdout.emit(text)
class QualityErrorDBUtils(QObject): progress_changed = pyqtSignal(int) # Progress changed def __init__(self): QObject.__init__(self) def get_quality_error_connector(self, output_path, timestamp, load_layers=False): # TODO: should we support both new and existent gpkgs in this method? I guess so! self.progress_changed.emit(0) res, msg, output_path = QualityErrorDBUtils.get_quality_validation_output_path( output_path, timestamp) if not res: return False, msg, None db_file = os.path.join(output_path, "Reglas_de_Calidad_{}.gpkg".format(timestamp)) db = GPKGConnector(db_file) ili2db = Ili2DB() error_model = LADMColModelRegistry().model( LADMNames.QUALITY_ERROR_MODEL_KEY) configuration = ili2db.get_import_schema_configuration( db, [error_model.full_name()]) res, msg = ili2db.import_schema(db, configuration) self.progress_changed.emit(50) if res: for catalog_key, catalog_xtf_path in error_model.get_catalogs( ).items(): logger.info( __name__, "Importing catalog '{}' to quality error database...". format(catalog_key)) configuration = ili2db.get_import_data_configuration( db, catalog_xtf_path) res_xtf, msg_xtf = ili2db.import_data(db, configuration) if not res_xtf: logger.warning( __name__, "There was a problem importing catalog '{}'! Skipping..." .format(catalog_key)) else: return False, "There were errors creating the 'Errores Calidad' structure!", None self.progress_changed.emit(80) if getattr(db.names, "T_ID_F", None) is None or db.names.T_ID_F is None: db.test_connection() # Just to build the names object if load_layers: names = db.names layers = { names.ERR_QUALITY_ERROR_T: None, names.ERR_RULE_TYPE_T: None, names.ERR_ERROR_TYPE_T: None, names.ERR_POINT_T: None, names.ERR_LINE_T: None, names.ERR_POLYGON_T: None, names.ERR_METADATA_T: None, names.ERR_ERROR_STATE_D: None } app.core.get_layers( db, layers, load=True, group=QualityErrorDBUtils.get_quality_error_group(timestamp)) self.progress_changed.emit(100) return res, msg, None if not res else db @staticmethod def get_quality_validation_output_path(base_output_path, timestamp): res, msg = True, 'Success' if not os.path.exists(base_output_path): base_output_path = tempfile.gettempdir() logger.warning( __name__, QCoreApplication.translate( "QualityRuleEngine", "Output dir doesn't exist! Using now '{}'").format( base_output_path)) output_path = os.path.join(base_output_path, "Reglas_de_Calidad_{}".format(timestamp)) if not os.path.exists(output_path): try: os.makedirs(output_path) except PermissionError as e: res = False msg = QCoreApplication.translate( "QualityRuleEngine", "Output dir '{}' is read-only!").format(base_output_path) logger.critical(__name__, msg) output_path = None return res, msg, output_path @staticmethod def save_errors(db_qr, rule_code, error_code, error_data, target_layer, ili_name=None): """ Save error data in the quality error model by error_code, which means that you should call this function once per error type. Accepts both, spatial and non-spatial errors. :param db_qr: DB Connector :param rule_code: Rule code as specified in the external catalogs. :param error_code: Error code as specified in the external catalogs. :param error_data: Dict of lists: {'geometries': [geometries], 'data': [obj_uuids, rel_obj_uuids, values, details, state]} Note: this dict will always have 2 elements. Note 2: For geometry errors, this dict will always have the same number of elements in each of the two lists (and the element order matters!). Note 3: For geometryless errors, the 'geometries' value won't be even read. :param target_layer: Value of EnumQualityRuleType. :param ili_name: Interlis name of the class where the object uuids can be found. :return: Tuple (res: boolean, msg: string) """ if not hasattr(db_qr.names, "T_ID_F"): db_qr.test_connection() names = db_qr.names layers = { names.ERR_QUALITY_ERROR_T: None, names.ERR_RULE_TYPE_T: None, names.ERR_ERROR_TYPE_T: None } if target_layer == EnumQualityRuleType.POINT: layers[names.ERR_POINT_T] = None elif target_layer == EnumQualityRuleType.LINE: layers[names.ERR_LINE_T] = None elif target_layer == EnumQualityRuleType.POLYGON: layers[names.ERR_POLYGON_T] = None app.core.get_layers(db_qr, layers, load=True) if not layers: return False, "At least one layer was not found in the quality error db!" # We do now a soon check of rule code and error code to stop if cannot be found fids = LADMData.get_fids_from_key_values(layers[names.ERR_RULE_TYPE_T], names.ERR_RULE_TYPE_T_CODE_F, [rule_code]) if not fids: return False, "There was a problem saving quality error data. The rule '{}' cannot be found in the database. Is that rule in any registered catalog?".format( rule_code) rule_code_id = fids[0] fids = LADMData.get_fids_from_key_values( layers[names.ERR_ERROR_TYPE_T], names.ERR_ERROR_TYPE_T_CODE_F, [error_code]) if not fids: return False, "There was a problem saving quality error data. The error '{}' cannot be found in the database. Is that error in any registered catalog?".format( error_code) error_code_id = fids[0] qr_error_layer = layers[names.ERR_QUALITY_ERROR_T] # First we deal with geometries geom_layer = None idx_geometry_fk = None geom_t_ids = list() # To store fks (geom t_id) if target_layer == EnumQualityRuleType.POINT: geom_layer = layers[names.ERR_POINT_T] idx_geometry_fk = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_POINT_F) elif target_layer == EnumQualityRuleType.LINE: geom_layer = layers[names.ERR_LINE_T] idx_geometry_fk = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_LINE_F) elif target_layer == EnumQualityRuleType.POLYGON: geom_layer = layers[names.ERR_POLYGON_T] idx_geometry_fk = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_POLYGON_F) if geom_layer: idx_t_ili_tid_geom = geom_layer.fields().indexOf(names.T_ILI_TID_F) geometries = error_data['geometries'] features = list() for geometry in geometries: new_feature = QgsVectorLayerUtils().createFeature( geom_layer, geometry, {idx_t_ili_tid_geom: str(uuid.uuid4())}) features.append(new_feature) res_geom, out_features = geom_layer.dataProvider().addFeatures( features) if res_geom: geom_t_ids = [ out_feature[names.T_ID_F] for out_feature in out_features ] else: return False, "There was a problem saving error geometries for error code '{}'!".format( error_code) # Now we deal with the alphanumeric data idx_t_ili_tid = qr_error_layer.fields().indexOf(names.T_ILI_TID_F) idx_rule_type = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_RULE_TYPE_F) idx_error_type = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_ERROR_TYPE_F) idx_object_ids = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_OBJECT_IDS_F) idx_rel_object_ids = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_RELATED_OBJECT_IDS_F) idx_values = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_VALUES_F) idx_ili_name = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_ILI_NAME_F) idx_details = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_DETAILS_F) idx_error_state = qr_error_layer.fields().indexOf( names.ERR_QUALITY_ERROR_T_ERROR_STATE_F) features = list() for i, data in enumerate(error_data['data']): attr_map = { idx_t_ili_tid: str(uuid.uuid4()), idx_rule_type: rule_code_id, idx_error_type: error_code_id, idx_object_ids: data[0], idx_rel_object_ids: data[1], idx_values: data[2], idx_details: data[3], idx_ili_name: ili_name, idx_error_state: data[4] } if geom_layer: attr_map[idx_geometry_fk] = geom_t_ids[ i] # We take advantage of the preserved order here features.append(QgsVectorLayerUtils().createFeature( qr_error_layer, attributes=attr_map)) res_data, out_features = qr_error_layer.dataProvider().addFeatures( features) if not res_data: return False, "There was a problem saving error alphanumeric data for error code '{}'! Details from the provider: {}".format( error_code, qr_error_layer.dataProvider().lastError()) return True, "Success!" @staticmethod def save_metadata(db_qr, metadata): """ :param db_qr: DBConnector :param metadata: Dict with the following information: QR_METADATA_TOOL QR_METADATA_DATA_SOURCE QR_METADATA_TOLERANCE QR_METADATA_TIMESTAMP QR_METADATA_RULES QR_METADATA_OPTIONS QR_METADATA_PERSON :return: tuple (bool with the result, string with a descriptive message) """ if not hasattr(db_qr.names, "T_ID_F"): db_qr.test_connection() names = db_qr.names layers = {names.ERR_METADATA_T: None, names.ERR_RULE_TYPE_T: None} app.core.get_layers(db_qr, layers, load=True) if not layers: return False, "At least one layer was not found in the QR DB!" metadata_layer = layers[names.ERR_METADATA_T] idx_t_ili_tid = metadata_layer.fields().indexOf(names.T_ILI_TID_F) idx_date = metadata_layer.fields().indexOf( names.ERR_METADATA_T_VALIDATION_DATE_F) idx_data_source = metadata_layer.fields().indexOf( names.ERR_METADATA_T_DATA_SOURCE_F) idx_tool = metadata_layer.fields().indexOf(names.ERR_METADATA_T_TOOL_F) idx_person = metadata_layer.fields().indexOf( names.ERR_METADATA_T_PERSON_F) idx_tolerance = metadata_layer.fields().indexOf( names.ERR_METADATA_T_TOLERANCE_F) idx_rules = metadata_layer.fields().indexOf( names.ERR_METADATA_T_RULES_F) idx_options = metadata_layer.fields().indexOf( names.ERR_METADATA_T_RULE_OPTIONS_F) # Initially, the metadata table had references to QR types, but as soon as we # wanted to save them as ARRAYs in GPKG or PG, ili2db said "no, I cannot handle that." # See https://github.com/claeis/ili2db/issues/438 #dict_rules = {f[names.ERR_ERROR_TYPE_T_CODE_F]:f[names.T_ID_F] for f in layers[names.ERR_RULE_TYPE_T].getFeatures()} #list_rules = str([dict_rules[qr_key] for qr_key in metadata[QR_METADATA_RULES] if qr_key in dict_rules]) time_structure = time.strptime(metadata[QR_METADATA_TIMESTAMP], '%Y%m%d_%H%M%S') iso_timestamp = time.strftime('%Y-%m-%dT%H:%M:%S', time_structure) attr_map = { idx_t_ili_tid: str(uuid.uuid4()), idx_date: QDateTime().fromString(iso_timestamp, Qt.ISODate), idx_data_source: metadata[QR_METADATA_DATA_SOURCE], idx_tool: metadata[QR_METADATA_TOOL], idx_person: metadata[QR_METADATA_PERSON], idx_tolerance: metadata[QR_METADATA_TOLERANCE], idx_rules: "{}: {}".format(len(metadata[QR_METADATA_RULES]), str(metadata[QR_METADATA_RULES])), idx_options: str(metadata[QR_METADATA_OPTIONS]) } metadata_layer.dataProvider().addFeature( QgsVectorLayerUtils().createFeature(metadata_layer, attributes=attr_map)) logger.info(__name__, "Quality rules metadata successfully saved!") return True, "Success!" @staticmethod def get_quality_error_group(timestamp, create_if_non_existent=True): root = QgsProject.instance().layerTreeRoot() prefix = TranslatableConfigStrings.get_translatable_config_strings( )[ERROR_LAYER_GROUP_PREFIX] group_name = "{} {}".format(prefix, timestamp) group = root.findGroup(group_name) if group is None and create_if_non_existent: group = root.insertGroup(0, group_name) ilg = qgis.utils.plugins['InvisibleLayersAndGroups'] ilg.hideGroup(group) group.setExpanded(False) return group @staticmethod def get_quality_error_group_names(): root = QgsProject.instance().layerTreeRoot() prefix = TranslatableConfigStrings.get_translatable_config_strings( )[ERROR_LAYER_GROUP_PREFIX] error_group_names = list() for group in root.findGroups(False): if group.name().startswith(prefix): error_group_names.append(group.name()) return error_group_names @staticmethod def remove_quality_error_group(timestamp): group = QualityErrorDBUtils.get_quality_error_group(timestamp, False) if group: parent = group.parent() parent.removeChildNode(group)
class UpdateWorker(QObject): projectUpdateStatus = pyqtSignal(str, str) projectInstalled = pyqtSignal(str) def __init__(self, basefolder): super(UpdateWorker, self).__init__() self.server = None self.basefolder = basefolder self.projectUpdateStatus.connect(self.status_updated) def check_url_found(self, url): url = quote_url(url) try: result = urlopen(url) return result.code == 200 except urllib.error.HTTPError as ex: return False def fetch_data(self, rootfolder, filename, serverurl): """ Download the update zip file for the project from the server """ serverurl = add_slash(serverurl) tempfolder = os.path.join(rootfolder, "_updates") if not os.path.exists(tempfolder): os.mkdir(tempfolder) filename = "{}.zip".format(filename) url = urlparse.urljoin(serverurl, "projects/{}".format(filename)) zippath = os.path.join(tempfolder, filename) if not self.check_url_found(url): yield "Skipping data download" yield "Done" return roam.utils.info("Downloading data zip from {}".format(url)) try: for status in download_file(url, zippath): yield status except UpdateExpection as ex: roam.utils.exception("Error in update for project") yield "Error in downloading data" return yield "Extracting data.." with zipfile.ZipFile(zippath, "r") as z: members = z.infolist() for i, member in enumerate(members): z.extract(member, rootfolder) roam.utils.debug("Extracting: {}".format(member.filename)) yield "Done" def status_updated(self, project, status): roam.utils.debug(project + ": " + status) def run(self): while True: project, version, server, is_new = forupdate.get() datadate = project["data_date"] datafolder = os.path.join(self.basefolder, "_data") if datadate != roam.config.settings.get("data_date", None) or not os.path.exists(datafolder): self.projectUpdateStatus.emit("Data", "Downloading Data..") for status in self.fetch_data(self.basefolder, "_data", server): self.projectUpdateStatus.emit("Data", status) self.projectUpdateStatus.emit("Data", "Installed") roam.config.settings["data_date"] = datadate roam.config.save() else: roam.utils.info("Data download skipped. Already up to date.") if is_new: name = project['name'] self.projectUpdateStatus.emit(name, "Installing") for status in install_project(project, self.basefolder, server): self.projectUpdateStatus.emit(name, status) self.projectUpdateStatus.emit(name, "Installed") self.projectInstalled.emit(name) else: name = project['name'] self.projectUpdateStatus.emit(name, "Updating") for status in install_project(project, self.basefolder, server, updateMode=True): self.projectUpdateStatus.emit(name, status) self.projectUpdateStatus.emit(name, "Complete")
class ExtentSelectorDialog(QDialog, FORM_CLASS): """Dialog for letting user determine analysis extents.""" extent_defined = pyqtSignal(QgsRectangle, QgsCoordinateReferenceSystem) clear_extent = pyqtSignal() extent_selector_closed = pyqtSignal() def __init__(self, iface, parent=None, extent=None, crs=None): """Constructor for the dialog. :param iface: A Quantum GIS QgisAppInterface instance. :type iface: QgisAppInterface :param parent: Parent widget of this dialog. :type parent: QWidget :param extent: Extent of the user's preferred analysis area. :type extent: QgsRectangle :param crs: CRS for user defined analysis extent. :type crs: QgsCoordinateReferenceSystem """ QDialog.__init__(self, parent) self.setupUi(self) icon = resources_path('img', 'icons', 'set-extents-tool.svg') self.setWindowIcon(QIcon(icon)) self.iface = iface self.parent = parent self.canvas = iface.mapCanvas() self.previous_map_tool = None # Prepare the map tool self.tool = RectangleMapTool(self.canvas) self.previous_map_tool = self.canvas.mapTool() if extent is None: # Use the current map canvas extents as a starting point self.tool.set_rectangle(self.canvas.extent()) else: if isinstance(extent, QgsGeometry): # In InaSAFE V4, the extent is a QgsGeometry. # This like a hack to transform a geometry to a rectangle. extent = wkt_to_rectangle(extent.asWkt()) # Ensure supplied extent is in current canvas crs transform = QgsCoordinateTransform( crs, self.canvas.mapSettings().destinationCrs(), QgsProject.instance()) transformed_extent = transform.transformBoundingBox(extent) self.tool.set_rectangle(transformed_extent) self._populate_coordinates() # Observe inputs for changes self.x_minimum.valueChanged.connect(self._coordinates_changed) self.y_minimum.valueChanged.connect(self._coordinates_changed) self.x_maximum.valueChanged.connect(self._coordinates_changed) self.y_maximum.valueChanged.connect(self._coordinates_changed) # Draw the rubberband self._coordinates_changed() # Wire up button events self.capture_button.clicked.connect(self.start_capture) # Handle cancel cancel_button = self.button_box.button( QtWidgets.QDialogButtonBox.Cancel) cancel_button.clicked.connect(self.reject) # Make sure to reshow this dialog when rectangle is captured self.tool.rectangle_created.connect(self.stop_capture) # Setup ok button self.ok_button = self.button_box.button(QtWidgets.QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up context help self.help_button = self.button_box.button( QtWidgets.QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # Reset / Clear button clear_button = self.button_box.button(QtWidgets.QDialogButtonBox.Reset) clear_button.setText(self.tr('Clear')) clear_button.clicked.connect(self.clear) # Populate the bookmarks list and connect the combobox self._populate_bookmarks_list() self.bookmarks_list.currentIndexChanged.connect( self.bookmarks_index_changed) # Reinstate the last used radio button mode = setting('analysis_extents_mode', HAZARD_EXPOSURE_VIEW) if mode == HAZARD_EXPOSURE_VIEW: self.hazard_exposure_view_extent.setChecked(True) elif mode == EXPOSURE: self.exposure_only.setChecked(True) elif mode == HAZARD_EXPOSURE: self.hazard_exposure_only.setChecked(True) elif mode == HAZARD_EXPOSURE_BOOKMARK: self.hazard_exposure_bookmark.setChecked(True) elif mode == HAZARD_EXPOSURE_BOUNDINGBOX: self.hazard_exposure_user_extent.setChecked(True) self.show_warnings.setChecked( setting('show_extent_warnings', True, bool)) self.show_confirmations.setChecked( setting('show_extent_confirmations', True, bool)) @pyqtSlot(bool) # prevents actions being handled twice def help_toggled(self, flag): """Show or hide the help tab in the main stacked widget. .. versionadded: 3.2.1 :param flag: Flag indicating whether help should be shown or hidden. :type flag: bool """ if flag: self.help_button.setText(self.tr('Hide Help')) self.show_help() else: self.help_button.setText(self.tr('Show Help')) self.hide_help() def hide_help(self): """Hide the usage info from the user. .. versionadded:: 3.2.1 """ self.main_stacked_widget.setCurrentIndex(1) def show_help(self): """Show usage info to the user.""" # Read the header and footer html snippets self.main_stacked_widget.setCurrentIndex(0) header = html_header() footer = html_footer() string = header message = extent_selector_help() string += message.to_html() string += footer self.help_web_view.setHtml(string) def start_capture(self): """Start capturing the rectangle.""" previous_map_tool = self.canvas.mapTool() if previous_map_tool != self.tool: self.previous_map_tool = previous_map_tool self.canvas.setMapTool(self.tool) self.hide() def stop_capture(self): """Stop capturing the rectangle and reshow the dialog.""" self._populate_coordinates() self.canvas.setMapTool(self.previous_map_tool) self.show() def clear(self): """Clear the currently set extent.""" self.tool.reset() self._populate_coordinates() # Revert to using hazard, exposure and view as basis for analysis self.hazard_exposure_view_extent.setChecked(True) def reject(self): """User rejected the rectangle.""" self.canvas.unsetMapTool(self.tool) if self.previous_map_tool != self.tool: self.canvas.setMapTool(self.previous_map_tool) self.tool.reset() self.extent_selector_closed.emit() super(ExtentSelectorDialog, self).reject() def accept(self): """User accepted the rectangle.""" mode = None if self.hazard_exposure_view_extent.isChecked(): mode = HAZARD_EXPOSURE_VIEW elif self.exposure_only.isChecked(): mode = EXPOSURE elif self.hazard_exposure_only.isChecked(): mode = HAZARD_EXPOSURE elif self.hazard_exposure_bookmark.isChecked(): mode = HAZARD_EXPOSURE_BOOKMARK elif self.hazard_exposure_user_extent.isChecked(): mode = HAZARD_EXPOSURE_BOUNDINGBOX set_setting('analysis_extents_mode', mode) self.canvas.unsetMapTool(self.tool) if self.previous_map_tool != self.tool: self.canvas.setMapTool(self.previous_map_tool) if not self.tool.rectangle().isEmpty(): self.extent_defined.emit( self.tool.rectangle(), self.canvas.mapSettings().destinationCrs()) extent = QgsGeometry.fromRect(self.tool.rectangle()) LOGGER.info('Requested extent : {wkt}'.format(wkt=extent.asWkt())) else: self.clear_extent.emit() # State handlers for showing warning message bars set_setting('show_extent_warnings', self.show_warnings.isChecked()) set_setting('show_extent_confirmations', self.show_confirmations.isChecked()) self.tool.reset() self.extent_selector_closed.emit() super(ExtentSelectorDialog, self).accept() def _are_coordinates_valid(self): """Check if the coordinates are valid. :return: True if coordinates are valid otherwise False. :type: bool """ try: QgsPointXY(self.x_minimum.value(), self.y_maximum.value()) QgsPointXY(self.x_maximum.value(), self.y_minimum.value()) except ValueError: return False return True def _coordinates_changed(self): """Handle a change in the coordinate input boxes.""" if self._are_coordinates_valid(): point1 = QgsPointXY(self.x_minimum.value(), self.y_maximum.value()) point2 = QgsPointXY(self.x_maximum.value(), self.y_minimum.value()) rect = QgsRectangle(point1, point2) self.tool.set_rectangle(rect) def _populate_coordinates(self): """Update the UI with the current active coordinates.""" rect = self.tool.rectangle() self.blockSignals(True) if not rect.isEmpty(): self.x_minimum.setValue(rect.xMinimum()) self.y_minimum.setValue(rect.yMinimum()) self.x_maximum.setValue(rect.xMaximum()) self.y_maximum.setValue(rect.yMaximum()) else: self.x_minimum.clear() self.y_minimum.clear() self.x_maximum.clear() self.y_maximum.clear() self.blockSignals(False) def bookmarks_index_changed(self): """Update the UI when the bookmarks combobox has changed.""" index = self.bookmarks_list.currentIndex() if index >= 0: self.tool.reset() rectangle = self.bookmarks_list.itemData(index) self.tool.set_rectangle(rectangle) self.canvas.setExtent(rectangle) self.ok_button.setEnabled(True) else: self.ok_button.setDisabled(True) def on_hazard_exposure_view_extent_toggled(self, enabled): """Handler for hazard/exposure/view radiobutton toggle. :param enabled: The status of the radiobutton. :type enabled: bool """ if enabled: self.tool.reset() self._populate_coordinates() def on_hazard_exposure_only_toggled(self, enabled): """Handler for hazard/exposure radiobutton toggle. :param enabled: The status of the radiobutton. :type enabled: bool """ if enabled: self.tool.reset() self._populate_coordinates() def on_hazard_exposure_bookmark_toggled(self, enabled): """Update the UI when the user toggles the bookmarks radiobutton. :param enabled: The status of the radiobutton. :type enabled: bool """ if enabled: self.bookmarks_index_changed() else: self.ok_button.setEnabled(True) self._populate_coordinates() def _populate_bookmarks_list(self): """Read the sqlite database and populate the bookmarks list. If no bookmarks are found, the bookmarks radio button will be disabled and the label will be shown indicating that the user should add bookmarks in QGIS first. Every bookmark are reprojected to mapcanvas crs. """ # Connect to the QGIS sqlite database and check if the table exists. # noinspection PyArgumentList db_file_path = QgsApplication.qgisUserDatabaseFilePath() db = sqlite3.connect(db_file_path) cursor = db.cursor() cursor.execute('SELECT COUNT(*) ' 'FROM sqlite_master ' 'WHERE type=\'table\' ' 'AND name=\'tbl_bookmarks\';') number_of_rows = cursor.fetchone()[0] if number_of_rows > 0: cursor.execute('SELECT * ' 'FROM tbl_bookmarks;') bookmarks = cursor.fetchall() canvas_crs = self.canvas.mapSettings().destinationCrs() for bookmark in bookmarks: name = bookmark[1] srid = bookmark[7] rectangle = QgsRectangle(bookmark[3], bookmark[4], bookmark[5], bookmark[6]) if srid != canvas_crs.srsid(): transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem(srid), canvas_crs, QgsProject.instance()) try: rectangle = transform.transform(rectangle) except QgsCsException: rectangle = QgsRectangle() if rectangle.isEmpty(): pass self.bookmarks_list.addItem(name, rectangle) if self.bookmarks_list.currentIndex() >= 0: self.create_bookmarks_label.hide() else: self.create_bookmarks_label.show() self.hazard_exposure_bookmark.setDisabled(True) self.bookmarks_list.hide()
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) @property def configurl(self): url = urlparse.urljoin(add_slash(self.server), "projects/roam.txt") print("URL", url) return url def check_updates(self, server, installedprojects): if not server: return self.server = server req = QNetworkRequest(QUrl(self.configurl)) reply = self.net.get(req) reply.finished.connect(functools.partial(self.list_versions, reply, installedprojects)) def update_server(self, newserverurl, installedprojects): self.check_updates(newserverurl, installedprojects) def list_versions(self, reply, installedprojects): if reply.error() == QNetworkReply.NoError: content = reply.readAll().data() serverversions = parse_serverprojects(content) updateable = list(updateable_projects(installedprojects, serverversions)) new = list(new_projects(installedprojects, serverversions)) print(updateable) print(new) if updateable or new: self.foundProjects.emit(updateable, new) else: msg = "Error in network request for projects: {}".format(reply.errorString()) roam.utils.warning(msg) def update_project(self, projectinfo): self.projectUpdateStatus.emit(projectinfo['name'], "Pending") forupdate.put((projectinfo, projectinfo['version'], self.server, False)) self.updatethread.start() def install_project(self, projectinfo): self.projectUpdateStatus.emit(projectinfo['name'], "Pending") is_new = True forupdate.put((projectinfo, projectinfo['version'], self.server, is_new)) self.updatethread.start()
class ReferencedTableEditor(QWidget): referenced_table_changed = pyqtSignal(str) def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi() self.cbo_ref_table.setInsertPolicy(QComboBox.InsertAlphabetically) self.cbo_ref_table.currentIndexChanged[str].connect(self._on_ref_table_changed) # Tables that will be omitted from the referenced table list self._omit_ref_tables = [] def add_omit_table(self, table): """ Add a table name that will be omitted from the list of referenced tables. :param table: Table name that will be omitted :type table: str """ if not table in self._omit_ref_tables: self._omit_ref_tables.append(table) def add_omit_tables(self, tables): """ Add a list of tables that will be omitted from the list of referenced tables. :param tables: Table names to be omitted. :type tables: list """ for t in tables: self.add_omit_table(t) @property def omit_tables(self): """ :return: Returns a list of tables that are to be omitted from the list of referenced tables. """ return self._omit_ref_tables def setupUi(self): self.setObjectName("ReferencedTableEditor") self.gridLayout = QGridLayout(self) self.gridLayout.setVerticalSpacing(10) self.gridLayout.setObjectName("gridLayout") self.label_2 = QLabel(self) self.label_2.setMaximumSize(QSize(100, 16777215)) self.label_2.setObjectName("label_2") self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) self.cbo_source_field = QComboBox(self) self.cbo_source_field.setMinimumSize(QSize(0, 30)) self.cbo_source_field.setObjectName("cbo_source_field") self.gridLayout.addWidget(self.cbo_source_field, 2, 1, 1, 1) self.label = QLabel(self) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 2, 0, 1, 1) self.label_3 = QLabel(self) self.label_3.setObjectName("label_3") self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1) self.cbo_referencing_col = QComboBox(self) self.cbo_referencing_col.setMinimumSize(QSize(0, 30)) self.cbo_referencing_col.setObjectName("cbo_referencing_col") self.gridLayout.addWidget(self.cbo_referencing_col, 3, 1, 1, 1) self.cbo_ref_table = QComboBox(self) self.cbo_ref_table.setMinimumSize(QSize(0, 30)) self.cbo_ref_table.setObjectName("cbo_ref_table") self.gridLayout.addWidget(self.cbo_ref_table, 1, 1, 1, 1) self.label_2.setText(QApplication.translate("ReferencedTableEditor", "References")) self.label.setText(QApplication.translate("ReferencedTableEditor", "Data source field")) self.label_3.setText(QApplication.translate("ReferencedTableEditor", "Referencing")) self._current_profile = current_profile() self._current_profile_tables = [] if self._current_profile is not None: self._current_profile_tables = self._current_profile.table_names() # Connect signals QMetaObject.connectSlotsByName(self) self.cbo_ref_table.currentIndexChanged[str].connect(self._load_source_table_fields) @pyqtSlot(str) def on_data_source_changed(self, data_source_name): """ Loads data source fields for the given data source name. """ self.load_data_source_fields(data_source_name) def _on_ref_table_changed(self, table): """ Raise signal when the referenced table changes. :param table: Selected table name. :type table: str """ self.referenced_table_changed.emit(table) def properties(self): """ :returns: Returns the user-defined mapping of linked table and column pairings. :rtype: LinkedTableProps """ l_table = self.cbo_ref_table.currentText() s_field = self.cbo_source_field.currentText() l_field = self.cbo_referencing_col.currentText() return LinkedTableProps(linked_table=l_table, source_field=s_field, linked_field=l_field) def set_properties(self, table_props): """ Sets the combo selection based on the text in the linked table object properties. :param table_props: Object containing the linked table information. :type table_props: LinkedTableProps """ setComboCurrentIndexWithText(self.cbo_ref_table, table_props.linked_table) setComboCurrentIndexWithText(self.cbo_source_field, table_props.source_field) setComboCurrentIndexWithText(self.cbo_referencing_col, table_props.linked_field) def load_data_source_fields(self, data_source_name): """ Load fields/columns of the given data source. """ if data_source_name == "": self.clear() return columns_names = table_column_names(data_source_name) if len(columns_names) == 0: return self.cbo_source_field.clear() self.cbo_source_field.addItem("") self.cbo_source_field.addItems(columns_names) def clear(self): """ Resets combo box selections. """ self._reset_combo_index(self.cbo_ref_table) self._reset_combo_index(self.cbo_referencing_col) self._reset_combo_index(self.cbo_source_field) def _reset_combo_index(self, combo): if combo.count > 0: combo.setCurrentIndex(0) def reset_referenced_table(self): self._reset_combo_index(self.cbo_ref_table) def load_link_tables(self, reg_exp=None, source=TABLES | VIEWS): self.cbo_ref_table.clear() self.cbo_ref_table.addItem("") ref_tables = [] source_tables = [] # Table source if (TABLES & source) == TABLES: ref_tables.extend(pg_tables(exclude_lookups=True)) for t in ref_tables: # Ensure we are dealing with tables in the current profile if not t in self._current_profile_tables: continue # Assert if the table is in the list of omitted tables if t in self._omit_ref_tables: continue if not reg_exp is None: if reg_exp.indexIn(t) >= 0: source_tables.append(t) else: source_tables.append(t) # View source if (VIEWS & source) == VIEWS: profile_user_views = profile_and_user_views(self._current_profile) source_tables = source_tables + profile_user_views self.cbo_ref_table.addItems(source_tables) def _load_source_table_fields(self, sel): self.cbo_referencing_col.clear() data_source_index = self.cbo_source_field.currentIndex() self.on_data_source_changed( self.cbo_source_field.itemData(data_source_index) ) if not sel: return columns_names = table_column_names(sel) self.cbo_referencing_col.clear() self.cbo_referencing_col.addItem("") self.cbo_referencing_col.addItems(columns_names)
class GridGeneratorDockWidget(QtWidgets.QDockWidget, FORM_CLASS): closingPlugin = pyqtSignal() def __init__(self, parent=None): """Constructor.""" super(GridGeneratorDockWidget, self).__init__(parent) self.gridAndLabelCreator = GridAndLabelCreator() self.setupUi(self) self.iface = iface self.mapLayerSelection.setFilters(QgsMapLayerProxyModel.PolygonLayer) self.mapLayerSelection.layerChanged.connect(self.idSelection.setLayer) self.okButton.pressed.connect(self.send_inputs) self.resetButton.pressed.connect(self.send_reset) def send_inputs(self): if (not self.mapLayerSelection.currentLayer()) and ( self.idSelection.currentField() ) and (self.idValue.value()) and (self.utmSpacing.value( )) and (self.crossesX.value()) and (self.crossesY.value()) and ( self.mapScale.value()) and (self.labelFontSize.value()) and ( self.fontType.currentFont() ) and (self.fontTypeLL.currentFont()) and ( self.width_geo.value()) and (self.width_utm.value()) and ( self.width_buffer_geo.value()) and ( self.width_buffer_utm.value()): return layer = self.mapLayerSelection.currentLayer() id_attr = self.idSelection.currentField() id_value = self.idValue.value() spacing = self.utmSpacing.value() crossX = self.crossesX.value() crossY = self.crossesY.value() scale = self.mapScale.value() geo_grid_color = self.geo_grid_color.color() utm_grid_color = self.utm_grid_color.color() geo_grid_buffer_color = self.geo_grid_buffer_color.color() utm_grid_buffer_color = self.utm_grid_buffer_color.color() fontSize = self.labelFontSize.value() font = self.fontType.currentFont() fontLL = self.fontTypeLL.currentFont() llcolor = self.llColor.color() linwidth_geo = self.width_geo.value() linwidth_utm = self.width_utm.value() linwidth_buffer_geo = self.width_buffer_geo.value() linwidth_buffer_utm = self.width_buffer_utm.value() masks_check = self.maskCheckBox.isChecked() if layer == None: QMessageBox.information( self, u"Aviso", u"Nenhuma camada selecionada.\nSelecione uma camada vetorial poligonal para gerar o grid." ) return else: testFeature = layer.getFeatures('"' + id_attr + '"' + '=' + str(id_value)) featureList = [i for i in testFeature] if not featureList: QMessageBox.critical( None, u"Erro", u"Escolha um valor existente do atributo de identificação") return else: dialogUTMZoneSelection = UTMZoneSelection( self.iface, layer, id_attr, id_value, spacing, crossX, crossY, scale, fontSize, font, fontLL, llcolor, linwidth_geo, linwidth_utm, linwidth_buffer_geo, linwidth_buffer_utm, geo_grid_color, utm_grid_color, geo_grid_buffer_color, utm_grid_buffer_color, masks_check) dialogUTMZoneSelection.setDialog() def send_reset(self): layer = self.mapLayerSelection.currentLayer() self.gridAndLabelCreator.reset(layer) def closeEvent(self, event): self.closingPlugin.emit() event.accept()
class PlotItem(LogItem): # emitted when the style is updated style_updated = pyqtSignal() def __init__(self, size=QSizeF(400, 200), render_type=POINT_RENDERER, x_orientation=ORIENTATION_LEFT_TO_RIGHT, y_orientation=ORIENTATION_UPWARD, allow_mouse_translation=False, allow_wheel_zoom=False, symbology=None, parent_item=None): """ Parameters ---------- size: QSize Size of the item render_type: Literal[POINT_RENDERER, LINE_RENDERER, POLYGON_RENDERER] Type of renderer x_orientation: Literal[ORIENTATION_LEFT_TO_RIGHT, ORIENTATION_RIGHT_TO_LEFT] y_orientation: Literal[ORIENTATION_UPWARD, ORIENTATION_DOWNWARD] allow_mouse_translation: bool Allow the user to translate the item with the mouse (??) allow_wheel_zoom: bool Allow the user to zoom with the mouse wheel symbology: QDomDocument QGIS symbology to use for the renderer parent_item: QGraphicsItem """ LogItem.__init__(self, parent_item) self.__item_size = size self.__data_rect = None self.__data = None self.__delta = None self.__x_orientation = x_orientation self.__y_orientation = y_orientation # origin point of the graph translation, if any self.__translation_orig = None self.__render_type = render_type # type: Literal[POINT_RENDERER, LINE_RENDERER, POLYGON_RENDERER] self.__allow_mouse_translation = allow_mouse_translation self.__allow_wheel_zoom = allow_wheel_zoom self.__layer = None self.__default_renderers = [ QgsFeatureRenderer.defaultRenderer(POINT_RENDERER), QgsFeatureRenderer.defaultRenderer(LINE_RENDERER), QgsFeatureRenderer.defaultRenderer(POLYGON_RENDERER) ] symbol = self.__default_renderers[1].symbol() symbol.setWidth(1.0) symbol = self.__default_renderers[0].symbol() symbol.setSize(5.0) symbol = self.__default_renderers[2].symbol() symbol.symbolLayers()[0].setStrokeWidth(1.0) if not symbology: self.__renderer = self.__default_renderers[self.__render_type] else: self.__renderer = QgsFeatureRenderer.load( symbology.documentElement(), QgsReadWriteContext()) # index of the current point to label self.__old_point_to_label = None self.__point_to_label = None def boundingRect(self): return QRectF(0, 0, self.__item_size.width(), self.__item_size.height()) def height(self): return self.__item_size.height() def set_height(self, height): self.__item_size.setHeight(height) def width(self): return self.__item_size.width() def set_width(self, width): self.__item_size.setWidth(width) def set_data_window(self, window): """window: QRectF""" self.__data_rect = window def min_depth(self): if self.__data_rect is None: return None return self.__data_rect.x() def max_depth(self): if self.__data_rect is None: return None return (self.__data_rect.x() + self.__data_rect.width()) def set_min_depth(self, min_depth): if self.__data_rect is not None: self.__data_rect.setX(min_depth) def set_max_depth(self, max_depth): if self.__data_rect is not None: w = max_depth - self.__data_rect.x() self.__data_rect.setWidth(w) def layer(self): return self.__layer def set_layer(self, layer): self.__layer = layer def data_window(self): return self.__data_rect def set_data(self, x_values, y_values): self.__x_values = x_values self.__y_values = y_values if len(self.__x_values) != len(self.__y_values): raise ValueError("X and Y array has different length : " "{} != {}".format(len(self.__x_values), len(self.__y_values))) # Remove None values for i in reversed(range(len(self.__y_values))): if (self.__y_values[i] is None or self.__x_values[i] is None or math.isnan(self.__y_values[i]) or math.isnan(self.__x_values[i])): self.__y_values.pop(i) self.__x_values.pop(i) if not self.__x_values: return # Initialize data rect to display all data # with a 20% buffer around Y values min_x = min(self.__x_values) max_x = max(self.__x_values) min_y = min(self.__y_values) max_y = max(self.__y_values) h = max_y - min_y if h == 0.0: h = 1.0 min_y -= h * 0.1 max_y += h * 0.1 self.__data_rect = QRectF(min_x, min_y, max_x - min_x, max_y - min_y) def renderer(self): return self.__renderer def paint(self, painter, option, widget): self.draw_background(painter) if self.__data_rect is None: return # Look for the first and last X values that fit our rendering rect imin_x = bisect.bisect_left(self.__x_values, self.__data_rect.x()) imax_x = bisect.bisect_right(self.__x_values, self.__data_rect.right()) # For lines and polygons, retain also one value before the min and one after the max # so that lines do not appear truncated # Do this only if we have at least one point to render within out rect if imin_x > 0 and imin_x < len( self.__x_values ) and self.__x_values[imin_x] >= self.__data_rect.x(): # FIXME add a test to avoid adding a point too "far away" ? imin_x -= 1 if imax_x < len(self.__x_values) - 1 and self.__x_values[ imax_x] <= self.__data_rect.right(): # FIXME add a test to avoid adding a point too "far away" ? imax_x += 1 x_values_slice = np.array(self.__x_values[imin_x:imax_x]) y_values_slice = np.array(self.__y_values[imin_x:imax_x]) if len(x_values_slice) == 0: return # filter points that are not None (nan in numpy arrays) n_points = len(x_values_slice) if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD: if self.__data_rect.width() > 0: rw = float(self.__item_size.width()) / self.__data_rect.width() else: rw = float(self.__item_size.width()) if self.__data_rect.height() > 0: rh = float( self.__item_size.height()) / self.__data_rect.height() else: rh = float(self.__item_size.height()) xx = (x_values_slice - self.__data_rect.x()) * rw yy = (y_values_slice - self.__data_rect.y()) * rh elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT: if self.__data_rect.width() > 0: rw = float( self.__item_size.height()) / self.__data_rect.width() else: rw = float(self.__item_size.height()) if self.__data_rect.height() > 0: rh = float( self.__item_size.width()) / self.__data_rect.height() else: rh = float(self.__item_size.width()) xx = (y_values_slice - self.__data_rect.y()) * rh yy = self.__item_size.height() - (x_values_slice - self.__data_rect.x()) * rw if self.__render_type == LINE_RENDERER: # WKB structure of a linestring # # 01 : endianness # 02 00 00 00 : WKB type (linestring) # nn nn nn nn : number of points (int32) # Then, for each point: # xx xx xx xx xx xx xx xx : X coordinate (float64) # yy yy yy yy yy yy yy yy : Y coordinate (float64) wkb = np.zeros(8 * 2 * n_points + 9, dtype='uint8') wkb[0] = 1 # wkb endianness wkb[1] = 2 # linestring size_view = np.ndarray(buffer=wkb, dtype='int32', offset=5, shape=(1, )) size_view[0] = n_points coords_view = np.ndarray(buffer=wkb, dtype='float64', offset=9, shape=(n_points, 2)) coords_view[:, 0] = xx[:] coords_view[:, 1] = yy[:] elif self.__render_type == POINT_RENDERER: # WKB structure of a multipoint # # 01 : endianness # 04 00 00 00 : WKB type (multipoint) # nn nn nn nn : number of points (int32) # Then, for each point: # 01 : endianness # 01 00 00 00 : WKB type (point) # xx xx xx xx xx xx xx xx : X coordinate (float64) # yy yy yy yy yy yy yy yy : Y coordinate (float64) wkb = np.zeros((8 * 2 + 5) * n_points + 9, dtype='uint8') wkb[0] = 1 # wkb endianness wkb[1] = 4 # multipoint size_view = np.ndarray(buffer=wkb, dtype='int32', offset=5, shape=(1, )) size_view[0] = n_points coords_view = np.ndarray(buffer=wkb, dtype='float64', offset=9 + 5, shape=(n_points, 2), strides=(16 + 5, 8)) coords_view[:, 0] = xx[:] coords_view[:, 1] = yy[:] # header of each point h_view = np.ndarray(buffer=wkb, dtype='uint8', offset=9, shape=(n_points, 2), strides=(16 + 5, 1)) h_view[:, 0] = 1 # endianness h_view[:, 1] = 1 # point elif self.__render_type == POLYGON_RENDERER: # WKB structure of a polygon # # 01 : endianness # 03 00 00 00 : WKB type (polygon) # 01 00 00 00 : Number of rings (always 1 here) # nn nn nn nn : number of points (int32) # Then, for each point: # xx xx xx xx xx xx xx xx : X coordinate (float64) # yy yy yy yy yy yy yy yy : Y coordinate (float64) # # We add two additional points to close the polygon wkb = np.zeros(8 * 2 * (n_points + 2) + 9 + 4, dtype='uint8') wkb[0] = 1 # wkb endianness wkb[1] = 3 # polygon wkb[5] = 1 # number of rings size_view = np.ndarray(buffer=wkb, dtype='int32', offset=9, shape=(1, )) size_view[0] = n_points + 2 coords_view = np.ndarray(buffer=wkb, dtype='float64', offset=9 + 4, shape=(n_points, 2)) coords_view[:, 0] = xx[:] coords_view[:, 1] = yy[:] # two extra points extra_coords = np.ndarray(buffer=wkb, dtype='float64', offset=8 * 2 * n_points + 9 + 4, shape=(2, 2)) if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD: extra_coords[0, 0] = coords_view[-1, 0] extra_coords[0, 1] = 0.0 extra_coords[1, 0] = coords_view[0, 0] extra_coords[1, 1] = 0.0 elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT: extra_coords[0, 0] = 0.0 extra_coords[0, 1] = coords_view[-1, 1] extra_coords[1, 0] = 0.0 extra_coords[1, 1] = coords_view[0, 1] # build a geometry from the WKB # since numpy arrays have buffer protocol, sip is able to read it geom = QgsGeometry() geom.fromWkb(wkb.tobytes()) painter.setClipRect(0, 0, self.__item_size.width(), self.__item_size.height()) fields = QgsFields() #fields.append(QgsField("", QVariant.String)) feature = QgsFeature(fields, 1) feature.setGeometry(geom) context = qgis_render_context(painter, self.__item_size.width(), self.__item_size.height()) context.setExtent( QgsRectangle(0, 1, self.__item_size.width(), self.__item_size.height())) self.__renderer.startRender(context, fields) self.__renderer.renderFeature(feature, context) self.__renderer.stopRender(context) if self.__point_to_label is not None: i = self.__point_to_label x, y = self.__x_values[i], self.__y_values[i] if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD: px = (x - self.__data_rect.x()) * rw py = self.__item_size.height() - (y - self.__data_rect.y()) * rh elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT: px = (y - self.__data_rect.y()) * rh py = (x - self.__data_rect.x()) * rw painter.drawLine(px - 5, py, px + 5, py) painter.drawLine(px, py - 5, px, py + 5) def mouseMoveEvent(self, event): if not self.__x_values: return if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD: xx = (event.scenePos().x() - self.pos().x()) / self.width( ) * self.__data_rect.width() + self.__data_rect.x() elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT: xx = (event.scenePos().y() - self.pos().y()) / self.height( ) * self.__data_rect.width() + self.__data_rect.x() i = bisect.bisect_left(self.__x_values, xx) if i >= 0 and i < len(self.__x_values): # switch the attached point when we are between two points if i > 0 and (xx - self.__x_values[i - 1]) < (self.__x_values[i] - xx): i -= 1 self.__point_to_label = i else: self.__point_to_label = None if self.__point_to_label != self.__old_point_to_label: self.update() if self.__point_to_label is not None: x, y = self.__x_values[i], self.__y_values[i] if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD: dt = datetime.fromtimestamp(x, UTC()) txt = "Time: {} Value: {}".format(dt.strftime("%x %X"), y) elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT: txt = "Depth: {} Value: {}".format(x, y) self.tooltipRequested.emit(txt) self.__old_point_to_label = self.__point_to_label def edit_style(self): from qgis.gui import QgsSingleSymbolRendererWidget from qgis.core import QgsStyle style = QgsStyle() sw = QStackedWidget() sw.addWidget for i in range(3): if self.__renderer and i == self.__render_type: w = QgsSingleSymbolRendererWidget(self.__layer, style, self.__renderer) else: w = QgsSingleSymbolRendererWidget(self.__layer, style, self.__default_renderers[i]) sw.addWidget(w) combo = QComboBox() combo.addItem("Points") combo.addItem("Line") combo.addItem("Polygon") combo.currentIndexChanged[int].connect(sw.setCurrentIndex) combo.setCurrentIndex(self.__render_type) dlg = QDialog() vbox = QVBoxLayout() btn = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) btn.accepted.connect(dlg.accept) btn.rejected.connect(dlg.reject) vbox.addWidget(combo) vbox.addWidget(sw) vbox.addWidget(btn) dlg.setLayout(vbox) dlg.resize(800, 600) r = dlg.exec_() if r == QDialog.Accepted: self.__render_type = combo.currentIndex() self.__renderer = sw.currentWidget().renderer().clone() self.update() self.style_updated.emit() def qgis_style(self): """Returns the current style, as a QDomDocument""" from PyQt5.QtXml import QDomDocument from qgis.core import QgsReadWriteContext doc = QDomDocument() elt = self.__renderer.save(doc, QgsReadWriteContext()) doc.appendChild(elt) return (doc, self.__render_type)
class WidgetWrapper(QObject): widgetValueHasChanged = pyqtSignal(object) def __init__(self, param, dialog, row=0, col=0, **kwargs): QObject.__init__(self) self.param = param self.dialog = dialog self.row = row self.col = col self.dialogType = dialogTypes.get(dialog.__class__.__name__, DIALOG_STANDARD) self.widget = self.createWidget(**kwargs) if param.default is not None: self.setValue(param.default) def comboValue(self, validator=None, combobox=None): if combobox is None: combobox = self.widget idx = combobox.findText(combobox.currentText()) if idx < 0: v = combobox.currentText().strip() if validator is not None and not validator(v): raise InvalidParameterValue() return v return combobox.currentData() def createWidget(self, **kwargs): pass def setValue(self, value): pass def setComboValue(self, value, combobox=None): if combobox is None: combobox = self.widget if isinstance(value, list): value = value[0] values = [combobox.itemData(i) for i in range(combobox.count())] try: idx = values.index(value) combobox.setCurrentIndex(idx) return except ValueError: pass if combobox.isEditable(): if value is not None: combobox.setEditText(str(value)) else: combobox.setCurrentIndex(0) def value(self): pass def postInitialize(self, wrappers): pass def refresh(self): pass def getFileName(self, initial_value=''): """Shows a file open dialog""" settings = QgsSettings() if os.path.isdir(initial_value): path = initial_value elif os.path.isdir(os.path.dirname(initial_value)): path = os.path.dirname(initial_value) elif settings.contains('/Processing/LastInputPath'): path = str(settings.value('/Processing/LastInputPath')) else: path = '' filename, selected_filter = QFileDialog.getOpenFileName( self.widget, self.tr('Select file'), path, self.tr('All files (*.*);;') + self.param.getFileFilter()) if filename: settings.setValue('/Processing/LastInputPath', os.path.dirname(str(filename))) return filename, selected_filter
class ListMultiSelectWidget(QGroupBox): """Widget to show two parallel lists and move elements between the two usage from code: self.myWidget = ListMultiSelectWidget(title='myTitle') self.myLayout.insertWidget(1, self.myWidget) usage from designer: insert a QGroupBox in your UI file optionally give a title to the QGroupBox promote it to ListMultiSelectWidget """ selection_changed = pyqtSignal() def __init__(self, parent=None, title=None): QGroupBox.__init__(self) self.setTitle(title) self.selected_widget = None self.deselected_widget = None self._setupUI() # connect actions self.select_all_btn.clicked.connect(self._select_all) self.deselect_all_btn.clicked.connect(self._deselect_all) self.select_btn.clicked.connect(self._select) self.deselect_btn.clicked.connect(self._deselect) self.deselected_widget.itemDoubleClicked.connect(self._select) self.selected_widget.itemDoubleClicked.connect(self._deselect) def get_selected_items(self): """ :return list with all the selected items text """ return self._get_items(self.selected_widget) def get_deselected_items(self): """ :return list with all the deselected items text """ return self._get_items(self.deselected_widget) def add_selected_items(self, items): """ :param items list of strings to be added in the selected list """ self._add_items(self.selected_widget, items) def add_deselected_items(self, items): """ :param items list of strings to be added in the deselected list """ self._add_items(self.deselected_widget, items) def set_selected_items(self, items): """ :param items list of strings to be set as the selected list """ self._set_items(self.selected_widget, items) def set_deselected_items(self, items): """ :param items list of strings to be set as the deselected list """ self._set_items(self.deselected_widget, items) def clear(self): """ removes all items from selected and deselected """ self.set_selected_items([]) self.set_deselected_items([]) def addItem(self, item): """ This is for Processing :param item: string to be added in the deselected list """ self.add_deselected_items([item]) def addItems(self, items): """ This is for Processing :param items: list of strings to be added in the deselected list """ self.add_deselected_items(items) def _get_items(self, widget): for i in range(widget.count()): yield widget.item(i).text() def _set_items(self, widget, items): widget.clear() self._add_items(widget, items) def _add_items(self, widget, items): widget.addItems(items) def _select_all(self): self.deselected_widget.selectAll() self._do_move(self.deselected_widget, self.selected_widget) def _deselect_all(self): self.selected_widget.selectAll() self._do_move(self.selected_widget, self.deselected_widget) def _select(self): self._do_move(self.deselected_widget, self.selected_widget) def _deselect(self): self._do_move(self.selected_widget, self.deselected_widget) def _do_move(self, fromList, toList): for item in fromList.selectedItems(): prev_from_item = fromList.item(fromList.row(item) - 1) toList.addItem(fromList.takeItem(fromList.row(item))) fromList.scrollToItem(prev_from_item) self.selection_changed.emit() def _setupUI(self): self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumHeight(180) self.main_horizontal_layout = QHBoxLayout(self) italic_font = QFont() italic_font.setItalic(True) # deselected widget self.deselected_widget = QListWidget(self) self._set_list_widget_defaults(self.deselected_widget) deselected_label = QLabel() deselected_label.setText('Deselected') deselected_label.setAlignment(Qt.AlignCenter) deselected_label.setFont(italic_font) deselected_v_layout = QVBoxLayout() deselected_v_layout.addWidget(deselected_label) deselected_v_layout.addWidget(self.deselected_widget) # selected widget self.selected_widget = QListWidget(self) self._set_list_widget_defaults(self.selected_widget) selected_label = QLabel() selected_label.setText('Selected') selected_label.setAlignment(Qt.AlignCenter) selected_label.setFont(italic_font) selected_v_layout = QVBoxLayout() selected_v_layout.addWidget(selected_label) selected_v_layout.addWidget(self.selected_widget) # buttons self.buttons_vertical_layout = QVBoxLayout() self.buttons_vertical_layout.setContentsMargins(0, -1, 0, -1) self.select_all_btn = SmallQPushButton('>>') self.deselect_all_btn = SmallQPushButton('<<') self.select_btn = SmallQPushButton('>') self.deselect_btn = SmallQPushButton('<') self.select_btn.setToolTip('Add the selected items') self.deselect_btn.setToolTip('Remove the selected items') self.select_all_btn.setToolTip('Add all') self.deselect_all_btn.setToolTip('Remove all') # add buttons spacer_label = QLabel() # pragmatic way to create a spacer with # the same height of the labels on top # of the lists, in order to align the # buttons with the lists. self.buttons_vertical_layout.addWidget(spacer_label) self.buttons_vertical_layout.addWidget(self.select_btn) self.buttons_vertical_layout.addWidget(self.deselect_btn) self.buttons_vertical_layout.addWidget(self.select_all_btn) self.buttons_vertical_layout.addWidget(self.deselect_all_btn) # add sub widgets self.main_horizontal_layout.addLayout(deselected_v_layout) self.main_horizontal_layout.addLayout(self.buttons_vertical_layout) self.main_horizontal_layout.addLayout(selected_v_layout) def _set_list_widget_defaults(self, widget): widget.setAlternatingRowColors(True) widget.setSortingEnabled(True) widget.setDragEnabled(True) widget.setDragDropMode(QAbstractItemView.DragDrop) widget.setDragDropOverwriteMode(False) widget.setDefaultDropAction(Qt.MoveAction) widget.setSelectionMode(QAbstractItemView.MultiSelection)
class NumberInputPanel(NUMBER_BASE, NUMBER_WIDGET): """ Number input panel for use outside the modeler - this input panel contains a user friendly spin box for entering values. """ hasChanged = pyqtSignal() def __init__(self, param): super(NumberInputPanel, self).__init__(None) self.setupUi(self) self.spnValue.setExpressionsEnabled(True) self.param = param if self.param.dataType() == QgsProcessingParameterNumber.Integer: self.spnValue.setDecimals(0) else: # Guess reasonable step value if self.param.maximum() is not None and self.param.minimum( ) is not None: try: self.spnValue.setSingleStep( self.calculateStep(float(self.param.minimum()), float(self.param.maximum()))) except: pass if self.param.maximum() is not None: self.spnValue.setMaximum(self.param.maximum()) else: self.spnValue.setMaximum(999999999) if self.param.minimum() is not None: self.spnValue.setMinimum(self.param.minimum()) else: self.spnValue.setMinimum(-999999999) self.allowing_null = False # set default value if param.flags() & QgsProcessingParameterDefinition.FlagOptional: self.spnValue.setShowClearButton(True) min = self.spnValue.minimum() - 1 self.spnValue.setMinimum(min) self.spnValue.setValue(min) self.spnValue.setSpecialValueText(self.tr('Not set')) self.allowing_null = True if param.defaultValue() is not None: self.setValue(param.defaultValue()) if not self.allowing_null: try: self.spnValue.setClearValue(float(param.defaultValue())) except: pass elif self.param.minimum() is not None: try: self.setValue(float(self.param.minimum())) if not self.allowing_null: self.spnValue.setClearValue(float(self.param.minimum())) except: pass elif not self.allowing_null: self.setValue(0) self.spnValue.setClearValue(0) # we don't show the expression button outside of modeler self.layout().removeWidget(self.btnSelect) sip.delete(self.btnSelect) self.btnSelect = None if not self.param.isDynamic(): # only show data defined button for dynamic properties self.layout().removeWidget(self.btnDataDefined) sip.delete(self.btnDataDefined) self.btnDataDefined = None else: self.btnDataDefined.init(0, QgsProperty(), self.param.dynamicPropertyDefinition()) self.btnDataDefined.registerEnabledWidget(self.spnValue, False) self.spnValue.valueChanged.connect(lambda: self.hasChanged.emit()) def setDynamicLayer(self, layer): try: self.btnDataDefined.setVectorLayer(self.getLayerFromValue(layer)) except: pass def getLayerFromValue(self, value): context = createContext() if isinstance(value, QgsProcessingFeatureSourceDefinition): value, ok = value.source.valueAsString(context.expressionContext()) if isinstance(value, str): value = QgsProcessingUtils.mapLayerFromString(value, context) return value def getValue(self): if self.btnDataDefined is not None and self.btnDataDefined.isActive(): return self.btnDataDefined.toProperty() elif self.allowing_null and self.spnValue.value( ) == self.spnValue.minimum(): return None else: return self.spnValue.value() def setValue(self, value): try: self.spnValue.setValue(float(value)) except: return def calculateStep(self, minimum, maximum): value_range = maximum - minimum if value_range <= 1.0: step = value_range / 10.0 # round to 1 significant figrue return round(step, -int(math.floor(math.log10(step)))) else: return 1.0