mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
Port some more model designer Python code to c++
This commit is contained in:
parent
eefd04cbc1
commit
a0e6a374eb
@ -9,6 +9,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsModelDesignerDialog : QMainWindow
|
||||
{
|
||||
%Docstring
|
||||
@ -26,11 +27,26 @@ Model designer dialog base class
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsModelDesignerDialog( QWidget *parent = 0, Qt::WindowFlags flags = 0 );
|
||||
QgsModelDesignerDialog( QWidget *parent /TransferThis/ = 0, Qt::WindowFlags flags = 0 );
|
||||
|
||||
protected:
|
||||
|
||||
virtual void repaintModel( bool showControls = true ) = 0;
|
||||
virtual QgsProcessingModelAlgorithm *model() = 0;
|
||||
virtual void addAlgorithm( const QString &algorithmId, const QPointF &pos ) = 0;
|
||||
virtual void addInput( const QString &inputId, const QPointF &pos ) = 0;
|
||||
|
||||
QToolBar *toolbar();
|
||||
QAction *actionOpen();
|
||||
QAction *actionSave();
|
||||
QAction *actionSaveAs();
|
||||
QAction *actionSaveInProject();
|
||||
QAction *actionEditHelp();
|
||||
QAction *actionRun();
|
||||
QAction *actionExportImage();
|
||||
|
||||
QgsMessageBar *messageBar();
|
||||
QGraphicsView *view();
|
||||
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/gui/processing/models/qgsmodelgraphicsview.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsModelGraphicsView : QGraphicsView
|
||||
{
|
||||
%Docstring
|
||||
QGraphicsView subclass representing the model designer.
|
||||
|
||||
.. warning::
|
||||
|
||||
Not stable API
|
||||
|
||||
.. versionadded:: 3.14
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsmodelgraphicsview.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsModelGraphicsView( QWidget *parent = 0 );
|
||||
%Docstring
|
||||
Constructor for QgsModelGraphicsView, with the specified ``parent`` widget.
|
||||
%End
|
||||
|
||||
virtual void dragEnterEvent( QDragEnterEvent *event );
|
||||
|
||||
virtual void dropEvent( QDropEvent *event );
|
||||
|
||||
virtual void dragMoveEvent( QDragMoveEvent *event );
|
||||
|
||||
virtual void wheelEvent( QWheelEvent *event );
|
||||
|
||||
virtual void enterEvent( QEvent *event );
|
||||
|
||||
virtual void mousePressEvent( QMouseEvent *event );
|
||||
|
||||
virtual void mouseMoveEvent( QMouseEvent *event );
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
void algorithmDropped( const QString &algorithmId, const QPointF &pos );
|
||||
%Docstring
|
||||
Emitted when an algorithm is dropped onto the view.
|
||||
%End
|
||||
|
||||
void inputDropped( const QString &inputId, const QPointF &pos );
|
||||
%Docstring
|
||||
Emitted when an input parameter is dropped onto the view.
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/gui/processing/models/qgsmodelgraphicsview.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -314,6 +314,7 @@
|
||||
%Include auto_generated/processing/models/qgsmodeldesignerdialog.sip
|
||||
%Include auto_generated/processing/models/qgsmodelgraphicitem.sip
|
||||
%Include auto_generated/processing/models/qgsmodelgraphicsscene.sip
|
||||
%Include auto_generated/processing/models/qgsmodelgraphicsview.sip
|
||||
%Include auto_generated/raster/qgscolorrampshaderwidget.sip
|
||||
%Include auto_generated/raster/qgshillshaderendererwidget.sip
|
||||
%Include auto_generated/raster/qgsmultibandcolorrendererwidget.sip
|
||||
|
@ -122,7 +122,7 @@ class ProcessingModelItem(QgsDataItem):
|
||||
ProcessingDropHandler.runAlg(self.path())
|
||||
|
||||
def editModel(self):
|
||||
dlg = ModelerDialog()
|
||||
dlg = ModelerDialog.create()
|
||||
dlg.loadModel(self.path())
|
||||
dlg.show()
|
||||
|
||||
@ -332,7 +332,7 @@ class ProcessingPlugin:
|
||||
self.toolboxAction.setChecked(visible)
|
||||
|
||||
def openModeler(self):
|
||||
dlg = ModelerDialog()
|
||||
dlg = ModelerDialog.create()
|
||||
dlg.update_model.connect(self.updateModel)
|
||||
dlg.show()
|
||||
|
||||
|
@ -26,6 +26,7 @@ import os
|
||||
from qgis.PyQt.QtCore import QCoreApplication
|
||||
|
||||
from qgis.core import QgsApplication
|
||||
from qgis.utils import iface
|
||||
|
||||
from processing.gui.ToolboxAction import ToolboxAction
|
||||
from processing.modeler.ModelerDialog import ModelerDialog
|
||||
@ -43,7 +44,7 @@ class CreateNewModelAction(ToolboxAction):
|
||||
return QgsApplication.getThemeIcon("/processingModel.svg")
|
||||
|
||||
def execute(self):
|
||||
dlg = ModelerDialog()
|
||||
dlg = ModelerDialog.create()
|
||||
dlg.update_model.connect(self.updateModel)
|
||||
dlg.show()
|
||||
|
||||
|
@ -44,7 +44,7 @@ class EditModelAction(ContextAction):
|
||||
if not ok:
|
||||
iface.messageBar().pushMessage(QCoreApplication.translate('EditModelAction', 'Cannot edit model: {}').format(msg), level=Qgis.Warning)
|
||||
else:
|
||||
dlg = ModelerDialog(alg)
|
||||
dlg = ModelerDialog.create(alg)
|
||||
dlg.update_model.connect(self.updateModel)
|
||||
dlg.show()
|
||||
|
||||
|
@ -21,12 +21,9 @@ __author__ = 'Victor Olaya'
|
||||
__date__ = 'August 2012'
|
||||
__copyright__ = '(C) 2012, Victor Olaya'
|
||||
|
||||
import codecs
|
||||
import sys
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from qgis.PyQt import uic
|
||||
from qgis.PyQt.QtCore import (
|
||||
Qt,
|
||||
QCoreApplication,
|
||||
@ -35,22 +32,14 @@ from qgis.PyQt.QtCore import (
|
||||
QMimeData,
|
||||
QPoint,
|
||||
QPointF,
|
||||
QByteArray,
|
||||
QSize,
|
||||
QSizeF,
|
||||
pyqtSignal,
|
||||
QDataStream,
|
||||
QIODevice,
|
||||
QUrl,
|
||||
QTimer)
|
||||
from qgis.PyQt.QtWidgets import (QGraphicsView,
|
||||
QTreeWidget,
|
||||
QUrl)
|
||||
from qgis.PyQt.QtWidgets import (QTreeWidget,
|
||||
QMessageBox,
|
||||
QFileDialog,
|
||||
QTreeWidgetItem,
|
||||
QSizePolicy,
|
||||
QMainWindow,
|
||||
QShortcut,
|
||||
QLabel,
|
||||
QDockWidget,
|
||||
QWidget,
|
||||
@ -60,12 +49,7 @@ from qgis.PyQt.QtWidgets import (QGraphicsView,
|
||||
QLineEdit,
|
||||
QToolButton,
|
||||
QAction)
|
||||
from qgis.PyQt.QtGui import (QIcon,
|
||||
QImage,
|
||||
QPainter,
|
||||
QKeySequence)
|
||||
from qgis.PyQt.QtSvg import QSvgGenerator
|
||||
from qgis.PyQt.QtPrintSupport import QPrinter
|
||||
from qgis.PyQt.QtGui import QIcon
|
||||
from qgis.core import (Qgis,
|
||||
QgsApplication,
|
||||
QgsProcessing,
|
||||
@ -78,8 +62,7 @@ from qgis.core import (Qgis,
|
||||
QgsExpressionContextScope,
|
||||
QgsExpressionContext
|
||||
)
|
||||
from qgis.gui import (QgsMessageBar,
|
||||
QgsDockWidget,
|
||||
from qgis.gui import (QgsDockWidget,
|
||||
QgsScrollArea,
|
||||
QgsFilterLineEdit,
|
||||
QgsProcessingToolboxTreeView,
|
||||
@ -101,7 +84,6 @@ from processing.core.ProcessingConfig import ProcessingConfig
|
||||
from processing.tools.dataobjects import createContext
|
||||
from qgis.utils import iface
|
||||
|
||||
|
||||
pluginPath = os.path.split(os.path.dirname(__file__))[0]
|
||||
|
||||
|
||||
@ -134,11 +116,24 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
|
||||
update_model = pyqtSignal()
|
||||
|
||||
def __init__(self, model=None):
|
||||
super().__init__(None)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
dlgs = []
|
||||
|
||||
@staticmethod
|
||||
def create(model=None):
|
||||
"""
|
||||
Workaround crappy sip handling of QMainWindow. It doesn't know that we are using the deleteonclose
|
||||
flag, so happily just deletes dialogs as soon as they go out of scope. The only workaround possible
|
||||
while we still have to drag around this Python code is to store a reference to the sip wrapper so that
|
||||
sip doesn't get confused. The underlying object will still be deleted by the deleteonclose flag though!
|
||||
"""
|
||||
dlg = ModelerDialog(model)
|
||||
ModelerDialog.dlgs.append(dlg)
|
||||
return dlg
|
||||
|
||||
def __init__(self, model=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self._variables_scope = None
|
||||
self._model = None
|
||||
|
||||
# LOTS of bug reports when we include the dock creation in the UI file
|
||||
# see e.g. #16428, #19068
|
||||
@ -258,10 +253,6 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
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:
|
||||
@ -273,10 +264,11 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
|
||||
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.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.toolbutton_export_to_script.setDefaultAction(self.export_to_script_algorithm_action)
|
||||
self.toolbar().insertWidget(self.mActionExportImage, self.toolbutton_export_to_script)
|
||||
self.toolbar().insertWidget(self.actionExportImage(), self.toolbutton_export_to_script)
|
||||
self.export_to_script_algorithm_action.triggered.connect(self.export_as_script_algorithm)
|
||||
|
||||
self.addDockWidget(Qt.LeftDockWidgetArea, self.propertiesDock)
|
||||
@ -285,111 +277,14 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
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)
|
||||
self.scene.setSceneRect(QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE))
|
||||
self.scene.rebuildRequired.connect(self.repaintModel)
|
||||
self.scene.componentChanged.connect(self.componentChanged)
|
||||
|
||||
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
|
||||
self.view().setScene(self.scene)
|
||||
self.view().ensureVisible(0, 0, 10, 10)
|
||||
self.view().scale(QgsApplication.desktop().logicalDpiX() / 96, QgsApplication.desktop().logicalDpiX() / 96)
|
||||
|
||||
def _mimeDataInput(items):
|
||||
mimeData = QMimeData()
|
||||
@ -420,54 +315,40 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
self.textGroup.setPlaceholderText(self.tr('Enter group name here'))
|
||||
|
||||
# Connect signals and slots
|
||||
self.inputsTree.doubleClicked.connect(self.addInput)
|
||||
self.inputsTree.doubleClicked.connect(self._addInput)
|
||||
self.searchBox.textChanged.connect(self.algorithmTree.setFilterString)
|
||||
self.algorithmTree.doubleClicked.connect(self.addAlgorithm)
|
||||
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.actionOpen().triggered.connect(self.openModel)
|
||||
self.actionSave().triggered.connect(self.save)
|
||||
self.actionSaveAs().triggered.connect(self.saveAs)
|
||||
self.actionSaveInProject().triggered.connect(self.saveInProject)
|
||||
self.actionEditHelp().triggered.connect(self.editHelp)
|
||||
self.actionRun().triggered.connect(self.runModel)
|
||||
|
||||
self.mActionClose.triggered.connect(self.close)
|
||||
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)
|
||||
# self.mActionShowComments.toggled.connect(self.showComments)
|
||||
# self.mActionShowComments.setChecked(settings.value("/Processing/stateModeler", self.saveState())))
|
||||
|
||||
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._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._model = QgsProcessingModelAlgorithm()
|
||||
self._model.setProvider(QgsApplication.processingRegistry().providerById('model'))
|
||||
self.update_variables_gui()
|
||||
|
||||
self.fillInputsTree()
|
||||
|
||||
self.view.centerOn(0, 0)
|
||||
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?'),
|
||||
@ -484,17 +365,20 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
else:
|
||||
evt.accept()
|
||||
|
||||
def model(self):
|
||||
return self._model
|
||||
|
||||
def editHelp(self):
|
||||
alg = self.model
|
||||
alg = self.model()
|
||||
dlg = HelpEditionDialog(alg)
|
||||
dlg.exec_()
|
||||
if dlg.descriptions:
|
||||
self.model.setHelpContent(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():
|
||||
for k, v in self.model().variables().items():
|
||||
variables_scope.setVariable(k, v)
|
||||
variables_context = QgsExpressionContext()
|
||||
variables_context.appendScope(variables_scope)
|
||||
@ -502,19 +386,21 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
self.variables_editor.setEditableScopeIndex(0)
|
||||
|
||||
def variables_changed(self):
|
||||
self.model.setVariables(self.variables_editor.variablesInActiveScope())
|
||||
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)
|
||||
if len(self.model().childAlgorithms()) == 0:
|
||||
self.messageBar().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.setParameters(self.model.designerParameterValues())
|
||||
dlg = AlgorithmDialog(self.model().create(), parent=self)
|
||||
dlg.setParameters(self.model().designerParameterValues())
|
||||
dlg.exec_()
|
||||
|
||||
if dlg.wasExecuted():
|
||||
self.model.setDesignerParameterValues(dlg.getParameterValues())
|
||||
self.model().setDesignerParameterValues(dlg.getParameterValues())
|
||||
|
||||
def save(self):
|
||||
self.saveModel(False)
|
||||
@ -526,161 +412,27 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
if not self.can_save():
|
||||
return
|
||||
|
||||
self.model.setName(str(self.textName.text()))
|
||||
self.model.setGroup(str(self.textGroup.text()))
|
||||
self.model.setSourceFilePath(None)
|
||||
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)
|
||||
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.messageBar().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.messageBar().pushWarning(
|
||||
"", self.tr('Please a enter model name before saving')
|
||||
)
|
||||
return False
|
||||
@ -690,10 +442,10 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
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()
|
||||
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'),
|
||||
@ -702,23 +454,26 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
if filename:
|
||||
if not filename.endswith('.model3'):
|
||||
filename += '.model3'
|
||||
self.model.setSourceFilePath(filename)
|
||||
self.model().setSourceFilePath(filename)
|
||||
if filename:
|
||||
if not self.model.toFile(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."))
|
||||
)
|
||||
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)
|
||||
self.messageBar().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.messageBar().pushMessage("", self.tr("Model was correctly saved"), level=Qgis.Success, duration=5)
|
||||
|
||||
self.hasChanged = False
|
||||
|
||||
@ -733,15 +488,15 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
def loadModel(self, filename):
|
||||
alg = QgsProcessingModelAlgorithm()
|
||||
if alg.fromFile(filename):
|
||||
self.model = alg
|
||||
self.model.setProvider(QgsApplication.processingRegistry().providerById('model'))
|
||||
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.view().centerOn(0, 0)
|
||||
self.hasChanged = False
|
||||
else:
|
||||
QgsMessageLog.logMessage(self.tr('Could not load model {0}').format(filename),
|
||||
@ -751,17 +506,17 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
self.tr('The selected model could not be loaded.\n'
|
||||
'See the log for more information.'))
|
||||
|
||||
def repaintModel(self, controls=True):
|
||||
def repaintModel(self, showControls=True):
|
||||
self.scene = ModelerScene(self)
|
||||
self.scene.setSceneRect(QRectF(0, 0, self.CANVAS_SIZE,
|
||||
self.CANVAS_SIZE))
|
||||
|
||||
if not controls:
|
||||
if not showControls:
|
||||
self.scene.setFlag(QgsModelGraphicsScene.FlagHideControls)
|
||||
|
||||
context = createContext()
|
||||
self.scene.createItems(self.model, context)
|
||||
self.view.setScene(self.scene)
|
||||
self.scene.createItems(self.model(), context)
|
||||
self.view().setScene(self.scene)
|
||||
|
||||
self.scene.rebuildRequired.connect(self.repaintModel)
|
||||
self.scene.componentChanged.connect(self.componentChanged)
|
||||
@ -769,10 +524,10 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
def componentChanged(self):
|
||||
self.hasChanged = True
|
||||
|
||||
def addInput(self):
|
||||
def _addInput(self):
|
||||
item = self.inputsTree.currentItem()
|
||||
param = item.data(0, Qt.UserRole)
|
||||
self.addInputOfType(param)
|
||||
self.addInput(param)
|
||||
|
||||
def create_widget_context(self):
|
||||
"""
|
||||
@ -782,7 +537,7 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
widget_context.setProject(QgsProject.instance())
|
||||
if iface is not None:
|
||||
widget_context.setMapCanvas(iface.mapCanvas())
|
||||
widget_context.setModel(self.model)
|
||||
widget_context.setModel(self.model())
|
||||
return widget_context
|
||||
|
||||
def autogenerate_parameter_name(self, parameter):
|
||||
@ -795,16 +550,19 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
safeName = ''.join(c for c in parameter.description() if c in validChars)
|
||||
name = safeName.lower()
|
||||
i = 2
|
||||
while self.model.parameterDefinition(name):
|
||||
while self.model().parameterDefinition(name):
|
||||
name = safeName.lower() + str(i)
|
||||
i += 1
|
||||
parameter.setName(safeName)
|
||||
|
||||
def addInputOfType(self, paramType, pos=None):
|
||||
def addInput(self, paramType, pos=None):
|
||||
if paramType not in [param.id() for param in QgsApplication.instance().processingRegistry().parameterTypes()]:
|
||||
return
|
||||
|
||||
new_param = None
|
||||
comment = None
|
||||
if ModelerParameterDefinitionDialog.use_legacy_dialog(paramType=paramType):
|
||||
dlg = ModelerParameterDefinitionDialog(self.model, paramType)
|
||||
dlg = ModelerParameterDefinitionDialog(self.model(), paramType)
|
||||
if dlg.exec_():
|
||||
new_param = dlg.param
|
||||
comment = dlg.comments()
|
||||
@ -815,7 +573,7 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
dlg = QgsProcessingParameterDefinitionDialog(type=paramType,
|
||||
context=context,
|
||||
widgetContext=widget_context,
|
||||
algorithm=self.model)
|
||||
algorithm=self.model())
|
||||
if dlg.exec_():
|
||||
new_param = dlg.createParameter()
|
||||
self.autogenerate_parameter_name(new_param)
|
||||
@ -835,17 +593,17 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
component.size().width(),
|
||||
-1.5 * component.size().height()))
|
||||
|
||||
self.model.addModelParameter(new_param, component)
|
||||
self.model().addModelParameter(new_param, component)
|
||||
self.repaintModel()
|
||||
# self.view.ensureVisible(self.scene.getLastParameterItem())
|
||||
# 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())])
|
||||
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
|
||||
@ -868,14 +626,15 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
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):
|
||||
self.addAlgorithm(self.algorithmTree.selectedAlgorithm())
|
||||
|
||||
def _addAlgorithm(self, alg, pos=None):
|
||||
dlg = ModelerParametersDialog(alg, self.model)
|
||||
def addAlgorithm(self, alg_id, pos=None):
|
||||
alg = QgsApplication.processingRegistry().createAlgorithmById(alg_id)
|
||||
if not alg:
|
||||
return
|
||||
|
||||
dlg = ModelerParametersDialog(alg, self.model())
|
||||
if dlg.exec_():
|
||||
alg = dlg.createAlgorithm()
|
||||
if pos is None:
|
||||
@ -893,7 +652,7 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
alg.modelOutput(out).setPosition(alg.position() + QPointF(output_offset_x, output_offset_y))
|
||||
output_offset_y += 1.5 * alg.modelOutput(out).size().height()
|
||||
|
||||
self.model.addChildAlgorithm(alg)
|
||||
self.model().addChildAlgorithm(alg)
|
||||
self.repaintModel()
|
||||
self.hasChanged = True
|
||||
|
||||
@ -901,12 +660,12 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
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())])
|
||||
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)
|
||||
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
|
||||
@ -915,5 +674,5 @@ class ModelerDialog(QgsModelDesignerDialog):
|
||||
def export_as_script_algorithm(self):
|
||||
dlg = ScriptEditorDialog(None)
|
||||
|
||||
dlg.editor.setText('\n'.join(self.model.asPythonCode(QgsProcessing.PythonQgsProcessingAlgorithmSubclass, 4)))
|
||||
dlg.editor.setText('\n'.join(self.model().asPythonCode(QgsProcessing.PythonQgsProcessingAlgorithmSubclass, 4)))
|
||||
dlg.show()
|
||||
|
@ -26,7 +26,7 @@ from qgis.PyQt.QtWidgets import QFileDialog
|
||||
from qgis.PyQt.QtCore import QFileInfo, QCoreApplication
|
||||
|
||||
from qgis.core import QgsApplication, QgsSettings
|
||||
|
||||
from qgis.utils import iface
|
||||
from processing.gui.ToolboxAction import ToolboxAction
|
||||
from processing.modeler.ModelerDialog import ModelerDialog
|
||||
|
||||
@ -52,6 +52,6 @@ class OpenModelFromFileAction(ToolboxAction):
|
||||
settings.setValue('Processing/lastModelsDir',
|
||||
QFileInfo(filename).absoluteDir().absolutePath())
|
||||
|
||||
dlg = ModelerDialog()
|
||||
dlg = ModelerDialog.create()
|
||||
dlg.loadModel(filename)
|
||||
dlg.show()
|
||||
|
@ -258,6 +258,7 @@ SET(QGIS_GUI_SRCS
|
||||
processing/models/qgsmodeldesignerdialog.cpp
|
||||
processing/models/qgsmodelgraphicitem.cpp
|
||||
processing/models/qgsmodelgraphicsscene.cpp
|
||||
processing/models/qgsmodelgraphicsview.cpp
|
||||
|
||||
providers/gdal/qgsgdalsourceselect.cpp
|
||||
providers/gdal/qgsgdalguiprovider.cpp
|
||||
@ -890,6 +891,7 @@ SET(QGIS_GUI_HDRS
|
||||
processing/models/qgsmodeldesignerdialog.h
|
||||
processing/models/qgsmodelgraphicitem.h
|
||||
processing/models/qgsmodelgraphicsscene.h
|
||||
processing/models/qgsmodelgraphicsview.h
|
||||
|
||||
providers/gdal/qgsgdalguiprovider.h
|
||||
providers/gdal/qgsgdalsourceselect.h
|
||||
|
@ -14,6 +14,21 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsmodeldesignerdialog.h"
|
||||
#include "qgssettings.h"
|
||||
#include "qgsapplication.h"
|
||||
#include "qgsfileutils.h"
|
||||
#include "qgsmessagebar.h"
|
||||
#include "qgsprocessingmodelalgorithm.h"
|
||||
#include "qgsprocessingregistry.h"
|
||||
#include "qgsprocessingalgorithm.h"
|
||||
#include "qgsgui.h"
|
||||
|
||||
#include <QShortcut>
|
||||
#include <QDesktopWidget>
|
||||
#include <QKeySequence>
|
||||
#include <QFileDialog>
|
||||
#include <QPrinter>
|
||||
#include <QSvgGenerator>
|
||||
|
||||
///@cond NOT_STABLE
|
||||
|
||||
@ -21,6 +36,181 @@ QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags
|
||||
: QMainWindow( parent, flags )
|
||||
{
|
||||
setupUi( this );
|
||||
QgsGui::enableAutoGeometryRestore( this );
|
||||
|
||||
setAttribute( Qt::WA_DeleteOnClose );
|
||||
setWindowFlags( Qt::WindowMinimizeButtonHint |
|
||||
Qt::WindowMaximizeButtonHint |
|
||||
Qt::WindowCloseButtonHint );
|
||||
mMessageBar = new QgsMessageBar();
|
||||
mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
|
||||
mainLayout->insertWidget( 0, mMessageBar );
|
||||
|
||||
mView->setAcceptDrops( true );
|
||||
|
||||
connect( mActionClose, &QAction::triggered, this, &QWidget::close );
|
||||
connect( mActionZoomIn, &QAction::triggered, this, &QgsModelDesignerDialog::zoomIn );
|
||||
connect( mActionZoomOut, &QAction::triggered, this, &QgsModelDesignerDialog::zoomOut );
|
||||
connect( mActionZoomActual, &QAction::triggered, this, &QgsModelDesignerDialog::zoomActual );
|
||||
connect( mActionZoomToItems, &QAction::triggered, this, &QgsModelDesignerDialog::zoomFull );
|
||||
connect( mActionExportImage, &QAction::triggered, this, &QgsModelDesignerDialog::exportToImage );
|
||||
connect( mActionExportPdf, &QAction::triggered, this, &QgsModelDesignerDialog::exportToPdf );
|
||||
connect( mActionExportSvg, &QAction::triggered, this, &QgsModelDesignerDialog::exportToSvg );
|
||||
connect( mActionExportPython, &QAction::triggered, this, &QgsModelDesignerDialog::exportAsPython );
|
||||
|
||||
connect( mView, &QgsModelGraphicsView::algorithmDropped, this, [ = ]( const QString & algorithmId, const QPointF & pos )
|
||||
{
|
||||
addAlgorithm( algorithmId, pos );
|
||||
} );
|
||||
connect( mView, &QgsModelGraphicsView::inputDropped, this, &QgsModelDesignerDialog::addInput );
|
||||
|
||||
// Ctrl+= should also trigger a zoom in action
|
||||
QShortcut *ctrlEquals = new QShortcut( QKeySequence( QStringLiteral( "Ctrl+=" ) ), this );
|
||||
connect( ctrlEquals, &QShortcut::activated, this, &QgsModelDesignerDialog::zoomIn );
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::zoomIn()
|
||||
{
|
||||
mView->setTransformationAnchor( QGraphicsView::NoAnchor );
|
||||
QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
|
||||
QgsSettings settings;
|
||||
const double factor = settings.value( QStringLiteral( "/qgis/zoom_favor" ), 2.0 ).toDouble();
|
||||
mView->scale( factor, factor );
|
||||
mView->centerOn( point );
|
||||
repaintModel();
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::zoomOut()
|
||||
{
|
||||
mView->setTransformationAnchor( QGraphicsView::NoAnchor );
|
||||
QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
|
||||
QgsSettings settings;
|
||||
const double factor = 1.0 / settings.value( QStringLiteral( "/qgis/zoom_favor" ), 2.0 ).toDouble();
|
||||
mView->scale( factor, factor );
|
||||
mView->centerOn( point );
|
||||
repaintModel();
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::zoomActual()
|
||||
{
|
||||
QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
|
||||
mView->resetTransform();
|
||||
mView->scale( QgsApplication::desktop()->logicalDpiX() / 96, QgsApplication::desktop()->logicalDpiX() / 96 );
|
||||
mView->centerOn( point );
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::zoomFull()
|
||||
{
|
||||
QRectF totalRect = mView->scene()->itemsBoundingRect();
|
||||
totalRect.adjust( -10, -10, 10, 10 );
|
||||
mView->fitInView( totalRect, Qt::KeepAspectRatio );
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::exportToImage()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Image" ), tr( "PNG files (*.png *.PNG)" ) );
|
||||
if ( filename.isEmpty() )
|
||||
return;
|
||||
|
||||
filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "png" ) );
|
||||
|
||||
repaintModel( false );
|
||||
|
||||
QRectF totalRect = mView->scene()->itemsBoundingRect();
|
||||
totalRect.adjust( -10, -10, 10, 10 );
|
||||
const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
|
||||
|
||||
QImage img( totalRect.width(), totalRect.height(),
|
||||
QImage::Format_ARGB32_Premultiplied );
|
||||
img.fill( Qt::white );
|
||||
QPainter painter;
|
||||
painter.setRenderHint( QPainter::Antialiasing );
|
||||
painter.begin( &img );
|
||||
mView->scene()->render( &painter, imageRect, totalRect );
|
||||
painter.end();
|
||||
|
||||
img.save( filename );
|
||||
|
||||
mMessageBar->pushMessage( QString(), tr( "Successfully exported model as image to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::Success, 5 );
|
||||
repaintModel( true );
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::exportToPdf()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as PDF" ), tr( "PDF files (*.pdf *.PDF)" ) );
|
||||
if ( filename.isEmpty() )
|
||||
return;
|
||||
|
||||
filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "pdf" ) );
|
||||
|
||||
repaintModel( false );
|
||||
|
||||
QRectF totalRect = mView->scene()->itemsBoundingRect();
|
||||
totalRect.adjust( -10, -10, 10, 10 );
|
||||
const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
|
||||
|
||||
QPrinter printer;
|
||||
printer.setOutputFormat( QPrinter::PdfFormat );
|
||||
printer.setOutputFileName( filename );
|
||||
printer.setPaperSize( QSizeF( printerRect.width(), printerRect.height() ), QPrinter::DevicePixel );
|
||||
printer.setFullPage( true );
|
||||
|
||||
QPainter painter( &printer );
|
||||
mView->scene()->render( &painter, printerRect, totalRect );
|
||||
painter.end();
|
||||
|
||||
mMessageBar->pushMessage( QString(), tr( "Successfully exported model as PDF to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::Success, 5 );
|
||||
repaintModel( true );
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::exportToSvg()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as SVG" ), tr( "SVG files (*.svg *.SVG)" ) );
|
||||
if ( filename.isEmpty() )
|
||||
return;
|
||||
|
||||
filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "svg" ) );
|
||||
|
||||
repaintModel( false );
|
||||
|
||||
QRectF totalRect = mView->scene()->itemsBoundingRect();
|
||||
totalRect.adjust( -10, -10, 10, 10 );
|
||||
const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
|
||||
|
||||
QSvgGenerator svg;
|
||||
svg.setFileName( filename );
|
||||
svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
|
||||
svg.setViewBox( svgRect );
|
||||
svg.setTitle( model()->displayName() );
|
||||
|
||||
QPainter painter( &svg );
|
||||
mView->scene()->render( &painter, svgRect, totalRect );
|
||||
painter.end();
|
||||
|
||||
mMessageBar->pushMessage( QString(), tr( "Successfully exported model as SVG to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::Success, 5 );
|
||||
repaintModel( true );
|
||||
}
|
||||
|
||||
void QgsModelDesignerDialog::exportAsPython()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Python Script" ), tr( "Processing scripts (*.py *.PY)" ) );
|
||||
if ( filename.isEmpty() )
|
||||
return;
|
||||
|
||||
filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "py" ) );
|
||||
|
||||
const QString text = model()->asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, 4 ).join( '\n' );
|
||||
|
||||
QFile outFile( filename );
|
||||
if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
QTextStream fout( &outFile );
|
||||
fout << text;
|
||||
outFile.close();
|
||||
|
||||
mMessageBar->pushMessage( QString(), tr( "Successfully exported model as Python script to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::Success, 5 );
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,9 @@
|
||||
#include "qgis_gui.h"
|
||||
#include "ui_qgsmodeldesignerdialogbase.h"
|
||||
|
||||
class QgsMessageBar;
|
||||
class QgsProcessingModelAlgorithm;
|
||||
|
||||
///@cond NOT_STABLE
|
||||
|
||||
/**
|
||||
@ -32,11 +35,41 @@ class GUI_EXPORT QgsModelDesignerDialog : public QMainWindow, public Ui::QgsMode
|
||||
{
|
||||
public:
|
||||
|
||||
QgsModelDesignerDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = nullptr );
|
||||
QgsModelDesignerDialog( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags flags = nullptr );
|
||||
|
||||
protected:
|
||||
|
||||
virtual void repaintModel( bool showControls = true ) = 0;
|
||||
virtual QgsProcessingModelAlgorithm *model() = 0;
|
||||
virtual void addAlgorithm( const QString &algorithmId, const QPointF &pos ) = 0;
|
||||
virtual void addInput( const QString &inputId, const QPointF &pos ) = 0;
|
||||
|
||||
QToolBar *toolbar() { return mToolbar; }
|
||||
QAction *actionOpen() { return mActionOpen; }
|
||||
QAction *actionSave() { return mActionSave; }
|
||||
QAction *actionSaveAs() { return mActionSaveAs; }
|
||||
QAction *actionSaveInProject() { return mActionSaveInProject; }
|
||||
QAction *actionEditHelp() { return mActionEditHelp; }
|
||||
QAction *actionRun() { return mActionRun; }
|
||||
QAction *actionExportImage() { return mActionExportImage; }
|
||||
|
||||
QgsMessageBar *messageBar() { return mMessageBar; }
|
||||
QGraphicsView *view() { return mView; }
|
||||
|
||||
private slots:
|
||||
|
||||
void zoomIn();
|
||||
void zoomOut();
|
||||
void zoomActual();
|
||||
void zoomFull();
|
||||
void exportToImage();
|
||||
void exportToPdf();
|
||||
void exportToSvg();
|
||||
void exportAsPython();
|
||||
|
||||
private:
|
||||
|
||||
QgsMessageBar *mMessageBar = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
135
src/gui/processing/models/qgsmodelgraphicsview.cpp
Normal file
135
src/gui/processing/models/qgsmodelgraphicsview.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
/***************************************************************************
|
||||
qgsmodelgraphicsview.cpp
|
||||
----------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 Nyall Dawson
|
||||
Email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsmodelgraphicsview.h"
|
||||
#include "qgssettings.h"
|
||||
|
||||
#include <QDragEnterEvent>
|
||||
#include <QScrollBar>
|
||||
|
||||
///@cond NOT_STABLE
|
||||
|
||||
QgsModelGraphicsView::QgsModelGraphicsView( QWidget *parent )
|
||||
: QGraphicsView( parent )
|
||||
{
|
||||
setAcceptDrops( true );
|
||||
setDragMode( QGraphicsView::ScrollHandDrag );
|
||||
}
|
||||
|
||||
void QgsModelGraphicsView::dragEnterEvent( QDragEnterEvent *event )
|
||||
{
|
||||
if ( event->mimeData()->hasText() || event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.algorithmid" ) ) )
|
||||
event->acceptProposedAction();
|
||||
else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void QgsModelGraphicsView::dropEvent( QDropEvent *event )
|
||||
{
|
||||
const QPointF dropPoint = mapToScene( event->pos() );
|
||||
if ( event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.algorithmid" ) ) )
|
||||
{
|
||||
QByteArray data = event->mimeData()->data( QStringLiteral( "application/x-vnd.qgis.qgis.algorithmid" ) );
|
||||
QDataStream stream( &data, QIODevice::ReadOnly );
|
||||
QString algorithmId;
|
||||
stream >> algorithmId;
|
||||
|
||||
QTimer::singleShot( 0, this, [this, dropPoint, algorithmId ]
|
||||
{
|
||||
emit algorithmDropped( algorithmId, dropPoint );
|
||||
} );
|
||||
event->accept();
|
||||
}
|
||||
else if ( event->mimeData()->hasText() )
|
||||
{
|
||||
const QString itemId = event->mimeData()->text();
|
||||
QTimer::singleShot( 0, this, [this, dropPoint, itemId ]
|
||||
{
|
||||
emit inputDropped( itemId, dropPoint );
|
||||
} );
|
||||
event->accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void QgsModelGraphicsView::dragMoveEvent( QDragMoveEvent *event )
|
||||
{
|
||||
if ( event->mimeData()->hasText() || event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.algorithmid" ) ) )
|
||||
event->acceptProposedAction();
|
||||
else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void QgsModelGraphicsView::wheelEvent( QWheelEvent *event )
|
||||
{
|
||||
setTransformationAnchor( QGraphicsView::AnchorUnderMouse );
|
||||
|
||||
//get mouse wheel zoom behavior settings
|
||||
QgsSettings settings;
|
||||
double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
|
||||
|
||||
// "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
|
||||
zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( event->angleDelta().y() );
|
||||
|
||||
if ( event->modifiers() & Qt::ControlModifier )
|
||||
{
|
||||
//holding ctrl while wheel zooming results in a finer zoom
|
||||
zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
|
||||
}
|
||||
|
||||
//calculate zoom scale factor
|
||||
bool zoomIn = event->angleDelta().y() > 0;
|
||||
double scaleFactor = ( zoomIn ? 1 / zoomFactor : zoomFactor );
|
||||
|
||||
scale( scaleFactor, scaleFactor );
|
||||
}
|
||||
|
||||
void QgsModelGraphicsView::enterEvent( QEvent *event )
|
||||
{
|
||||
QGraphicsView::enterEvent( event );
|
||||
viewport()->setCursor( Qt::ArrowCursor );
|
||||
}
|
||||
|
||||
void QgsModelGraphicsView::mousePressEvent( QMouseEvent *event )
|
||||
{
|
||||
if ( event->button() == Qt::MidButton )
|
||||
mPreviousMousePos = event->pos();
|
||||
else
|
||||
QGraphicsView::mousePressEvent( event );
|
||||
}
|
||||
|
||||
void QgsModelGraphicsView::mouseMoveEvent( QMouseEvent *event )
|
||||
{
|
||||
if ( event->buttons() == Qt::MidButton )
|
||||
{
|
||||
const QPoint offset = mPreviousMousePos - event->pos();
|
||||
mPreviousMousePos = event->pos();
|
||||
|
||||
verticalScrollBar()->setValue( verticalScrollBar()->value() + offset.y() );
|
||||
horizontalScrollBar()->setValue( horizontalScrollBar()->value() + offset.x() );
|
||||
}
|
||||
else
|
||||
{
|
||||
QGraphicsView::mouseMoveEvent( event );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///@endcond
|
||||
|
||||
|
70
src/gui/processing/models/qgsmodelgraphicsview.h
Normal file
70
src/gui/processing/models/qgsmodelgraphicsview.h
Normal file
@ -0,0 +1,70 @@
|
||||
/***************************************************************************
|
||||
qgsmodelgraphicsview.h
|
||||
-----------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 Nyall Dawson
|
||||
Email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSMODELGRAPHICVIEW_H
|
||||
#define QGSMODELGRAPHICVIEW_H
|
||||
|
||||
#include "qgis.h"
|
||||
#include "qgis_gui.h"
|
||||
#include "qgsprocessingcontext.h"
|
||||
#include <QGraphicsView>
|
||||
|
||||
///@cond NOT_STABLE
|
||||
|
||||
/**
|
||||
* \ingroup gui
|
||||
* \brief QGraphicsView subclass representing the model designer.
|
||||
* \warning Not stable API
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class GUI_EXPORT QgsModelGraphicsView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsModelGraphicsView, with the specified \a parent widget.
|
||||
*/
|
||||
QgsModelGraphicsView( QWidget *parent = nullptr );
|
||||
|
||||
void dragEnterEvent( QDragEnterEvent *event ) override;
|
||||
void dropEvent( QDropEvent *event ) override;
|
||||
void dragMoveEvent( QDragMoveEvent *event ) override;
|
||||
void wheelEvent( QWheelEvent *event ) override;
|
||||
void enterEvent( QEvent *event ) override;
|
||||
void mousePressEvent( QMouseEvent *event ) override;
|
||||
void mouseMoveEvent( QMouseEvent *event ) override;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
* Emitted when an algorithm is dropped onto the view.
|
||||
*/
|
||||
void algorithmDropped( const QString &algorithmId, const QPointF &pos );
|
||||
|
||||
/**
|
||||
* Emitted when an input parameter is dropped onto the view.
|
||||
*/
|
||||
void inputDropped( const QString &inputId, const QPointF &pos );
|
||||
|
||||
private:
|
||||
QPoint mPreviousMousePos;
|
||||
|
||||
};
|
||||
|
||||
///@endcond
|
||||
|
||||
#endif // QGSMODELGRAPHICVIEW_H
|
@ -14,7 +14,7 @@
|
||||
<string>Graphical Modeler</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
@ -31,7 +31,7 @@
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGraphicsView" name="view"/>
|
||||
<widget class="QgsModelGraphicsView" name="mView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -279,8 +279,8 @@
|
||||
</action>
|
||||
<action name="mActionRun">
|
||||
<property name="icon">
|
||||
<iconset resource="../../plugins/georeferencer/georeferencer.qrc">
|
||||
<normaloff>:/icons/default/mActionStartGeoref.png</normaloff>:/icons/default/mActionStartGeoref.png</iconset>
|
||||
<iconset resource="../../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionStart.svg</normaloff>:/images/themes/default/mActionStart.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run Model…</string>
|
||||
@ -318,9 +318,15 @@
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QgsModelGraphicsView</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>qgsmodelgraphicsview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../images/images.qrc"/>
|
||||
<include location="../../plugins/georeferencer/georeferencer.qrc"/>
|
||||
<include location="../../../images/images.qrc"/>
|
||||
<include location="../../../images/images.qrc"/>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user