17#include "moc_qgsmodeldesignerdialog.cpp"
45#include <QKeySequence>
48#include <QSvgGenerator>
56#include <QActionGroup>
61QgsModelerToolboxModel::QgsModelerToolboxModel( QObject *parent )
67Qt::ItemFlags QgsModelerToolboxModel::flags(
const QModelIndex &index )
const
69 Qt::ItemFlags f = QgsProcessingToolboxProxyModel::flags( index );
70 const QModelIndex sourceIndex = mapToSource( index );
71 if ( toolboxModel()->isAlgorithm( sourceIndex ) )
73 f = f | Qt::ItemIsDragEnabled;
78Qt::DropActions QgsModelerToolboxModel::supportedDragActions()
const
80 return Qt::CopyAction;
85QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
86 : QMainWindow( parent, flags )
87 , mToolsActionGroup( new QActionGroup( this ) )
95 setAttribute( Qt::WA_DeleteOnClose );
96 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
97 setWindowFlags( Qt::WindowMinimizeButtonHint |
98 Qt::WindowMaximizeButtonHint |
99 Qt::WindowCloseButtonHint );
103 mModel = std::make_unique< QgsProcessingModelAlgorithm >();
106 mUndoStack =
new QUndoStack(
this );
107 connect( mUndoStack, &QUndoStack::indexChanged,
this, [ = ]
109 if ( mIgnoreUndoStackChanges )
112 mBlockUndoCommands++;
113 updateVariablesGui();
114 mGroupEdit->setText( mModel->group() );
115 mNameEdit->setText( mModel->displayName() );
116 mBlockUndoCommands--;
120 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
121 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
122 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
123 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
125 mAlgorithmsTree->header()->setVisible(
false );
126 mAlgorithmSearchEdit->setShowSearchIcon(
true );
127 mAlgorithmSearchEdit->setPlaceholderText( tr(
"Search…" ) );
128 connect( mAlgorithmSearchEdit, &QgsFilterLineEdit::textChanged, mAlgorithmsTree, &QgsProcessingToolboxTreeView::setFilterString );
130 mInputsTreeWidget->header()->setVisible(
false );
131 mInputsTreeWidget->setAlternatingRowColors(
true );
132 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
133 mInputsTreeWidget->setDropIndicatorShown(
true );
135 mNameEdit->setPlaceholderText( tr(
"Enter model name here" ) );
136 mGroupEdit->setPlaceholderText( tr(
"Enter group name here" ) );
139 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
140 mainLayout->insertWidget( 0, mMessageBar );
142 mView->setAcceptDrops(
true );
145 connect( mActionClose, &QAction::triggered,
this, &QWidget::close );
146 connect( mActionNew, &QAction::triggered,
this, &QgsModelDesignerDialog::newModel );
147 connect( mActionZoomIn, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomIn );
148 connect( mActionZoomOut, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomOut );
149 connect( mActionZoomActual, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomActual );
150 connect( mActionZoomToItems, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomFull );
151 connect( mActionExportImage, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToImage );
152 connect( mActionExportPdf, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToPdf );
153 connect( mActionExportSvg, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToSvg );
154 connect( mActionExportPython, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsPython );
155 connect( mActionSave, &QAction::triggered,
this, [ = ] { saveModel(
false ); } );
156 connect( mActionSaveAs, &QAction::triggered,
this, [ = ] { saveModel(
true ); } );
157 connect( mActionDeleteComponents, &QAction::triggered,
this, &QgsModelDesignerDialog::deleteSelected );
158 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
159 connect( mActionValidate, &QAction::triggered,
this, &QgsModelDesignerDialog::validate );
160 connect( mActionReorderInputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderInputs );
161 connect( mActionReorderOutputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderOutputs );
162 connect( mActionEditHelp, &QAction::triggered,
this, &QgsModelDesignerDialog::editHelp );
163 connect( mReorderInputsButton, &QPushButton::clicked,
this, &QgsModelDesignerDialog::reorderInputs );
164 connect( mActionRun, &QAction::triggered,
this, [
this] { run(); } );
165 connect( mActionRunSelectedSteps, &QAction::triggered,
this, &QgsModelDesignerDialog::runSelectedSteps );
167 mActionSnappingEnabled->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ),
false ).toBool() );
168 connect( mActionSnappingEnabled, &QAction::toggled,
this, [ = ](
bool enabled )
170 mView->snapper()->setSnapToGrid( enabled );
171 QgsSettings().
setValue( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ), enabled );
173 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
175 connect( mActionSelectAll, &QAction::triggered,
this, [ = ]
180 QStringList docksTitle = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), QStringList(),
QgsSettings::App ).toStringList();
181 QStringList docksActive = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), QStringList(),
QgsSettings::App ).toStringList();
182 if ( !docksTitle.isEmpty() )
184 for (
const auto &title : docksTitle )
186 mPanelStatus.insert( title, PanelStatus(
true, docksActive.contains( title ) ) );
189 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
190 connect( mActionHidePanels, &QAction::toggled,
this, &QgsModelDesignerDialog::setPanelVisibility );
192 mUndoAction = mUndoStack->createUndoAction(
this );
194 mUndoAction->setShortcuts( QKeySequence::Undo );
195 mRedoAction = mUndoStack->createRedoAction(
this );
197 mRedoAction->setShortcuts( QKeySequence::Redo );
199 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
200 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
201 mMenuEdit->insertSeparator( mActionDeleteComponents );
202 mToolbar->insertAction( mActionZoomIn, mUndoAction );
203 mToolbar->insertAction( mActionZoomIn, mRedoAction );
204 mToolbar->insertSeparator( mActionZoomIn );
206 mGroupMenu =
new QMenu( tr(
"Zoom To" ),
this );
207 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
208 connect( mGroupMenu, &QMenu::aboutToShow,
this, &QgsModelDesignerDialog::populateZoomToMenu );
212 mActionCut =
new QAction( tr(
"Cu&t" ),
this );
213 mActionCut->setShortcuts( QKeySequence::Cut );
214 mActionCut->setStatusTip( tr(
"Cut" ) );
216 connect( mActionCut, &QAction::triggered,
this, [ = ]
218 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut );
221 mActionCopy =
new QAction( tr(
"&Copy" ),
this );
222 mActionCopy->setShortcuts( QKeySequence::Copy );
223 mActionCopy->setStatusTip( tr(
"Copy" ) );
225 connect( mActionCopy, &QAction::triggered,
this, [ = ]
227 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy );
230 mActionPaste =
new QAction( tr(
"&Paste" ),
this );
231 mActionPaste->setShortcuts( QKeySequence::Paste );
232 mActionPaste->setStatusTip( tr(
"Paste" ) );
234 connect( mActionPaste, &QAction::triggered,
this, [ = ]
236 mView->pasteItems( QgsModelGraphicsView::PasteModeCursor );
238 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
239 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
240 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
241 mMenuEdit->insertSeparator( mActionDeleteComponents );
243 mAlgorithmsModel =
new QgsModelerToolboxModel(
this );
244 mAlgorithmsTree->setToolboxProxyModel( mAlgorithmsModel );
247 if ( settings.
value( QStringLiteral(
"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES" ),
false ).toBool() )
251 mAlgorithmsTree->setFilters( filters );
252 mAlgorithmsTree->setDragDropMode( QTreeWidget::DragOnly );
253 mAlgorithmsTree->setDropIndicatorShown(
true );
255 connect( mView, &QgsModelGraphicsView::algorithmDropped,
this, [ = ](
const QString & algorithmId,
const QPointF & pos )
257 addAlgorithm( algorithmId, pos );
259 connect( mAlgorithmsTree, &QgsProcessingToolboxTreeView::doubleClicked,
this, [ = ]()
261 if ( mAlgorithmsTree->selectedAlgorithm() )
262 addAlgorithm( mAlgorithmsTree->selectedAlgorithm()->id(), QPointF() );
264 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked,
this, [ = ](
const QModelIndex & )
266 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
267 addInput( parameterType, QPointF() );
270 connect( mView, &QgsModelGraphicsView::inputDropped,
this, &QgsModelDesignerDialog::addInput );
273 QShortcut *ctrlEquals =
new QShortcut( QKeySequence( QStringLiteral(
"Ctrl+=" ) ),
this );
274 connect( ctrlEquals, &QShortcut::activated,
this, &QgsModelDesignerDialog::zoomIn );
277 mUndoDock->setObjectName( QStringLiteral(
"UndoDock" ) );
278 mUndoView =
new QUndoView( mUndoStack,
this );
279 mUndoDock->setWidget( mUndoView );
280 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
281 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
283 tabifyDockWidget( mUndoDock, mPropertiesDock );
284 tabifyDockWidget( mVariablesDock, mPropertiesDock );
285 mPropertiesDock->raise();
286 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
287 mInputsDock->raise();
293 beginUndoCommand( tr(
"Change Model Variables" ) );
294 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
298 connect( mNameEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & name )
302 beginUndoCommand( tr(
"Change Model Name" ), NameChanged );
303 mModel->setName( name );
308 connect( mGroupEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & group )
312 beginUndoCommand( tr(
"Change Model Group" ), GroupChanged );
313 mModel->setGroup( group );
321 QToolButton *toolbuttonExportToScript =
new QToolButton();
322 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
323 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
324 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
325 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
326 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
328 mActionShowComments->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/ShowComments" ),
true ).toBool() );
329 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
332 mPanTool->setAction( mActionPan );
334 mToolsActionGroup->addAction( mActionPan );
335 connect( mActionPan, &QAction::triggered, mPanTool, [ = ] { mView->setTool( mPanTool ); } );
338 mSelectTool->setAction( mActionSelectMoveItem );
340 mToolsActionGroup->addAction( mActionSelectMoveItem );
341 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
343 mView->setTool( mSelectTool );
346 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [ = ](
const QString & text )
348 mIgnoreUndoStackChanges++;
349 mUndoStack->beginMacro( text );
350 mIgnoreUndoStackChanges--;
352 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [ = ]
354 mIgnoreUndoStackChanges++;
355 mUndoStack->endMacro();
356 mIgnoreUndoStackChanges--;
358 connect( mView, &QgsModelGraphicsView::beginCommand,
this, [ = ](
const QString & text )
360 beginUndoCommand( text );
362 connect( mView, &QgsModelGraphicsView::endCommand,
this, [ = ]
366 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [ = ]
371 connect( mActionAddGroupBox, &QAction::triggered,
this, [ = ]
373 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
374 QgsProcessingModelGroupBox group;
375 group.setPosition( viewCenter );
376 group.setDescription( tr(
"New Group" ) );
378 beginUndoCommand( tr(
"Add Group Box" ) );
379 model()->addGroupBox( group );
387 restoreState( settings.
value( QStringLiteral(
"ModelDesigner/state" ), QByteArray(),
QgsSettings::App ).toByteArray() );
390QgsModelDesignerDialog::~QgsModelDesignerDialog()
393 if ( !mPanelStatus.isEmpty() )
395 QStringList docksTitle;
396 QStringList docksActive;
398 for (
const auto &panel : mPanelStatus.toStdMap() )
400 if ( panel.second.isVisible )
401 docksTitle << panel.first;
402 if ( panel.second.isActive )
403 docksActive << panel.first;
417 mIgnoreUndoStackChanges++;
421void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
423 if ( checkForUnsavedChanges() )
429void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
int id )
431 if ( mBlockUndoCommands || !mUndoStack )
434 if ( mActiveCommand )
437 mActiveCommand = std::make_unique< QgsModelUndoCommand >( mModel.get(), text,
id );
440void QgsModelDesignerDialog::endUndoCommand()
442 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
445 mActiveCommand->saveAfterState();
446 mIgnoreUndoStackChanges++;
447 mUndoStack->push( mActiveCommand.release() );
448 mIgnoreUndoStackChanges--;
452QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
457void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
459 mModel.reset( model );
461 mGroupEdit->setText( mModel->group() );
462 mNameEdit->setText( mModel->displayName() );
464 updateVariablesGui();
466 mView->centerOn( 0, 0 );
469 mIgnoreUndoStackChanges++;
471 mIgnoreUndoStackChanges--;
476void QgsModelDesignerDialog::loadModel(
const QString &path )
478 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
479 if ( alg->fromFile( path ) )
482 alg->setSourceFilePath( path );
483 setModel( alg.release() );
488 QMessageBox::critical(
this, tr(
"Open Model" ), tr(
"The selected model could not be loaded.\n"
489 "See the log for more information." ) );
493void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
495 QgsModelGraphicsScene *oldScene = mScene;
498 mScene->setParent(
this );
499 mScene->setLastRunResult( mLastResult );
500 mScene->setModel( mModel.get() );
501 mScene->setMessageBar( mMessageBar );
503 const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
504 mView->setModelScene( mScene );
506 mSelectTool->resetCache();
507 mSelectTool->setScene( mScene );
509 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [ = ]
511 if ( mBlockRepaints )
516 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [ = ](
const QString & description,
int id ) { beginUndoCommand( description,
id ); } );
517 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [ = ] { endUndoCommand(); } );
518 connect( mScene, &QgsModelGraphicsScene::runFromChild,
this, &QgsModelDesignerDialog::runFromChild );
519 connect( mScene, &QgsModelGraphicsScene::runSelected,
this, &QgsModelDesignerDialog::runSelectedSteps );
520 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs,
this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
521 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog,
this, &QgsModelDesignerDialog::showChildAlgorithmLog );
523 mView->centerOn( center );
526 oldScene->deleteLater();
529void QgsModelDesignerDialog::activate()
533 setWindowState( windowState() & ~Qt::WindowMinimized );
537void QgsModelDesignerDialog::updateVariablesGui()
539 mBlockUndoCommands++;
541 std::unique_ptr< QgsExpressionContextScope > variablesScope = std::make_unique< QgsExpressionContextScope >( tr(
"Model Variables" ) );
542 const QVariantMap modelVars = mModel->variables();
543 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
545 variablesScope->setVariable( it.key(), it.value() );
548 variablesContext.
appendScope( variablesScope.release() );
549 mVariablesEditor->setContext( &variablesContext );
550 mVariablesEditor->setEditableScopeIndex( 0 );
552 mBlockUndoCommands--;
555void QgsModelDesignerDialog::setDirty(
bool dirty )
561bool QgsModelDesignerDialog::validateSave( SaveAction action )
565 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
567 case QgsModelDesignerDialog::SaveAction::SaveInProject:
568 if ( mNameEdit->text().trimmed().isEmpty() )
570 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
579bool QgsModelDesignerDialog::checkForUnsavedChanges()
583 QMessageBox::StandardButton ret = QMessageBox::question(
this, tr(
"Save Model?" ),
584 tr(
"There are unsaved changes in this model. Do you want to keep those?" ),
585 QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, QMessageBox::Cancel );
588 case QMessageBox::Save:
589 return saveModel(
false );
591 case QMessageBox::Discard:
606 mLastResult.mergeWith( result );
608 mScene->setLastRunResult( mLastResult );
611void QgsModelDesignerDialog::setModelName(
const QString &name )
613 mNameEdit->setText( name );
616void QgsModelDesignerDialog::zoomIn()
618 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
619 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
621 const double factor = settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
622 mView->scale( factor, factor );
623 mView->centerOn( point );
626void QgsModelDesignerDialog::zoomOut()
628 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
629 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
631 const double factor = 1.0 / settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
632 mView->scale( factor, factor );
633 mView->centerOn( point );
636void QgsModelDesignerDialog::zoomActual()
638 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
639 mView->resetTransform();
640 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
641 mView->centerOn( point );
644void QgsModelDesignerDialog::zoomFull()
646 QRectF totalRect = mView->scene()->itemsBoundingRect();
647 totalRect.adjust( -10, -10, 10, 10 );
648 mView->fitInView( totalRect, Qt::KeepAspectRatio );
651void QgsModelDesignerDialog::newModel()
653 if ( !checkForUnsavedChanges() )
656 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
658 setModel( alg.release() );
661void QgsModelDesignerDialog::exportToImage()
664 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
666 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ),
668 tr(
"PNG files (*.png *.PNG)" ) );
672 if ( filename.isEmpty() )
677 const QFileInfo saveFileInfo( filename );
680 repaintModel(
false );
682 QRectF totalRect = mView->scene()->itemsBoundingRect();
683 totalRect.adjust( -10, -10, 10, 10 );
684 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
686 QImage img( totalRect.width(), totalRect.height(),
687 QImage::Format_ARGB32_Premultiplied );
688 img.fill( Qt::white );
690 painter.setRenderHint( QPainter::Antialiasing );
691 painter.begin( &img );
692 mView->scene()->render( &painter, imageRect, totalRect );
695 img.save( filename );
697 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 );
698 repaintModel(
true );
701void QgsModelDesignerDialog::exportToPdf()
704 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
706 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ),
708 tr(
"PDF files (*.pdf *.PDF)" ) );
712 if ( filename.isEmpty() )
717 const QFileInfo saveFileInfo( filename );
720 repaintModel(
false );
722 QRectF totalRect = mView->scene()->itemsBoundingRect();
723 totalRect.adjust( -10, -10, 10, 10 );
724 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
726 QPdfWriter pdfWriter( filename );
728 const double scaleFactor = 96 / 25.4;
730 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ),
731 QPageLayout::Portrait,
732 QMarginsF( 0, 0, 0, 0 ) );
733 pageLayout.setMode( QPageLayout::FullPageMode );
734 pdfWriter.setPageLayout( pageLayout );
736 QPainter painter( &pdfWriter );
737 mView->scene()->render( &painter, printerRect, totalRect );
740 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as PDF to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(),
742 repaintModel(
true );
745void QgsModelDesignerDialog::exportToSvg()
748 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
750 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ),
752 tr(
"SVG files (*.svg *.SVG)" ) );
756 if ( filename.isEmpty() )
761 const QFileInfo saveFileInfo( filename );
764 repaintModel(
false );
766 QRectF totalRect = mView->scene()->itemsBoundingRect();
767 totalRect.adjust( -10, -10, 10, 10 );
768 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
771 svg.setFileName( filename );
772 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
773 svg.setViewBox( svgRect );
774 svg.setTitle( mModel->displayName() );
776 QPainter painter( &svg );
777 mView->scene()->render( &painter, svgRect, totalRect );
780 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 );
781 repaintModel(
true );
784void QgsModelDesignerDialog::exportAsPython()
787 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
789 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ),
791 tr(
"Processing scripts (*.py *.PY)" ) );
795 if ( filename.isEmpty() )
800 const QFileInfo saveFileInfo( filename );
805 QFile outFile( filename );
806 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
810 QTextStream fout( &outFile );
814 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 );
817void QgsModelDesignerDialog::toggleComments(
bool show )
821 repaintModel(
true );
824void QgsModelDesignerDialog::updateWindowTitle()
826 QString title = tr(
"Model Designer" );
827 if ( !mModel->name().isEmpty() )
828 title = mModel->group().isEmpty()
829 ? QStringLiteral(
"%1: %2" ).arg( title, mModel->name() )
830 : QStringLiteral(
"%1: %2 - %3" ).arg( title, mModel->group(), mModel->name() );
833 title.prepend(
'*' );
835 setWindowTitle( title );
838void QgsModelDesignerDialog::deleteSelected()
840 QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
844 if ( items.size() == 1 )
846 items.at( 0 )->deleteComponent();
850 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem * p1, QgsModelComponentGraphicItem * p2 )
853 if ( dynamic_cast< QgsModelCommentGraphicItem *>( p1 ) )
855 else if ( dynamic_cast< QgsModelCommentGraphicItem *>( p2 ) )
857 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p1 ) )
859 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p2 ) )
861 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p1 ) )
863 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p2 ) )
865 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p1 ) )
867 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p2 ) )
873 beginUndoCommand( tr(
"Delete Components" ) );
875 QVariant prevState = mModel->toVariant();
876 mBlockUndoCommands++;
877 mBlockRepaints =
true;
879 while ( !items.empty() )
881 QgsModelComponentGraphicItem *toDelete =
nullptr;
882 for ( QgsModelComponentGraphicItem *item : items )
884 if ( item->canDeleteComponent() )
897 toDelete->deleteComponent();
898 items.removeAll( toDelete );
903 mModel->loadVariant( prevState );
904 QMessageBox::warning(
nullptr, QObject::tr(
"Could not remove components" ),
905 QObject::tr(
"Components depend on the selected items.\n"
906 "Try to remove them before trying deleting these components." ) );
907 mBlockUndoCommands--;
908 mActiveCommand.reset();
912 mBlockUndoCommands--;
916 mBlockRepaints =
false;
920void QgsModelDesignerDialog::populateZoomToMenu()
923 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
925 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
927 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
928 connect( zoomAction, &QAction::triggered,
this, [ = ]
930 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
931 groupRect.adjust( -10, -10, 10, 10 );
932 mView->fitInView( groupRect, Qt::KeepAspectRatio );
933 mView->centerOn( item );
935 mGroupMenu->addAction( zoomAction );
940void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
942 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
943 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
947 mPanelStatus.clear();
949 for ( QDockWidget *dock : docks )
951 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
952 dock->setVisible(
false );
956 for ( QTabBar *tabBar : tabBars )
958 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
959 mPanelStatus[ currentTabTitle ].isActive =
true;
965 for ( QDockWidget *dock : docks )
967 if ( mPanelStatus.contains( dock->windowTitle() ) )
969 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
974 for ( QTabBar *tabBar : tabBars )
977 for (
int i = 0; i < tabBar->count(); ++i )
979 QString tabTitle = tabBar->tabText( i );
980 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
982 tabBar->setCurrentIndex( i );
986 mPanelStatus.clear();
990void QgsModelDesignerDialog::editHelp()
992 QgsProcessingHelpEditorDialog dialog(
this );
993 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
994 dialog.setAlgorithm( mModel.get() );
997 beginUndoCommand( tr(
"Edit Model Help" ) );
998 mModel->setHelpContent( dialog.helpContent() );
1003void QgsModelDesignerDialog::runSelectedSteps()
1005 QSet<QString> children;
1006 const QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
1007 for ( QgsModelComponentGraphicItem *item : items )
1009 if ( QgsProcessingModelChildAlgorithm *childAlgorithm =
dynamic_cast< QgsProcessingModelChildAlgorithm *
>( item->component() ) )
1011 children.insert( childAlgorithm->childId() );
1015 if ( children.isEmpty() )
1017 mMessageBar->pushWarning( QString(), tr(
"No steps are selected" ) );
1024void QgsModelDesignerDialog::runFromChild(
const QString &
id )
1026 QSet<QString> children = mModel->dependentChildAlgorithms(
id );
1027 children.insert(
id );
1031void QgsModelDesignerDialog::run(
const QSet<QString> &childAlgorithmSubset )
1034 const bool isValid = model()->validate( errors );
1037 QMessageBox messageBox;
1038 messageBox.setWindowTitle( tr(
"Model is Invalid" ) );
1039 messageBox.setIcon( QMessageBox::Icon::Warning );
1040 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?" ) );
1041 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1042 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1044 QString errorString;
1045 for (
const QString &error : std::as_const( errors ) )
1047 QString cleanedError = error;
1048 const thread_local QRegularExpression re( QStringLiteral(
"<[^>]*>" ) );
1049 cleanedError.replace( re, QString() );
1050 errorString += QStringLiteral(
"• %1\n" ).arg( cleanedError );
1053 messageBox.setDetailedText( errorString );
1054 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1058 if ( !childAlgorithmSubset.isEmpty() )
1060 for (
const QString &child : childAlgorithmSubset )
1063 const QSet< QString > requirements = mModel->dependsOnChildAlgorithms( child );
1064 for (
const QString &requirement : requirements )
1066 if ( !mLastResult.executedChildIds().contains( requirement ) )
1068 QMessageBox messageBox;
1069 messageBox.setWindowTitle( tr(
"Run Model" ) );
1070 messageBox.setIcon( QMessageBox::Icon::Warning );
1071 messageBox.setText( tr(
"Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1072 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1080 std::unique_ptr< QgsProcessingAlgorithmDialogBase > dialog( createExecutionDialog() );
1085 dialog->setParameters( mModel->designerParameterValues() );
1087 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmAboutToRun,
this, [
this, &childAlgorithmSubset](
QgsProcessingContext * context )
1089 if ( ! childAlgorithmSubset.empty() )
1092 std::unique_ptr< QgsProcessingModelInitialRunConfig > modelConfig = std::make_unique< QgsProcessingModelInitialRunConfig >();
1093 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1094 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1095 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1096 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1100 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1101 std::unique_ptr<QgsMapLayerStore> previousResultStore = std::make_unique< QgsMapLayerStore >();
1102 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1104 std::unique_ptr< QgsMapLayer > clone( it.value()->clone() );
1105 clone->setId( it.value()->id() );
1106 previousResultStore->addMapLayer( clone.release() );
1108 previousResultStore->moveToThread( nullptr );
1109 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1110 context->setModelInitialRunConfig( std::move( modelConfig ) );
1114 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmFinished,
this, [
this, &dialog](
bool,
const QVariantMap & )
1116 QgsProcessingContext *context = dialog->processingContext();
1118 setLastRunResult( context->modelResult() );
1120 mModel->setDesignerParameterValues( dialog->createProcessingParameters( QgsProcessingParametersGenerator::Flag::SkipDefaultValueParameters ) );
1123 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1124 mLayerStore.takeResultsFrom( *context );
1130void QgsModelDesignerDialog::showChildAlgorithmOutputs(
const QString &childId )
1132 const QString childDescription = mModel->childAlgorithm( childId ).description();
1135 const QVariantMap childAlgorithmOutputs = result.
outputs();
1136 if ( childAlgorithmOutputs.isEmpty() )
1138 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1145 mMessageBar->pushCritical( QString(), tr(
"Results cannot be shown for an invalid model component" ) );
1150 if ( outputParams.isEmpty() )
1153 QgsDebugError(
"Cannot show results for algorithms with no outputs" );
1157 bool foundResults =
false;
1160 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1161 if ( !output.isValid() )
1164 if ( output.type() == QVariant::String )
1168 QgsDebugMsgLevel( QStringLiteral(
"Loading previous result for %1: %2" ).arg( outputParam->name(), output.toString() ), 2 );
1170 std::unique_ptr< QgsMapLayer > layer( resultLayer->clone() );
1173 if ( outputParams.size() > 1 )
1174 baseName = tr(
"%1 — %2" ).arg( childDescription, outputParam->name() );
1176 baseName = childDescription;
1180 QString name = baseName;
1185 name = tr(
"%1 (%2)" ).arg( baseName ).arg( counter );
1188 layer->setName( name );
1191 foundResults =
true;
1196 QgsDebugError( QStringLiteral(
"Could not load previous result for %1: %2" ).arg( outputParam->name(), output.toString() ) );
1201 if ( !foundResults )
1203 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1208void QgsModelDesignerDialog::showChildAlgorithmLog(
const QString &childId )
1210 const QString childDescription = mModel->childAlgorithm( childId ).description();
1213 if ( result.
htmlLog().isEmpty() )
1215 mMessageBar->pushWarning( QString(), tr(
"No log is available for %1" ).arg( childDescription ) );
1220 m.setWindowTitle( childDescription );
1221 m.setCheckBoxVisible(
false );
1222 m.setMessageAsHtml( result.
htmlLog() );
1226void QgsModelDesignerDialog::validate()
1229 if ( model()->validate( issues ) )
1231 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
1236 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
1237 connect( detailsButton, &QPushButton::clicked, detailsButton, [ = ]
1240 dialog->
setTitle( tr(
"Model is Invalid" ) );
1242 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + QStringLiteral(
"<ul>" );
1243 for (
const QString &issue : issues )
1245 longMessage += QStringLiteral(
"<li>%1</li>" ).arg( issue );
1247 longMessage += QLatin1String(
"</ul>" );
1252 messageWidget->layout()->addWidget( detailsButton );
1253 mMessageBar->clearWidgets();
1258void QgsModelDesignerDialog::reorderInputs()
1260 QgsModelInputReorderDialog dlg(
this );
1261 dlg.setModel( mModel.get() );
1264 const QStringList inputOrder = dlg.inputOrder();
1265 beginUndoCommand( tr(
"Reorder Inputs" ) );
1266 mModel->setParameterOrder( inputOrder );
1271void QgsModelDesignerDialog::reorderOutputs()
1273 QgsModelOutputReorderDialog dlg(
this );
1274 dlg.setModel( mModel.get() );
1277 const QStringList outputOrder = dlg.outputOrder();
1278 beginUndoCommand( tr(
"Reorder Outputs" ) );
1279 mModel->setOutputOrder( outputOrder );
1280 mModel->setOutputGroup( dlg.outputGroup() );
1285bool QgsModelDesignerDialog::isDirty()
const
1287 return mHasChanged && mUndoStack->index() != -1;
1290void QgsModelDesignerDialog::fillInputsTree()
1293 std::unique_ptr< QTreeWidgetItem > parametersItem = std::make_unique< QTreeWidgetItem >();
1294 parametersItem->setText( 0, tr(
"Parameters" ) );
1298 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1305 std::unique_ptr< QTreeWidgetItem > paramItem = std::make_unique< QTreeWidgetItem >();
1306 paramItem->setText( 0, param->name() );
1307 paramItem->setData( 0, Qt::UserRole, param->id() );
1308 paramItem->setIcon( 0, icon );
1309 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1310 paramItem->setToolTip( 0, param->description() );
1311 parametersItem->addChild( paramItem.release() );
1314 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1315 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1323QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1326 , mChildId( childId )
1328 QHBoxLayout *hl =
new QHBoxLayout();
1329 hl->setContentsMargins( 0, 0, 0, 0 );
1331 mLineEdit =
new QLineEdit();
1332 mLineEdit->setEnabled(
false );
1333 hl->addWidget( mLineEdit, 1 );
1335 mToolButton =
new QToolButton();
1336 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1337 hl->addWidget( mToolButton );
1341 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1343 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1346void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1350 updateSummaryText();
1353void QgsModelChildDependenciesWidget::showDialog()
1355 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1357 QVariantList availableOptions;
1358 for (
const QgsProcessingModelChildDependency &dep : available )
1359 availableOptions << QVariant::fromValue( dep );
1360 QVariantList selectedOptions;
1361 for (
const QgsProcessingModelChildDependency &dep : mValue )
1362 selectedOptions << QVariant::fromValue( dep );
1367 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1368 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1370 widget->setValueFormatter( [ = ](
const QVariant & v ) -> QString
1372 const QgsProcessingModelChildDependency dep = v.value< QgsProcessingModelChildDependency >();
1374 const QString description = mModel->childAlgorithm( dep.childId ).description();
1375 if ( dep.conditionalBranch.isEmpty() )
1378 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1381 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [ = ]()
1383 QList< QgsProcessingModelChildDependency > res;
1384 for (
const QVariant &v : widget->selectedOptions() )
1386 res << v.value< QgsProcessingModelChildDependency >();
1395void QgsModelChildDependenciesWidget::updateSummaryText()
1397 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)
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.
This class is a composition of two QSettings instances:
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)