17#include "moc_qgsmodeldesignerdialog.cpp"
45#include <QKeySequence>
48#include <QSvgGenerator>
56#include <QActionGroup>
61QgsModelerToolboxModel::QgsModelerToolboxModel( QObject *parent )
66Qt::ItemFlags QgsModelerToolboxModel::flags(
const QModelIndex &index )
const
68 Qt::ItemFlags f = QgsProcessingToolboxProxyModel::flags( index );
69 const QModelIndex sourceIndex = mapToSource( index );
70 if ( toolboxModel()->isAlgorithm( sourceIndex ) || toolboxModel()->isParameter( sourceIndex ) )
72 f = f | Qt::ItemIsDragEnabled;
77Qt::DropActions QgsModelerToolboxModel::supportedDragActions()
const
79 return Qt::CopyAction;
82QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
83 : QMainWindow( parent, flags )
84 , mToolsActionGroup( new QActionGroup( this ) )
92 setAttribute( Qt::WA_DeleteOnClose );
93 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
94 setWindowFlags( Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint );
98 mModel = std::make_unique<QgsProcessingModelAlgorithm>();
101 mUndoStack =
new QUndoStack(
this );
102 connect( mUndoStack, &QUndoStack::indexChanged,
this, [=] {
103 if ( mIgnoreUndoStackChanges )
106 mBlockUndoCommands++;
107 updateVariablesGui();
108 mGroupEdit->setText( mModel->group() );
109 mNameEdit->setText( mModel->displayName() );
110 mBlockUndoCommands--;
114 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
115 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
116 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
117 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
119 mToolboxTree->header()->setVisible(
false );
120 mToolboxSearchEdit->setShowSearchIcon(
true );
121 mToolboxSearchEdit->setPlaceholderText( tr(
"Search…" ) );
122 connect( mToolboxSearchEdit, &QgsFilterLineEdit::textChanged, mToolboxTree, &QgsProcessingToolboxTreeView::setFilterString );
124 mInputsTreeWidget->header()->setVisible(
false );
125 mInputsTreeWidget->setAlternatingRowColors(
true );
126 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
127 mInputsTreeWidget->setDropIndicatorShown(
true );
129 mNameEdit->setPlaceholderText( tr(
"Enter model name here" ) );
130 mGroupEdit->setPlaceholderText( tr(
"Enter group name here" ) );
133 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
134 mainLayout->insertWidget( 0, mMessageBar );
136 mView->setAcceptDrops(
true );
139 connect( mActionClose, &QAction::triggered,
this, &QWidget::close );
140 connect( mActionNew, &QAction::triggered,
this, &QgsModelDesignerDialog::newModel );
141 connect( mActionZoomIn, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomIn );
142 connect( mActionZoomOut, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomOut );
143 connect( mActionZoomActual, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomActual );
144 connect( mActionZoomToItems, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomFull );
145 connect( mActionExportImage, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToImage );
146 connect( mActionExportPdf, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToPdf );
147 connect( mActionExportSvg, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToSvg );
148 connect( mActionExportPython, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsPython );
149 connect( mActionSave, &QAction::triggered,
this, [=] { saveModel(
false ); } );
150 connect( mActionSaveAs, &QAction::triggered,
this, [=] { saveModel(
true ); } );
151 connect( mActionDeleteComponents, &QAction::triggered,
this, &QgsModelDesignerDialog::deleteSelected );
152 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
153 connect( mActionValidate, &QAction::triggered,
this, &QgsModelDesignerDialog::validate );
154 connect( mActionReorderInputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderInputs );
155 connect( mActionReorderOutputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderOutputs );
156 connect( mActionEditHelp, &QAction::triggered,
this, &QgsModelDesignerDialog::editHelp );
157 connect( mReorderInputsButton, &QPushButton::clicked,
this, &QgsModelDesignerDialog::reorderInputs );
158 connect( mActionRun, &QAction::triggered,
this, [
this] { run(); } );
159 connect( mActionRunSelectedSteps, &QAction::triggered,
this, &QgsModelDesignerDialog::runSelectedSteps );
161 mActionSnappingEnabled->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ),
false ).toBool() );
162 connect( mActionSnappingEnabled, &QAction::toggled,
this, [=](
bool enabled ) {
163 mView->snapper()->setSnapToGrid( enabled );
164 QgsSettings().
setValue( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ), enabled );
166 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
168 connect( mActionSelectAll, &QAction::triggered,
this, [=] {
172 QStringList docksTitle = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), QStringList(),
QgsSettings::App ).toStringList();
173 QStringList docksActive = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), QStringList(),
QgsSettings::App ).toStringList();
174 if ( !docksTitle.isEmpty() )
176 for (
const auto &title : docksTitle )
178 mPanelStatus.insert( title, PanelStatus(
true, docksActive.contains( title ) ) );
181 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
182 connect( mActionHidePanels, &QAction::toggled,
this, &QgsModelDesignerDialog::setPanelVisibility );
184 mUndoAction = mUndoStack->createUndoAction(
this );
186 mUndoAction->setShortcuts( QKeySequence::Undo );
187 mRedoAction = mUndoStack->createRedoAction(
this );
189 mRedoAction->setShortcuts( QKeySequence::Redo );
191 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
192 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
193 mMenuEdit->insertSeparator( mActionDeleteComponents );
194 mToolbar->insertAction( mActionZoomIn, mUndoAction );
195 mToolbar->insertAction( mActionZoomIn, mRedoAction );
196 mToolbar->insertSeparator( mActionZoomIn );
198 mGroupMenu =
new QMenu( tr(
"Zoom To" ),
this );
199 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
200 connect( mGroupMenu, &QMenu::aboutToShow,
this, &QgsModelDesignerDialog::populateZoomToMenu );
204 mActionCut =
new QAction( tr(
"Cu&t" ),
this );
205 mActionCut->setShortcuts( QKeySequence::Cut );
206 mActionCut->setStatusTip( tr(
"Cut" ) );
208 connect( mActionCut, &QAction::triggered,
this, [=] {
209 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut );
212 mActionCopy =
new QAction( tr(
"&Copy" ),
this );
213 mActionCopy->setShortcuts( QKeySequence::Copy );
214 mActionCopy->setStatusTip( tr(
"Copy" ) );
216 connect( mActionCopy, &QAction::triggered,
this, [=] {
217 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy );
220 mActionPaste =
new QAction( tr(
"&Paste" ),
this );
221 mActionPaste->setShortcuts( QKeySequence::Paste );
222 mActionPaste->setStatusTip( tr(
"Paste" ) );
224 connect( mActionPaste, &QAction::triggered,
this, [=] {
225 mView->pasteItems( QgsModelGraphicsView::PasteModeCursor );
227 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
228 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
229 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
230 mMenuEdit->insertSeparator( mActionDeleteComponents );
232 mAlgorithmsModel =
new QgsModelerToolboxModel(
this );
233 mToolboxTree->setToolboxProxyModel( mAlgorithmsModel );
236 if ( settings.
value( QStringLiteral(
"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES" ),
false ).toBool() )
240 mToolboxTree->setFilters( filters );
241 mToolboxTree->setDragDropMode( QTreeWidget::DragOnly );
242 mToolboxTree->setDropIndicatorShown(
true );
244 connect( mView, &QgsModelGraphicsView::algorithmDropped,
this, [=](
const QString &algorithmId,
const QPointF &pos ) {
245 addAlgorithm( algorithmId, pos );
247 connect( mView, &QgsModelGraphicsView::inputDropped,
this, &QgsModelDesignerDialog::addInput );
249 connect( mToolboxTree, &QgsProcessingToolboxTreeView::doubleClicked,
this, [=](
const QModelIndex & ) {
250 if ( mToolboxTree->selectedAlgorithm() )
251 addAlgorithm( mToolboxTree->selectedAlgorithm()->id(), QPointF() );
252 if ( mToolboxTree->selectedParameterType() )
253 addInput( mToolboxTree->selectedParameterType()->id(), QPointF() );
256 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked,
this, [=](
const QModelIndex & ) {
257 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
258 addInput( parameterType, QPointF() );
262 QShortcut *ctrlEquals =
new QShortcut( QKeySequence( QStringLiteral(
"Ctrl+=" ) ),
this );
263 connect( ctrlEquals, &QShortcut::activated,
this, &QgsModelDesignerDialog::zoomIn );
266 mUndoDock->setObjectName( QStringLiteral(
"UndoDock" ) );
267 mUndoView =
new QUndoView( mUndoStack,
this );
268 mUndoDock->setWidget( mUndoView );
269 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
270 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
272 tabifyDockWidget( mUndoDock, mPropertiesDock );
273 tabifyDockWidget( mVariablesDock, mPropertiesDock );
274 mPropertiesDock->raise();
275 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
276 mInputsDock->raise();
281 beginUndoCommand( tr(
"Change Model Variables" ) );
282 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
286 connect( mNameEdit, &QLineEdit::textChanged,
this, [=](
const QString &name ) {
289 beginUndoCommand( tr(
"Change Model Name" ), NameChanged );
290 mModel->setName( name );
295 connect( mGroupEdit, &QLineEdit::textChanged,
this, [=](
const QString &group ) {
298 beginUndoCommand( tr(
"Change Model Group" ), GroupChanged );
299 mModel->setGroup( group );
307 QToolButton *toolbuttonExportToScript =
new QToolButton();
308 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
309 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
310 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
311 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
312 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
314 mActionShowComments->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/ShowComments" ),
true ).toBool() );
315 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
318 mPanTool->setAction( mActionPan );
320 mToolsActionGroup->addAction( mActionPan );
321 connect( mActionPan, &QAction::triggered, mPanTool, [=] { mView->setTool( mPanTool ); } );
324 mSelectTool->setAction( mActionSelectMoveItem );
326 mToolsActionGroup->addAction( mActionSelectMoveItem );
327 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [=] { mView->setTool( mSelectTool ); } );
329 mView->setTool( mSelectTool );
332 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [=](
const QString &text ) {
333 mIgnoreUndoStackChanges++;
334 mUndoStack->beginMacro( text );
335 mIgnoreUndoStackChanges--;
337 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [=] {
338 mIgnoreUndoStackChanges++;
339 mUndoStack->endMacro();
340 mIgnoreUndoStackChanges--;
342 connect( mView, &QgsModelGraphicsView::commandBegun,
this, [=](
const QString &text ) {
343 beginUndoCommand( text );
345 connect( mView, &QgsModelGraphicsView::commandEnded,
this, [=] {
348 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [=] {
352 connect( mActionAddGroupBox, &QAction::triggered,
this, [=] {
353 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
354 QgsProcessingModelGroupBox group;
355 group.setPosition( viewCenter );
356 group.setDescription( tr(
"New Group" ) );
358 beginUndoCommand( tr(
"Add Group Box" ) );
359 model()->addGroupBox( group );
367 restoreState( settings.
value( QStringLiteral(
"ModelDesigner/state" ), QByteArray(),
QgsSettings::App ).toByteArray() );
370QgsModelDesignerDialog::~QgsModelDesignerDialog()
373 if ( !mPanelStatus.isEmpty() )
375 QStringList docksTitle;
376 QStringList docksActive;
378 for (
const auto &panel : mPanelStatus.toStdMap() )
380 if ( panel.second.isVisible )
381 docksTitle << panel.first;
382 if ( panel.second.isActive )
383 docksActive << panel.first;
397 mIgnoreUndoStackChanges++;
401void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
403 if ( checkForUnsavedChanges() )
409void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
int id )
411 if ( mBlockUndoCommands || !mUndoStack )
414 if ( mActiveCommand )
417 mActiveCommand = std::make_unique<QgsModelUndoCommand>( mModel.get(), text,
id );
420void QgsModelDesignerDialog::endUndoCommand()
422 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
425 mActiveCommand->saveAfterState();
426 mIgnoreUndoStackChanges++;
427 mUndoStack->push( mActiveCommand.release() );
428 mIgnoreUndoStackChanges--;
432QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
437void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
439 mModel.reset( model );
441 mGroupEdit->setText( mModel->group() );
442 mNameEdit->setText( mModel->displayName() );
444 updateVariablesGui();
446 mView->centerOn( 0, 0 );
449 mIgnoreUndoStackChanges++;
451 mIgnoreUndoStackChanges--;
456void QgsModelDesignerDialog::loadModel(
const QString &path )
458 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
459 if ( alg->fromFile( path ) )
462 alg->setSourceFilePath( path );
463 setModel( alg.release() );
468 QMessageBox::critical(
this, tr(
"Open Model" ), tr(
"The selected model could not be loaded.\n"
469 "See the log for more information." ) );
473void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
475 QgsModelGraphicsScene *oldScene = mScene;
478 mScene->setParent(
this );
479 mScene->setLastRunResult( mLastResult );
480 mScene->setModel( mModel.get() );
481 mScene->setMessageBar( mMessageBar );
483 const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
484 mView->setModelScene( mScene );
486 mSelectTool->resetCache();
487 mSelectTool->setScene( mScene );
489 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [=] {
490 if ( mBlockRepaints )
495 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [=](
const QString &description,
int id ) { beginUndoCommand( description,
id ); } );
496 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [=] { endUndoCommand(); } );
497 connect( mScene, &QgsModelGraphicsScene::runFromChild,
this, &QgsModelDesignerDialog::runFromChild );
498 connect( mScene, &QgsModelGraphicsScene::runSelected,
this, &QgsModelDesignerDialog::runSelectedSteps );
499 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs,
this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
500 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog,
this, &QgsModelDesignerDialog::showChildAlgorithmLog );
502 mView->centerOn( center );
505 oldScene->deleteLater();
508void QgsModelDesignerDialog::activate()
512 setWindowState( windowState() & ~Qt::WindowMinimized );
516void QgsModelDesignerDialog::updateVariablesGui()
518 mBlockUndoCommands++;
520 auto variablesScope = std::make_unique<QgsExpressionContextScope>( tr(
"Model Variables" ) );
521 const QVariantMap modelVars = mModel->variables();
522 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
524 variablesScope->setVariable( it.key(), it.value() );
527 variablesContext.
appendScope( variablesScope.release() );
528 mVariablesEditor->setContext( &variablesContext );
529 mVariablesEditor->setEditableScopeIndex( 0 );
531 mBlockUndoCommands--;
534void QgsModelDesignerDialog::setDirty(
bool dirty )
540bool QgsModelDesignerDialog::validateSave( SaveAction action )
544 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
546 case QgsModelDesignerDialog::SaveAction::SaveInProject:
547 if ( mNameEdit->text().trimmed().isEmpty() )
549 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
558bool QgsModelDesignerDialog::checkForUnsavedChanges()
562 QMessageBox::StandardButton ret = QMessageBox::question(
this, tr(
"Save Model?" ), tr(
"There are unsaved changes in this model. Do you want to keep those?" ), QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, QMessageBox::Cancel );
565 case QMessageBox::Save:
566 return saveModel(
false );
568 case QMessageBox::Discard:
583 mLastResult.mergeWith( result );
585 mScene->setLastRunResult( mLastResult );
588void QgsModelDesignerDialog::setModelName(
const QString &name )
590 mNameEdit->setText( name );
593void QgsModelDesignerDialog::zoomIn()
595 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
596 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
598 const double factor = settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
599 mView->scale( factor, factor );
600 mView->centerOn( point );
603void QgsModelDesignerDialog::zoomOut()
605 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
606 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
608 const double factor = 1.0 / settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
609 mView->scale( factor, factor );
610 mView->centerOn( point );
613void QgsModelDesignerDialog::zoomActual()
615 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
616 mView->resetTransform();
617 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
618 mView->centerOn( point );
621void QgsModelDesignerDialog::zoomFull()
623 QRectF totalRect = mView->scene()->itemsBoundingRect();
624 totalRect.adjust( -10, -10, 10, 10 );
625 mView->fitInView( totalRect, Qt::KeepAspectRatio );
628void QgsModelDesignerDialog::newModel()
630 if ( !checkForUnsavedChanges() )
633 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
635 setModel( alg.release() );
638void QgsModelDesignerDialog::exportToImage()
641 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
643 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ), lastExportDir, tr(
"PNG files (*.png *.PNG)" ) );
647 if ( filename.isEmpty() )
652 const QFileInfo saveFileInfo( filename );
655 repaintModel(
false );
657 QRectF totalRect = mView->scene()->itemsBoundingRect();
658 totalRect.adjust( -10, -10, 10, 10 );
659 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
661 QImage img( totalRect.width(), totalRect.height(), QImage::Format_ARGB32_Premultiplied );
662 img.fill( Qt::white );
664 painter.setRenderHint( QPainter::Antialiasing );
665 painter.begin( &img );
666 mView->scene()->render( &painter, imageRect, totalRect );
669 img.save( filename );
671 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as image to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ),
Qgis::MessageLevel::Success, 0 );
672 repaintModel(
true );
675void QgsModelDesignerDialog::exportToPdf()
678 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
680 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ), lastExportDir, tr(
"PDF files (*.pdf *.PDF)" ) );
684 if ( filename.isEmpty() )
689 const QFileInfo saveFileInfo( filename );
692 repaintModel(
false );
694 QRectF totalRect = mView->scene()->itemsBoundingRect();
695 totalRect.adjust( -10, -10, 10, 10 );
696 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
698 QPdfWriter pdfWriter( filename );
700 const double scaleFactor = 96 / 25.4;
702 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ), QPageLayout::Portrait, QMarginsF( 0, 0, 0, 0 ) );
703 pageLayout.setMode( QPageLayout::FullPageMode );
704 pdfWriter.setPageLayout( pageLayout );
706 QPainter painter( &pdfWriter );
707 mView->scene()->render( &painter, printerRect, totalRect );
710 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as PDF to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ),
Qgis::MessageLevel::Success, 0 );
711 repaintModel(
true );
714void QgsModelDesignerDialog::exportToSvg()
717 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
719 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ), lastExportDir, tr(
"SVG files (*.svg *.SVG)" ) );
723 if ( filename.isEmpty() )
728 const QFileInfo saveFileInfo( filename );
731 repaintModel(
false );
733 QRectF totalRect = mView->scene()->itemsBoundingRect();
734 totalRect.adjust( -10, -10, 10, 10 );
735 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
738 svg.setFileName( filename );
739 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
740 svg.setViewBox( svgRect );
741 svg.setTitle( mModel->displayName() );
743 QPainter painter( &svg );
744 mView->scene()->render( &painter, svgRect, totalRect );
747 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as SVG to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ),
Qgis::MessageLevel::Success, 0 );
748 repaintModel(
true );
751void QgsModelDesignerDialog::exportAsPython()
754 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
756 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ), lastExportDir, tr(
"Processing scripts (*.py *.PY)" ) );
760 if ( filename.isEmpty() )
765 const QFileInfo saveFileInfo( filename );
770 QFile outFile( filename );
771 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
775 QTextStream fout( &outFile );
779 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as Python script to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ),
Qgis::MessageLevel::Success, 0 );
782void QgsModelDesignerDialog::toggleComments(
bool show )
786 repaintModel(
true );
789void QgsModelDesignerDialog::updateWindowTitle()
791 QString title = tr(
"Model Designer" );
792 if ( !mModel->name().isEmpty() )
793 title = mModel->group().isEmpty()
794 ? QStringLiteral(
"%1: %2" ).arg( title, mModel->name() )
795 : QStringLiteral(
"%1: %2 - %3" ).arg( title, mModel->group(), mModel->name() );
798 title.prepend(
'*' );
800 setWindowTitle( title );
803void QgsModelDesignerDialog::deleteSelected()
805 QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
809 if ( items.size() == 1 )
811 items.at( 0 )->deleteComponent();
815 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem *p1, QgsModelComponentGraphicItem *p2 ) {
820 if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) && dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
822 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) )
824 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
827 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) && dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
829 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) )
831 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
834 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) && dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
836 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) )
838 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
841 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) && dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
843 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) )
845 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
852 beginUndoCommand( tr(
"Delete Components" ) );
854 QVariant prevState = mModel->toVariant();
855 mBlockUndoCommands++;
856 mBlockRepaints =
true;
858 while ( !items.empty() )
860 QgsModelComponentGraphicItem *toDelete =
nullptr;
861 for ( QgsModelComponentGraphicItem *item : items )
863 if ( item->canDeleteComponent() )
876 toDelete->deleteComponent();
877 items.removeAll( toDelete );
882 mModel->loadVariant( prevState );
883 QMessageBox::warning(
nullptr, QObject::tr(
"Could not remove components" ), QObject::tr(
"Components depend on the selected items.\n"
884 "Try to remove them before trying deleting these components." ) );
885 mBlockUndoCommands--;
886 mActiveCommand.reset();
890 mBlockUndoCommands--;
894 mBlockRepaints =
false;
898void QgsModelDesignerDialog::populateZoomToMenu()
901 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
903 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
905 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
906 connect( zoomAction, &QAction::triggered,
this, [=] {
907 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
908 groupRect.adjust( -10, -10, 10, 10 );
909 mView->fitInView( groupRect, Qt::KeepAspectRatio );
910 mView->centerOn( item );
912 mGroupMenu->addAction( zoomAction );
917void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
919 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
920 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
924 mPanelStatus.clear();
926 for ( QDockWidget *dock : docks )
928 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
929 dock->setVisible(
false );
933 for ( QTabBar *tabBar : tabBars )
935 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
936 mPanelStatus[currentTabTitle].isActive =
true;
942 for ( QDockWidget *dock : docks )
944 if ( mPanelStatus.contains( dock->windowTitle() ) )
946 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
951 for ( QTabBar *tabBar : tabBars )
954 for (
int i = 0; i < tabBar->count(); ++i )
956 QString tabTitle = tabBar->tabText( i );
957 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
959 tabBar->setCurrentIndex( i );
963 mPanelStatus.clear();
967void QgsModelDesignerDialog::editHelp()
969 QgsProcessingHelpEditorDialog dialog(
this );
970 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
971 dialog.setAlgorithm( mModel.get() );
974 beginUndoCommand( tr(
"Edit Model Help" ) );
975 mModel->setHelpContent( dialog.helpContent() );
980void QgsModelDesignerDialog::runSelectedSteps()
982 QSet<QString> children;
983 const QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
984 for ( QgsModelComponentGraphicItem *item : items )
986 if ( QgsProcessingModelChildAlgorithm *childAlgorithm =
dynamic_cast<QgsProcessingModelChildAlgorithm *
>( item->component() ) )
988 children.insert( childAlgorithm->childId() );
992 if ( children.isEmpty() )
994 mMessageBar->pushWarning( QString(), tr(
"No steps are selected" ) );
1001void QgsModelDesignerDialog::runFromChild(
const QString &
id )
1003 QSet<QString> children = mModel->dependentChildAlgorithms(
id );
1004 children.insert(
id );
1008void QgsModelDesignerDialog::run(
const QSet<QString> &childAlgorithmSubset )
1011 const bool isValid = model()->validate( errors );
1014 QMessageBox messageBox;
1015 messageBox.setWindowTitle( tr(
"Model is Invalid" ) );
1016 messageBox.setIcon( QMessageBox::Icon::Warning );
1017 messageBox.setText( tr(
"This model is not valid and contains one or more issues. Are you sure you want to run it in this state?" ) );
1018 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1019 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1021 QString errorString;
1022 for (
const QString &error : std::as_const( errors ) )
1024 QString cleanedError = error;
1025 const thread_local QRegularExpression re( QStringLiteral(
"<[^>]*>" ) );
1026 cleanedError.replace( re, QString() );
1027 errorString += QStringLiteral(
"• %1\n" ).arg( cleanedError );
1030 messageBox.setDetailedText( errorString );
1031 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1035 if ( !childAlgorithmSubset.isEmpty() )
1037 for (
const QString &child : childAlgorithmSubset )
1040 const QSet<QString> requirements = mModel->dependsOnChildAlgorithms( child );
1041 for (
const QString &requirement : requirements )
1043 if ( !mLastResult.executedChildIds().contains( requirement ) )
1045 QMessageBox messageBox;
1046 messageBox.setWindowTitle( tr(
"Run Model" ) );
1047 messageBox.setIcon( QMessageBox::Icon::Warning );
1048 messageBox.setText( tr(
"Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1049 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1057 std::unique_ptr<QgsProcessingAlgorithmDialogBase> dialog( createExecutionDialog() );
1062 dialog->setParameters( mModel->designerParameterValues() );
1064 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmAboutToRun,
this, [
this, &childAlgorithmSubset](
QgsProcessingContext *context ) {
1065 if ( !childAlgorithmSubset.empty() )
1068 auto modelConfig = std::make_unique<QgsProcessingModelInitialRunConfig>();
1069 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1070 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1071 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1072 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1076 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1077 auto previousResultStore = std::make_unique<QgsMapLayerStore>();
1078 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1080 std::unique_ptr<QgsMapLayer> clone( it.value()->clone() );
1081 clone->setId( it.value()->id() );
1082 previousResultStore->addMapLayer( clone.release() );
1084 previousResultStore->moveToThread( nullptr );
1085 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1086 context->setModelInitialRunConfig( std::move( modelConfig ) );
1090 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmFinished,
this, [
this, &dialog](
bool,
const QVariantMap & ) {
1091 QgsProcessingContext *context = dialog->processingContext();
1093 setLastRunResult( context->modelResult() );
1095 mModel->setDesignerParameterValues( dialog->createProcessingParameters( QgsProcessingParametersGenerator::Flag::SkipDefaultValueParameters ) );
1098 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1099 mLayerStore.takeResultsFrom( *context );
1105void QgsModelDesignerDialog::showChildAlgorithmOutputs(
const QString &childId )
1107 const QString childDescription = mModel->childAlgorithm( childId ).description();
1110 const QVariantMap childAlgorithmOutputs = result.
outputs();
1111 if ( childAlgorithmOutputs.isEmpty() )
1113 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1120 mMessageBar->pushCritical( QString(), tr(
"Results cannot be shown for an invalid model component" ) );
1125 if ( outputParams.isEmpty() )
1128 QgsDebugError(
"Cannot show results for algorithms with no outputs" );
1132 bool foundResults =
false;
1135 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1136 if ( !output.isValid() )
1139 if ( output.type() == QVariant::String )
1143 QgsDebugMsgLevel( QStringLiteral(
"Loading previous result for %1: %2" ).arg( outputParam->name(), output.toString() ), 2 );
1145 std::unique_ptr<QgsMapLayer> layer( resultLayer->clone() );
1148 if ( outputParams.size() > 1 )
1149 baseName = tr(
"%1 — %2" ).arg( childDescription, outputParam->name() );
1151 baseName = childDescription;
1155 QString name = baseName;
1160 name = tr(
"%1 (%2)" ).arg( baseName ).arg( counter );
1163 layer->setName( name );
1166 foundResults =
true;
1171 QgsDebugError( QStringLiteral(
"Could not load previous result for %1: %2" ).arg( outputParam->name(), output.toString() ) );
1176 if ( !foundResults )
1178 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1183void QgsModelDesignerDialog::showChildAlgorithmLog(
const QString &childId )
1185 const QString childDescription = mModel->childAlgorithm( childId ).description();
1188 if ( result.
htmlLog().isEmpty() )
1190 mMessageBar->pushWarning( QString(), tr(
"No log is available for %1" ).arg( childDescription ) );
1195 m.setWindowTitle( childDescription );
1196 m.setCheckBoxVisible(
false );
1197 m.setMessageAsHtml( result.
htmlLog() );
1201void QgsModelDesignerDialog::validate()
1204 if ( model()->validate( issues ) )
1206 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
1211 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
1212 connect( detailsButton, &QPushButton::clicked, detailsButton, [=] {
1214 dialog->
setTitle( tr(
"Model is Invalid" ) );
1216 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + QStringLiteral(
"<ul>" );
1217 for (
const QString &issue : issues )
1219 longMessage += QStringLiteral(
"<li>%1</li>" ).arg( issue );
1221 longMessage += QLatin1String(
"</ul>" );
1226 messageWidget->layout()->addWidget( detailsButton );
1227 mMessageBar->clearWidgets();
1232void QgsModelDesignerDialog::reorderInputs()
1234 QgsModelInputReorderDialog dlg(
this );
1235 dlg.setModel( mModel.get() );
1238 const QStringList inputOrder = dlg.inputOrder();
1239 beginUndoCommand( tr(
"Reorder Inputs" ) );
1240 mModel->setParameterOrder( inputOrder );
1245void QgsModelDesignerDialog::reorderOutputs()
1247 QgsModelOutputReorderDialog dlg(
this );
1248 dlg.setModel( mModel.get() );
1251 const QStringList outputOrder = dlg.outputOrder();
1252 beginUndoCommand( tr(
"Reorder Outputs" ) );
1253 mModel->setOutputOrder( outputOrder );
1254 mModel->setOutputGroup( dlg.outputGroup() );
1259bool QgsModelDesignerDialog::isDirty()
const
1261 return mHasChanged && mUndoStack->index() != -1;
1264void QgsModelDesignerDialog::fillInputsTree()
1267 auto parametersItem = std::make_unique<QTreeWidgetItem>();
1268 parametersItem->setText( 0, tr(
"Parameters" ) );
1271 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1278 auto paramItem = std::make_unique<QTreeWidgetItem>();
1279 paramItem->setText( 0, param->name() );
1280 paramItem->setData( 0, Qt::UserRole, param->id() );
1281 paramItem->setIcon( 0, icon );
1282 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1283 paramItem->setToolTip( 0, param->description() );
1284 parametersItem->addChild( paramItem.release() );
1287 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1288 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1296QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1299 , mChildId( childId )
1301 QHBoxLayout *hl =
new QHBoxLayout();
1302 hl->setContentsMargins( 0, 0, 0, 0 );
1304 mLineEdit =
new QLineEdit();
1305 mLineEdit->setEnabled(
false );
1306 hl->addWidget( mLineEdit, 1 );
1308 mToolButton =
new QToolButton();
1309 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1310 hl->addWidget( mToolButton );
1314 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1316 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1319void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1323 updateSummaryText();
1326void QgsModelChildDependenciesWidget::showDialog()
1328 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1330 QVariantList availableOptions;
1331 for (
const QgsProcessingModelChildDependency &dep : available )
1332 availableOptions << QVariant::fromValue( dep );
1333 QVariantList selectedOptions;
1334 for (
const QgsProcessingModelChildDependency &dep : mValue )
1335 selectedOptions << QVariant::fromValue( dep );
1340 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1341 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1343 widget->setValueFormatter( [=](
const QVariant &v ) -> QString {
1344 const QgsProcessingModelChildDependency dep = v.value<QgsProcessingModelChildDependency>();
1346 const QString description = mModel->childAlgorithm( dep.childId ).description();
1347 if ( dep.conditionalBranch.isEmpty() )
1350 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1353 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [=]() {
1354 QList<QgsProcessingModelChildDependency> res;
1355 for (
const QVariant &v : widget->selectedOptions() )
1357 res << v.value<QgsProcessingModelChildDependency>();
1366void QgsModelChildDependenciesWidget::updateSummaryText()
1368 mLineEdit->setText( tr(
"%n dependencies selected",
nullptr, mValue.count() ) );
@ ExposeToModeler
Is this parameter available in the modeler. Is set to on by default.
@ Warning
Warning message.
@ Critical
Critical/error message.
@ Success
Used for reporting a successful operation.
@ ModelDebug
Model debug level logging. Includes verbose logging and other outputs useful for debugging models.
static QgsProcessingRegistry * processingRegistry()
Returns the application's processing registry, used for managing processing providers,...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Base class for all map layer types.
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
Creates message bar item widget containing a message text to be displayed on the bar.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
A generic message view for displaying QGIS messages.
void setTitle(const QString &title) override
Sets title for the messages.
void setMessage(const QString &message, MessageType msgType) override
Sets message, it won't be displayed until.
void showMessage(bool blocking=true) override
display the message to the user and deletes itself
Abstract base class for processing algorithms.
QgsProcessingParameterDefinitions destinationParameterDefinitions() const
Returns a list of destination parameters definitions utilized by the algorithm.
Contains information about the context in which a processing algorithm is executed.
Encapsulates the results of running a child algorithm within a model.
QString htmlLog() const
Returns the HTML formatted contents of logged messages which occurred while running the child.
QVariantMap outputs() const
Returns the outputs generated by the child algorithm.
Encapsulates the results of running a Processing model.
QMap< QString, QgsProcessingModelChildAlgorithmResult > childResults() const
Returns the map of child algorithm results.
Base class for the definition of processing parameters.
Makes metadata of processing parameters available.
QList< QgsProcessingParameterType * > parameterTypes() const
Returns a list with all known parameter types.
static QgsMapLayer * mapLayerFromString(const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers=true, QgsProcessingUtils::LayerHint typeHint=QgsProcessingUtils::LayerHint::UnknownType, QgsProcessing::LayerOptionsFlags flags=QgsProcessing::LayerOptionsFlags())
Interprets a string as a map layer within the supplied context.
@ PythonQgsProcessingAlgorithmSubclass
Full Python QgsProcessingAlgorithm subclass.
static QgsProject * instance()
Returns the QgsProject singleton instance.
QgsMapLayer * addMapLayer(QgsMapLayer *mapLayer, bool addToLegend=true, bool takeOwnership=true)
Add a layer to the map of loaded layers.
A utility class for dynamic handling of changes to screen properties.
Stores settings for use within QGIS.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)