Port some more model designer Python code to c++

This commit is contained in:
Nyall Dawson 2020-03-05 15:11:13 +10:00
parent eefd04cbc1
commit a0e6a374eb
14 changed files with 652 additions and 369 deletions

View File

@ -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();
};

View File

@ -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 *
************************************************************************/

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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 );
}

View File

@ -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;
};

View 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

View 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

View File

@ -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>