class CadastaLogin(QtGui.QDialog, FORM_CLASS): def __init__(self, parent=None): """Constructor.""" super(CadastaLogin, self).__init__(parent) self.setupUi(self) self.login_button.clicked.connect(self.login) self.msg_bar = None def login(self): """Login function when tools button clicked""" username = self.username_input.displayText() password = self.password_input.displayText() if not username or not password: self.msg_bar = QgsMessageBar() self.msg_bar.pushWarning("Error", "Username/password is empty.") else: self.login_button.setEnabled(False) self.output_label.setText("logging in....") # call tools API self.login_api = Login(username, password, self.on_finished) def on_finished(self, result): """On finished function when tools request is finished""" if 'auth_token' in result: auth_token = result['auth_token'] output_result = "auth_token is %s" % auth_token else: output_result = "'%s'" % result self.output_label.setText(output_result) self.login_button.setEnabled(True)
def accept(self): self.tblStatistics.setRowCount(0) layer = self.cmbLayer.currentLayer() if self.chkSelectedOnly.isChecked() and layer.selectedFeatureCount() == 0: QgsMessageBar.pushWarning(self.tr("No selection"), self.tr("There is no selection in the input layer. Uncheck " "corresponding option or select some features before " "running analysis")) return self.calculator.setLayer(layer) self.calculator.setField(self.cmbField.currentField()) self.calculator.setSelectedOnly(self.chkSelectedOnly.isChecked()) self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) self.thread.start()
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 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) # 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.addDockWidget(Qt.DockWidgetArea(1), self.propertiesDock) 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.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.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.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() or event.mimeData().hasFormat('application/x-vnd.qgis.qgis.algorithmid'): event.acceptProposedAction() else: event.ignore() def _dropEvent(event): 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() alg = QgsApplication.processingRegistry().createAlgorithmById(algorithm_id) if alg is not None: self._addAlgorithm(alg, event.pos()) else: assert False, algorithm_id elif event.mimeData().hasText(): itemId = event.mimeData().text() if itemId in [param.id() for param in QgsApplication.instance().processingRegistry().parameterTypes()]: self.addInputOfType(itemId, event.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) self.algorithmTree.setFilters(QgsProcessingToolboxProxyModel.FilterModeler) 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.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 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 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.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.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)
class Ili2dbOptionsDialog(QDialog, DIALOG_UI): ValidExtensions = ['toml', 'TOML'] SQLValidExtensions = ['sql', 'SQL'] def __init__(self, parent=None): QDialog.__init__(self, parent) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.toml_file_key = None self.buttonBox.accepted.disconnect() self.buttonBox.accepted.connect(self.accepted) self.buttonBox.rejected.disconnect() self.buttonBox.rejected.connect(self.rejected) self.toml_file_browse_button.clicked.connect( make_file_selector( self.toml_file_line_edit, title=self.tr('Open Extra Model Information File (*.toml)'), file_filter=self.tr('Extra Model Info File (*.toml *.TOML)'))) self.pre_script_file_browse_button.clicked.connect( make_file_selector( self.pre_script_file_line_edit, title=self.tr('SQL script to run before (*.sql)'), file_filter=self.tr('SQL script to run before (*.sql *.SQL)'))) self.post_script_file_browse_button.clicked.connect( make_file_selector( self.post_script_file_line_edit, title=self.tr('SQL script to run after (*.sql)'), file_filter=self.tr('SQL script to run after (*.sql *.SQL)'))) self.validators = Validators() self.file_validator = FileValidator( pattern=['*.' + ext for ext in self.ValidExtensions], allow_empty=True) self.toml_file_line_edit.setValidator(self.file_validator) self.sql_file_validator = FileValidator( pattern=['*.' + ext for ext in self.SQLValidExtensions], allow_empty=True) self.pre_script_file_line_edit.setValidator(self.sql_file_validator) self.post_script_file_line_edit.setValidator(self.sql_file_validator) self.restore_configuration() self.toml_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.toml_file_line_edit.textChanged.emit( self.toml_file_line_edit.text()) self.pre_script_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.pre_script_file_line_edit.textChanged.emit( self.pre_script_file_line_edit.text()) self.post_script_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.post_script_file_line_edit.textChanged.emit( self.post_script_file_line_edit.text()) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) def accepted(self): """ Save settings before accepting the dialog """ for line_edit in [ self.pre_script_file_line_edit, self.post_script_file_line_edit, self.toml_file_line_edit ]: if line_edit.validator().validate(line_edit.text().strip(), 0)[0] != QValidator.Acceptable: self.bar.pushWarning( self.tr("Warning"), self.tr( "Please fix the '{}' value before saving the options." ).format(line_edit.objectName().split("_file_line_edit") [0].replace("_", " "))) return self.save_configuration() self.done(1) def rejected(self): self.restore_configuration() self.done(1) def create_import_tid(self): return self.create_import_tid_checkbox.isChecked() def create_basket_col(self): return self.create_basket_col_checkbox.isChecked() def inheritance_type(self): if self.smart1_radio_button.isChecked(): return 'smart1' else: return 'smart2' def set_toml_file_key(self, key_postfix): if key_postfix: self.toml_file_key = 'QgisModelBaker/ili2db/tomlfile/' + key_postfix else: self.toml_file_key = None self.restore_configuration() def toml_file(self): return self.toml_file_line_edit.text().strip() def pre_script(self): return self.pre_script_file_line_edit.text().strip() def post_script(self): return self.post_script_file_line_edit.text().strip() def stroke_arcs(self): return self.stroke_arcs_checkbox.isChecked() def save_configuration(self): settings = QSettings() settings.setValue('QgisModelBaker/ili2db/inheritance', self.inheritance_type()) settings.setValue(self.toml_file_key, self.toml_file()) settings.setValue('QgisModelBaker/ili2db/create_basket_col', self.create_basket_col()) settings.setValue('QgisModelBaker/ili2db/create_import_tid', self.create_import_tid()) settings.setValue('QgisModelBaker/ili2db/stroke_arcs', self.stroke_arcs()) def restore_configuration(self): settings = QSettings() inheritance = settings.value('QgisModelBaker/ili2db/inheritance', 'smart2') if inheritance == 'smart1': self.smart1_radio_button.setChecked(True) else: self.smart2_radio_button.setChecked(True) create_basket_col = settings.value( 'QgisModelBaker/ili2db/create_basket_col', defaultValue=False, type=bool) create_import_tid = settings.value( 'QgisModelBaker/ili2db/create_import_tid', defaultValue=True, type=bool) stroke_arcs = settings.value('QgisModelBaker/ili2db/stroke_arcs', defaultValue=True, type=bool) self.create_basket_col_checkbox.setChecked(create_basket_col) self.create_import_tid_checkbox.setChecked(create_import_tid) self.stroke_arcs_checkbox.setChecked(stroke_arcs) self.toml_file_line_edit.setText(settings.value(self.toml_file_key))
class StepProjectCreation1(WizardStep, FORM_CLASS): """Step 1 for project creation.""" def __init__(self, parent=None): """Constructor. :param parent: parent - widget to use as parent. :type parent: QWidget """ super(StepProjectCreation1, self).__init__(parent) self.organisation = Organization() self.organization = None def set_widgets(self): """Set all widgets on the tab.""" contacts = Contact.get_rows() for contact in contacts: contact_name = contact.name contact_email = ' - ' + contact.email if contact.email else '' contact_phone = ' - ' + contact.phone if contact.phone else '' contact_item = QListWidgetItem(contact_name + contact_email + contact_phone) contact_item.setData(Qt.UserRole, contact) self.project_contact_list.addItem(contact_item) self.project_contact_list.setSelectionMode( QAbstractItemView.ExtendedSelection) icon_path = resources_path('images', 'throbber.gif') movie = QMovie(icon_path) self.throbber_loader.setMovie(movie) movie.start() self.get_available_organisations() self.organisation_box.setFocus() self.project_description_text.setTabChangesFocus(True) def project_name(self): """Get project name from input. :returns: Project name :rtype: str """ return self.project_name_text.displayText() def project_url(self): """Get project url from input. :returns: Project url :rtype: str """ return self.project_url_text.displayText() def selected_layer(self): """Get selected layer from combo box. :returns: Layer data :rtype: QgsVectorLayer """ layer_data = self.qgis_layer_box.currentLayer() return layer_data def selected_organisation(self): """Get selected organisation from combo box. :returns: Organisation data :rtype: dict """ organisation_data = self.organisation_box.itemData( self.organisation_box.currentIndex()) return organisation_data def validate_step(self): """Check if the step is valid. :returns: Tuple of validation status and error message if any :rtype: ( bool, str ) """ error_message = '' if self.project_url() and \ not is_valid_url(self.project_url()): error_message = tr('Invalid url.') if not self.project_name() or \ not self.selected_organisation(): error_message = tr('Empty required field.') return (error_message == '', error_message) def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to :rtype: WizardStep instance or None """ # Check if layer has locations features = self.selected_layer().getFeatures() feature = next(features, None) # If no locations found, skip step 2 if not feature: new_step = self.parent.step_project_creation03 else: new_step = self.parent.step_project_creation02 return new_step def on_downloaded_organization(self, results): """Finish downloading organization list. """ self.throbber_loader.setVisible(False) if results[0]: self.organisation_box.clear() self.parent.organisations_list = results[1] for organisation in results[1]: self.organisation_box.addItem(organisation['name'], organisation) else: self.message_bar = QgsMessageBar() self.message_bar.pushWarning(self.tr('Error'), results[1]) def get_available_organisations(self): """Get available organisations and load it to organisation combo box. """ self.throbber_loader.setVisible(True) LOGGER.info('Getting organisations') self.organization = OrganizationList( permissions='project.create,project.list', on_finished=self.on_downloaded_organization) def all_data(self): """Return all data from this step. :returns: step 1 data :rtype: dict """ data = dict() data['organisation'] = self.selected_organisation() data['project_name'] = self.project_name() data['project_url'] = self.project_url() data['description'] = self.project_description_text.toPlainText() data['private'] = self.is_project_private.isChecked() # Get contacts data['contacts'] = [] contact_item_list = self.project_contact_list.selectedItems() for contact_item in contact_item_list: contact = contact_item.data(Qt.UserRole) contact_data = {} if contact.name: contact_data['name'] = contact.name if contact.phone: contact_data['tel'] = contact.phone if contact.email: contact_data['email'] = contact.email data['contacts'].append(contact_data) # Get extent layer = self.selected_layer() if self.use_layer_extents.isChecked(): # Layer extent extent = layer.extent() else: # Canvas extent extent = self.parent.iface.mapCanvas().extent() geometry = QgsGeometry().fromRect(extent) data['extent'] = geometry.exportToGeoJSON() # Save layer to geojson format output_file = get_path_data(project_slug='temp_project') output_file = os.path.join(output_file, '%s.geojson' % 'temp_project') result = QgsVectorFileWriter.writeAsVectorFormat( layer, output_file, 'utf-8', layer.crs(), 'GeoJson') if result == QgsVectorFileWriter.NoError: LOGGER.debug('Wrote layer to geojson: %s' % output_file) with open(output_file) as json_data: layer_data = json.load(json_data) data['locations'] = layer_data os.remove(output_file) else: LOGGER.error('Failed with error: %s' % result) return data
class StepProjectDownload02(WizardStep, FORM_CLASS): """Step 2 for project download.""" def __init__(self, parent=None): """Constructor. :param parent: parent - widget to use as parent. :type parent: QWidget """ super(StepProjectDownload02, self).__init__(parent) self.loading_label_string = None self.loaded_label_string = None self.spatial_api = None self.organisation_api = Organization() def set_widgets(self): """Set all widgets on the tab.""" self.loading_label_string = self.tr('Your data is being downloaded') self.loaded_label_string = self.tr('Your data has been downloaded') self.warning_label.setText(self.loading_label_string) self.get_project_spatial(self.project['organization']['slug'], self.project['slug']) self.parent.next_button.setEnabled(True) def validate_step(self): """Check if the step is valid. :returns: Tuple of validation status and error message if any :rtype: ( bool, str ) """ return True, '' def get_next_step(self): """Find the proper step when user clicks the Next button. This method must be implemented in derived classes. :returns: The step to be switched to :rtype: WizardStep instance or None """ return None def get_project_spatial(self, organization_slug, project_slug): """Call Organization Project Spatial api. :param project_slug: project_slug for getting spatial :type project_slug: str """ self.spatial_api = OrganizationProjectSpatial( organization_slug, project_slug, on_finished=self.organization_projects_spatial_call_finished) def organization_projects_spatial_call_finished(self, result): """Function when Organization Project Spatial Api finished. :param result: result of request :type result: (bool, list/dict/str) """ self.save_organizations() if result[0]: # save result to local file organization_slug = result[2] project_slug = result[3] vlayers = Utilities.save_layer(result[1], organization_slug, project_slug) self.progress_bar.setValue(50) download_relationship_and_party = False if self.project['access'] == 'public': # Get organization status, results = self.organisation_api.summary_organization( organization_slug) if status and 'users' in results: for user in results['users']: if user['username'] == get_setting('username'): download_relationship_and_party = True break else: download_relationship_and_party = True relationship_layer_id = None party_layer_id = None if download_relationship_and_party: relationship_layer = self.relationships_layer(vlayers) if relationship_layer: relationship_layer_id = relationship_layer.id() party_layer = self.parties_layer() if party_layer: party_layer_id = party_layer.id() self.progress_bar.setValue(80) QCoreApplication.processEvents() Utilities.save_project_basic_information(self.project, vlayers, relationship_layer_id, party_layer_id) else: pass self.progress_bar.setValue(self.progress_bar.maximum()) self.warning_label.setText(self.loaded_label_string) self.parent.close() def save_organizations(self): """Save organizations of user. Organization is saved to setting. If it is success, close dialog after that. """ status, results = self.organisation_api. \ organizations_project_filtered() if status: organization = [] for result in results: organization.append(result['slug']) save_user_organizations(organization) else: self.message_bar = QgsMessageBar() self.message_bar.pushWarning( self.tr('Error'), self.tr('Error when getting user permission.')) self.parent.downloaded.emit() def parties_layer(self): """Create parties layer. :param vector_layer: QGS vector layer in memory :type vector_layer: QgsVectorLayer """ organization_slug = self.project['organization']['slug'] project_slug = self.project['slug'] attribute = 'parties' csv_path = get_csv_path(organization_slug, project_slug, attribute) if os.path.isfile(csv_path): os.remove(csv_path) api = u'/api/v1/organizations/{organization_slug}/projects/' \ u'{project_slug}/parties/'.format( organization_slug=organization_slug, project_slug=project_slug) connector = ApiConnect(get_url_instance() + api) status, results = connector.get(paginated=True) if not status: return party_layer = tools.create_memory_layer( layer_name='%s/%s/%s' % (organization_slug, project_slug, attribute), geometry=QGis.NoGeometry, fields=[ QgsField('id', QVariant.String, "string"), QgsField('name', QVariant.String, "string"), QgsField('type', QVariant.String, "string"), QgsField('attributes', QVariant.String, "string"), ]) QgsMapLayerRegistry.instance().addMapLayer(party_layer) for party in results: party_layer.startEditing() feature = QgsFeature() questionnaire_attr = party['attributes'] if not questionnaire_attr: questionnaire_attr = '-' else: questionnaire_attr = json.dumps(questionnaire_attr) feature.setAttributes([ party['id'], party['name'], party['type'], questionnaire_attr ]) party_layer.addFeature(feature, True) party_layer.commitChanges() QCoreApplication.processEvents() self.process_attributes(party_layer) Utilities.add_tabular_layer(party_layer, organization_slug, project_slug, attribute) return party_layer def process_attributes(self, layer): """Check questionnaire attributes column on each feature. If json string, then run parse questionnaire. :param layer: Attribute layer :type layer: QgsVectorLayer """ features = layer.getFeatures() for feature in features: attributes = feature.attributes() questionnaire_index = layer.fieldNameIndex('attributes') questionnaire = attributes[questionnaire_index] layer.startEditing() layer.deleteAttribute(questionnaire_index) layer.commitChanges() if not questionnaire: continue try: questionnaire_dict = json.loads(questionnaire) except ValueError, e: continue if questionnaire_dict: self.parse_questionnaire(layer, feature, questionnaire_dict)
class ContactWidget(WidgetBase, FORM_CLASS): """Contact widget.""" def __init__(self, parent=None): """Constructor. :param parent: parent - widget to use as parent. :type parent: QWidget """ super(ContactWidget, self).__init__(parent) # Create model self.model = Contact.table_model() self.set_widgets() def set_widgets(self): """Set all widgets.""" self.contact_listview.horizontalHeader().setStretchLastSection(True) self.contact_listview.horizontalHeader().setResizeMode( QHeaderView.Stretch) self.contact_listview.setModel(self.model) self.contact_listview.hideColumn(0) self.contact_listview.setEditTriggers(QAbstractItemView.DoubleClicked) self.add_button.clicked.connect(self.add_contact) self.delete_button.clicked.connect(self.delete_contact) self.save_button.clicked.connect(self.save_model) def add_contact(self): """Add contact.""" row = self.model.rowCount() self.model.insertRows(row, 1) def delete_contact(self): """Delete contact.""" indexes = self.contact_listview.selectionModel().selection().indexes() for index in indexes: self.model.removeRows(index.row(), 1) def validate_email(self, email): """Delete email. :param email: email that will be checked. :type email: str :return: boolean is validated or not :rtype: bool """ return re.match(r"[^@]+@[^@]+\.[^@]+", email) def save_model(self): """Save model contact.""" error = None for i in xrange(self.model.rowCount()): record = self.model.record(i) if record.value("id") or record.value("name"): is_deleted = True else: is_deleted = False if is_deleted: if not record.value("email") and not record.value("phone"): error = self.tr( 'One or more contact doesn\'t has email and ' 'phone. Either email or phone must be provided.') break # validate email if record.value("email") and not self.validate_email( record.value("email")): error = self.tr( 'There is one or more wrong email in contact ist.') if not error: self.model.submitAll() else: self.message_bar = QgsMessageBar() self.message_bar.pushWarning(self.tr('Error'), error)
class Ili2dbOptionsDialog(QDialog, DIALOG_UI): ValidExtensions = ["toml", "TOML"] SQLValidExtensions = ["sql", "SQL"] def __init__(self, parent=None, remove_create_tid_group=True): """ remove_create_tid_group is to remove the "Create Import Tid" setting because on Schema Import it does nothing (legacy issues). After removing the single dialog for data import, this setting can be removed completely from the GUI. """ QDialog.__init__(self, parent) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.toml_file_key = None self.buttonBox.accepted.disconnect() self.buttonBox.accepted.connect(self.accepted) self.buttonBox.rejected.disconnect() self.buttonBox.rejected.connect(self.rejected) self.toml_file_browse_button.clicked.connect( make_file_selector( self.toml_file_line_edit, title=self.tr("Open Extra Model Information File (*.toml)"), file_filter=self.tr("Extra Model Info File (*.toml *.TOML)"), ) ) self.pre_script_file_browse_button.clicked.connect( make_file_selector( self.pre_script_file_line_edit, title=self.tr("SQL script to run before (*.sql)"), file_filter=self.tr("SQL script to run before (*.sql *.SQL)"), ) ) self.post_script_file_browse_button.clicked.connect( make_file_selector( self.post_script_file_line_edit, title=self.tr("SQL script to run after (*.sql)"), file_filter=self.tr("SQL script to run after (*.sql *.SQL)"), ) ) self.validators = Validators() self.file_validator = FileValidator( pattern=["*." + ext for ext in self.ValidExtensions], allow_empty=True ) self.toml_file_line_edit.setValidator(self.file_validator) self.sql_file_validator = FileValidator( pattern=["*." + ext for ext in self.SQLValidExtensions], allow_empty=True ) self.pre_script_file_line_edit.setValidator(self.sql_file_validator) self.post_script_file_line_edit.setValidator(self.sql_file_validator) self.restore_configuration() self.toml_file_line_edit.textChanged.connect( self.validators.validate_line_edits ) self.toml_file_line_edit.textChanged.emit(self.toml_file_line_edit.text()) self.pre_script_file_line_edit.textChanged.connect( self.validators.validate_line_edits ) self.pre_script_file_line_edit.textChanged.emit( self.pre_script_file_line_edit.text() ) self.post_script_file_line_edit.textChanged.connect( self.validators.validate_line_edits ) self.post_script_file_line_edit.textChanged.emit( self.post_script_file_line_edit.text() ) self.create_import_tid_groupbox.setHidden(remove_create_tid_group) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) def accepted(self): """Save settings before accepting the dialog""" for line_edit in [ self.pre_script_file_line_edit, self.post_script_file_line_edit, self.toml_file_line_edit, ]: if ( line_edit.validator().validate(line_edit.text().strip(), 0)[0] != QValidator.Acceptable ): self.bar.pushWarning( self.tr("Warning"), self.tr( "Please fix the '{}' value before saving the options." ).format( line_edit.objectName() .split("_file_line_edit")[0] .replace("_", " ") ), ) return self.save_configuration() self.done(1) def rejected(self): self.restore_configuration() self.done(1) def create_import_tid(self): return self.create_import_tid_checkbox.isChecked() def create_basket_col(self): return self.create_basket_col_checkbox.isChecked() def inheritance_type(self): if self.smart1_radio_button.isChecked(): return "smart1" else: return "smart2" def set_toml_file_key(self, key_postfix): if key_postfix: self.toml_file_key = "QgisModelBaker/ili2db/tomlfile/" + key_postfix else: self.toml_file_key = None self.restore_configuration() def toml_file(self): return self.toml_file_line_edit.text().strip() def pre_script(self): return self.pre_script_file_line_edit.text().strip() def post_script(self): return self.post_script_file_line_edit.text().strip() def stroke_arcs(self): return self.stroke_arcs_checkbox.isChecked() def save_configuration(self): settings = QSettings() settings.setValue("QgisModelBaker/ili2db/inheritance", self.inheritance_type()) settings.setValue(self.toml_file_key, self.toml_file()) settings.setValue( "QgisModelBaker/ili2db/create_basket_col", self.create_basket_col() ) settings.setValue( "QgisModelBaker/ili2db/create_import_tid", self.create_import_tid() ) settings.setValue("QgisModelBaker/ili2db/stroke_arcs", self.stroke_arcs()) def restore_configuration(self): settings = QSettings() inheritance = settings.value("QgisModelBaker/ili2db/inheritance", "smart2") if inheritance == "smart1": self.smart1_radio_button.setChecked(True) else: self.smart2_radio_button.setChecked(True) create_basket_col = settings.value( "QgisModelBaker/ili2db/create_basket_col", defaultValue=True, type=bool ) create_import_tid = settings.value( "QgisModelBaker/ili2db/create_import_tid", defaultValue=True, type=bool ) stroke_arcs = settings.value( "QgisModelBaker/ili2db/stroke_arcs", defaultValue=True, type=bool ) self.create_basket_col_checkbox.setChecked(create_basket_col) self.create_import_tid_checkbox.setChecked(create_import_tid) self.stroke_arcs_checkbox.setChecked(stroke_arcs) self.toml_file_line_edit.setText(settings.value(self.toml_file_key)) def load_metaconfig(self, metaconfig_ili2db): if "smart1Inheritance" in metaconfig_ili2db: self.smart1_radio_button.setChecked( metaconfig_ili2db.getboolean("smart1Inheritance") ) if "smart2Inheritance" in metaconfig_ili2db: self.smart2_radio_button.setChecked( metaconfig_ili2db.getboolean("smart2Inheritance") ) if "createBasketCol" in metaconfig_ili2db: self.create_basket_col_checkbox.setChecked( metaconfig_ili2db.getboolean("createBasketCol") ) if "importTid" in metaconfig_ili2db: self.create_import_tid_checkbox.setChecked( metaconfig_ili2db.getboolean("importTid") ) if "strokeArcs" in metaconfig_ili2db: self.stroke_arcs_checkbox.setChecked( metaconfig_ili2db.getboolean("strokeArcs") ) self.save_configuration() def load_toml_file_path(self, key_postfix, toml_file_path): self.set_toml_file_key(key_postfix) self.toml_file_line_edit.setText(toml_file_path) self.save_configuration() def load_post_script_path(self, post_script_path): self.post_script_file_line_edit.setText(post_script_path) def load_pre_script_path(self, pre_script_path): self.pre_script_file_line_edit.setText(pre_script_path)
class OptionsWidget(WidgetBase, FORM_CLASS): """Options widget.""" authenticated = pyqtSignal() unauthenticated = pyqtSignal() def __init__(self, parent=None, auth_token=None): """Constructor :param parent: parent - widget to use as parent. :type parent: QWidget """ super(OptionsWidget, self).__init__(parent) self.text_test_connection_button = None self.url = None self.auth_token = auth_token self.login_api = None self.organisation_api = Organization() self.set_widgets() def set_widgets(self): """Set all widgets.""" self.text_test_connection_button = self.test_connection_button.text() self.ok_label.setVisible(False) self.save_button.setEnabled(False) self.test_connection_button.clicked.connect(self.login) self.save_button.clicked.connect(self.save_authtoken) self.clear_button.setText(tr('Clear')) self.clear_button.setEnabled(True) self.clear_button.clicked.connect(self.clear_information) self.url_input.setText(get_url_instance()) # If login information exists if not self.auth_token: self.auth_token = get_authtoken() if self.auth_token: self.test_connection_button.setEnabled(False) self.username_input.setText(get_setting('username')) self.username_input.setReadOnly(True) self.password_input.setReadOnly(True) self.username_input.setStyleSheet( "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " "selection-background-color: '#969292'; }") self.password_input.setStyleSheet( "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " "selection-background-color: '#969292'; }") self.token_status.setText(tr('Auth token is saved.')) else: self.token_status.setText(tr('Auth token is empty.')) def clear_information(self): """Clear login information.""" self.username_input.setReadOnly(False) self.password_input.setReadOnly(False) self.username_input.setStyleSheet("") self.password_input.setStyleSheet("") self.username_input.clear() self.password_input.clear() self.ok_label.clear() delete_authtoken() delete_setting('username') self.test_connection_button.setEnabled(True) self.token_status.setText(tr('Auth token is empty.')) self.unauthenticated.emit() def login(self): """Login function when tools button clicked.""" self.clear_button.setEnabled(False) username = self.username_input.displayText() password = self.password_input.text() self.url = self.url_input.displayText() self.auth_token = None self.save_button.setEnabled(False) self.ok_label.setVisible(False) if not self.url or not username or not password: self.message_bar = QgsMessageBar() self.message_bar.pushWarning( self.tr('Error'), self.tr('URL/Username/password is empty.')) else: self.test_connection_button.setEnabled(False) self.test_connection_button.setText(self.tr('Logging in...')) # call tools API self.login_api = self.call_login_api(self.url, username, password) self.login_api.connect_post(self.login_api.post_data) def call_login_api(self, url, username, password): """Call login api. :param url: platform url :type url: str :param username: username for login :type username: str :param password: password for login :type password: str """ return Login(domain=url, username=username, password=password, on_finished=self.on_finished) def on_finished(self, result): """On finished function when tools request is finished.""" self.ok_label.setVisible(True) self.clear_button.setEnabled(True) if 'auth_token' in result: self.auth_token = result['auth_token'] self.save_button.setEnabled(True) self.ok_label.setText(self.tr('Success')) self.ok_label.setStyleSheet('color:green') else: self.save_button.setEnabled(False) self.ok_label.setText(self.tr('Failed')) self.ok_label.setStyleSheet('color:red') self.test_connection_button.setText(self.text_test_connection_button) self.test_connection_button.setEnabled(True) def save_authtoken(self): """Save received authtoken to setting. Authoken is saved to setting, and close dialog after that. """ if self.auth_token: set_setting('username', self.username_input.displayText()) save_authtoken(self.auth_token) save_url_instance(self.url) self.save_button.setEnabled(False) self.save_organizations() self.username_input.setReadOnly(True) self.password_input.setReadOnly(True) self.username_input.setStyleSheet( "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " "selection-background-color: '#969292'; }") self.password_input.setStyleSheet( "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " "selection-background-color: '#969292'; }") def save_organizations(self): """Save organizations of user. Organization is saved to setting. If it is success, close dialog after that. """ status, results = self.organisation_api. \ organizations_project_filtered() self.clear_button.setEnabled(True) self.save_button.setEnabled(False) if status: organization = [] for result in results: organization.append(result['slug']) save_user_organizations(organization) self.parent.close() else: self.message_bar = QgsMessageBar() self.message_bar.pushWarning( self.tr('Error'), self.tr('Error when getting user permission.')) self.authenticated.emit()
class WizardDialog(QDialog, FORM_CLASS): """Dialog implementation class for Wizard""" def __init__(self, parent=None, iface=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface """ QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle('Cadasta') self.iface = iface self.parent_step = None self.message_bar = None self.steps = [] self.set_logo() self.populate_stacked_widget() self.update_step_label() first_step = self.first_step() first_step.set_widgets() self.go_to_step(first_step) # Set tab order for button self.setTabOrder(self.next_button, self.back_button) self.setTabOrder(self.back_button, self.cancel_button) def last_step(self): """Returns the last step of wizard. This method must be implemented in derived classes. :returns: Last step of wizard. :rtype: WizardStep """ raise NotImplementedError("The current wizard class doesn't implement \ the populate_stacked_widget method") def first_step(self): """Returns the first step of wizard. This method must be implemented in derived classes. :returns: First step of wizard. :rtype: WizardStep """ raise NotImplementedError("The current wizard class doesn't implement \ the populate_stacked_widget method") def populate_stacked_widget(self): """Append widgets to stacked widget. This method must be implemented in derived classes. """ raise NotImplementedError("The current wizard class doesn't implement \ the populate_stacked_widget method") def update_step_label(self): """Update step label with current step / total step.""" current_step = self.stackedWidget.currentIndex() total_step = self.stackedWidget.count() self.label_step.setText('%d/%d' % (current_step + 1, total_step)) def set_subtitle(self, subtitle): """Set subtitle of dialog. :param subtitle: Subtitle string :type subtitle: str """ self.label_subtitle.setText(subtitle) def set_logo(self): """Set logo of dialog.""" filename = logo_element() pixmap = QPixmap(filename) self.label_main_icon.setPixmap(pixmap) # =========================== # NAVIGATION # =========================== def go_to_step(self, step): """Set the stacked widget to the given step, set up the buttons, and run all operations that should start immediately after entering the new step. :param step: The step widget to be moved to. :type step: QWidget """ self.stackedWidget.setCurrentWidget(step) self.update_step_label() # Enable the Back button unless it's not the first step or # last step self.back_button.setEnabled( step not in [self.first_step()] or self.parent_step is not None) # Set Next button label if step == self.last_step(): self.next_button.setText(self.tr('Close')) else: self.next_button.setText(self.tr('Next')) def get_current_step(self): """Return current step of the wizard. :returns: Current step of the wizard. :rtype: WizardStep instance """ return self.stackedWidget.currentWidget() def prepare_the_next_step(self, new_step): """Prepare the next tab. To be implemented in derived classes. :param new_step: New tab to be prepared. :type new_step: WizardStep """ pass def prepare_the_previous_step(self): """Prepare the previous tab. """ self.next_button.setEnabled(True) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_next_button_released(self): """Handle the Next button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ current_step = self.get_current_step() valid_status, message = current_step.validate_step() if not valid_status: self.message_bar = QgsMessageBar() self.message_bar.pushWarning( tr('Error'), message ) LOGGER.info(message) return self.steps.append(current_step) # Determine the new step to be switched new_step = current_step.get_next_step() if new_step is not None: # Prepare the next tab self.prepare_the_next_step(new_step) new_step.set_widgets() else: # Wizard complete self.accept() return self.go_to_step(new_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_back_button_released(self): """Handle the Back button release. .. note:: This is an automatic Qt slot executed when the Back button is released. """ previous_step = self.steps.pop() self.prepare_the_previous_step() self.update_step_label() self.go_to_step(previous_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_cancel_button_released(self): """Handle the Cancel button release. .. note:: This is an automatic Qt slot executed when the Back button is released. """ self.close()