44#include <QKeySequence>
47#include <QSvgGenerator>
55#include <QActionGroup>
60QgsModelerToolboxModel::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 ) )
72 f = f | Qt::ItemIsDragEnabled;
77Qt::DropActions QgsModelerToolboxModel::supportedDragActions()
const
79 return Qt::CopyAction;
84QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
85 : QMainWindow( parent, flags )
86 , mToolsActionGroup( new QActionGroup( this ) )
94 setAttribute( Qt::WA_DeleteOnClose );
95 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
96 setWindowFlags( Qt::WindowMinimizeButtonHint |
97 Qt::WindowMaximizeButtonHint |
98 Qt::WindowCloseButtonHint );
102 mModel = std::make_unique< QgsProcessingModelAlgorithm >();
105 mUndoStack =
new QUndoStack(
this );
106 connect( mUndoStack, &QUndoStack::indexChanged,
this, [ = ]
108 if ( mIgnoreUndoStackChanges )
111 mBlockUndoCommands++;
112 updateVariablesGui();
113 mGroupEdit->setText( mModel->group() );
114 mNameEdit->setText( mModel->displayName() );
115 mBlockUndoCommands--;
119 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
120 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
121 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
122 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
124 mAlgorithmsTree->header()->setVisible(
false );
125 mAlgorithmSearchEdit->setShowSearchIcon(
true );
126 mAlgorithmSearchEdit->setPlaceholderText( tr(
"Search…" ) );
127 connect( mAlgorithmSearchEdit, &QgsFilterLineEdit::textChanged, mAlgorithmsTree, &QgsProcessingToolboxTreeView::setFilterString );
129 mInputsTreeWidget->header()->setVisible(
false );
130 mInputsTreeWidget->setAlternatingRowColors(
true );
131 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
132 mInputsTreeWidget->setDropIndicatorShown(
true );
134 mNameEdit->setPlaceholderText( tr(
"Enter model name here" ) );
135 mGroupEdit->setPlaceholderText( tr(
"Enter group name here" ) );
138 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
139 mainLayout->insertWidget( 0, mMessageBar );
141 mView->setAcceptDrops(
true );
144 connect( mActionClose, &QAction::triggered,
this, &QWidget::close );
145 connect( mActionNew, &QAction::triggered,
this, &QgsModelDesignerDialog::newModel );
146 connect( mActionZoomIn, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomIn );
147 connect( mActionZoomOut, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomOut );
148 connect( mActionZoomActual, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomActual );
149 connect( mActionZoomToItems, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomFull );
150 connect( mActionExportImage, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToImage );
151 connect( mActionExportPdf, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToPdf );
152 connect( mActionExportSvg, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToSvg );
153 connect( mActionExportPython, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsPython );
154 connect( mActionSave, &QAction::triggered,
this, [ = ] { saveModel(
false ); } );
155 connect( mActionSaveAs, &QAction::triggered,
this, [ = ] { saveModel(
true ); } );
156 connect( mActionDeleteComponents, &QAction::triggered,
this, &QgsModelDesignerDialog::deleteSelected );
157 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
158 connect( mActionValidate, &QAction::triggered,
this, &QgsModelDesignerDialog::validate );
159 connect( mActionReorderInputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderInputs );
160 connect( mActionReorderOutputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderOutputs );
161 connect( mActionEditHelp, &QAction::triggered,
this, &QgsModelDesignerDialog::editHelp );
162 connect( mReorderInputsButton, &QPushButton::clicked,
this, &QgsModelDesignerDialog::reorderInputs );
163 connect( mActionRun, &QAction::triggered,
this, [
this] { run(); } );
164 connect( mActionRunSelectedSteps, &QAction::triggered,
this, &QgsModelDesignerDialog::runSelectedSteps );
166 mActionSnappingEnabled->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ),
false ).toBool() );
167 connect( mActionSnappingEnabled, &QAction::toggled,
this, [ = ](
bool enabled )
169 mView->snapper()->setSnapToGrid( enabled );
170 QgsSettings().
setValue( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ), enabled );
172 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
174 connect( mActionSelectAll, &QAction::triggered,
this, [ = ]
179 QStringList docksTitle = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), QStringList(),
QgsSettings::App ).toStringList();
180 QStringList docksActive = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), QStringList(),
QgsSettings::App ).toStringList();
181 if ( !docksTitle.isEmpty() )
183 for (
const auto &title : docksTitle )
185 mPanelStatus.insert( title, PanelStatus(
true, docksActive.contains( title ) ) );
188 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
189 connect( mActionHidePanels, &QAction::toggled,
this, &QgsModelDesignerDialog::setPanelVisibility );
191 mUndoAction = mUndoStack->createUndoAction(
this );
193 mUndoAction->setShortcuts( QKeySequence::Undo );
194 mRedoAction = mUndoStack->createRedoAction(
this );
196 mRedoAction->setShortcuts( QKeySequence::Redo );
198 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
199 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
200 mMenuEdit->insertSeparator( mActionDeleteComponents );
201 mToolbar->insertAction( mActionZoomIn, mUndoAction );
202 mToolbar->insertAction( mActionZoomIn, mRedoAction );
203 mToolbar->insertSeparator( mActionZoomIn );
205 mGroupMenu =
new QMenu( tr(
"Zoom To" ),
this );
206 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
207 connect( mGroupMenu, &QMenu::aboutToShow,
this, &QgsModelDesignerDialog::populateZoomToMenu );
211 mActionCut =
new QAction( tr(
"Cu&t" ),
this );
212 mActionCut->setShortcuts( QKeySequence::Cut );
213 mActionCut->setStatusTip( tr(
"Cut" ) );
215 connect( mActionCut, &QAction::triggered,
this, [ = ]
217 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut );
220 mActionCopy =
new QAction( tr(
"&Copy" ),
this );
221 mActionCopy->setShortcuts( QKeySequence::Copy );
222 mActionCopy->setStatusTip( tr(
"Copy" ) );
224 connect( mActionCopy, &QAction::triggered,
this, [ = ]
226 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy );
229 mActionPaste =
new QAction( tr(
"&Paste" ),
this );
230 mActionPaste->setShortcuts( QKeySequence::Paste );
231 mActionPaste->setStatusTip( tr(
"Paste" ) );
233 connect( mActionPaste, &QAction::triggered,
this, [ = ]
235 mView->pasteItems( QgsModelGraphicsView::PasteModeCursor );
237 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
238 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
239 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
240 mMenuEdit->insertSeparator( mActionDeleteComponents );
242 mAlgorithmsModel =
new QgsModelerToolboxModel(
this );
243 mAlgorithmsTree->setToolboxProxyModel( mAlgorithmsModel );
246 if ( settings.
value( QStringLiteral(
"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES" ),
false ).toBool() )
250 mAlgorithmsTree->setFilters( filters );
251 mAlgorithmsTree->setDragDropMode( QTreeWidget::DragOnly );
252 mAlgorithmsTree->setDropIndicatorShown(
true );
254 connect( mView, &QgsModelGraphicsView::algorithmDropped,
this, [ = ](
const QString & algorithmId,
const QPointF & pos )
256 addAlgorithm( algorithmId, pos );
258 connect( mAlgorithmsTree, &QgsProcessingToolboxTreeView::doubleClicked,
this, [ = ]()
260 if ( mAlgorithmsTree->selectedAlgorithm() )
261 addAlgorithm( mAlgorithmsTree->selectedAlgorithm()->id(), QPointF() );
263 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked,
this, [ = ](
const QModelIndex & )
265 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
266 addInput( parameterType, QPointF() );
269 connect( mView, &QgsModelGraphicsView::inputDropped,
this, &QgsModelDesignerDialog::addInput );
272 QShortcut *ctrlEquals =
new QShortcut( QKeySequence( QStringLiteral(
"Ctrl+=" ) ),
this );
273 connect( ctrlEquals, &QShortcut::activated,
this, &QgsModelDesignerDialog::zoomIn );
276 mUndoDock->setObjectName( QStringLiteral(
"UndoDock" ) );
277 mUndoView =
new QUndoView( mUndoStack,
this );
278 mUndoDock->setWidget( mUndoView );
279 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
280 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
282 tabifyDockWidget( mUndoDock, mPropertiesDock );
283 tabifyDockWidget( mVariablesDock, mPropertiesDock );
284 mPropertiesDock->raise();
285 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
286 mInputsDock->raise();
292 beginUndoCommand( tr(
"Change Model Variables" ) );
293 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
297 connect( mNameEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & name )
301 beginUndoCommand( tr(
"Change Model Name" ), NameChanged );
302 mModel->setName( name );
307 connect( mGroupEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & group )
311 beginUndoCommand( tr(
"Change Model Group" ), GroupChanged );
312 mModel->setGroup( group );
319 QToolButton *toolbuttonExportToScript =
new QToolButton();
320 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
321 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
322 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
323 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
324 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
326 mActionShowComments->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/ShowComments" ),
true ).toBool() );
327 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
330 mPanTool->setAction( mActionPan );
332 mToolsActionGroup->addAction( mActionPan );
333 connect( mActionPan, &QAction::triggered, mPanTool, [ = ] { mView->setTool( mPanTool ); } );
336 mSelectTool->setAction( mActionSelectMoveItem );
338 mToolsActionGroup->addAction( mActionSelectMoveItem );
339 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
341 mView->setTool( mSelectTool );
344 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [ = ](
const QString & text )
346 mIgnoreUndoStackChanges++;
347 mUndoStack->beginMacro( text );
348 mIgnoreUndoStackChanges--;
350 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [ = ]
352 mIgnoreUndoStackChanges++;
353 mUndoStack->endMacro();
354 mIgnoreUndoStackChanges--;
356 connect( mView, &QgsModelGraphicsView::beginCommand,
this, [ = ](
const QString & text )
358 beginUndoCommand( text );
360 connect( mView, &QgsModelGraphicsView::endCommand,
this, [ = ]
364 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [ = ]
369 connect( mActionAddGroupBox, &QAction::triggered,
this, [ = ]
371 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
372 QgsProcessingModelGroupBox group;
373 group.setPosition( viewCenter );
374 group.setDescription( tr(
"New Group" ) );
376 beginUndoCommand( tr(
"Add Group Box" ) );
377 model()->addGroupBox( group );
385 restoreState( settings.
value( QStringLiteral(
"ModelDesigner/state" ), QByteArray(),
QgsSettings::App ).toByteArray() );
388QgsModelDesignerDialog::~QgsModelDesignerDialog()
391 if ( !mPanelStatus.isEmpty() )
393 QStringList docksTitle;
394 QStringList docksActive;
396 for (
const auto &panel : mPanelStatus.toStdMap() )
398 if ( panel.second.isVisible )
399 docksTitle << panel.first;
400 if ( panel.second.isActive )
401 docksActive << panel.first;
415 mIgnoreUndoStackChanges++;
419void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
421 if ( checkForUnsavedChanges() )
427void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
int id )
429 if ( mBlockUndoCommands || !mUndoStack )
432 if ( mActiveCommand )
435 mActiveCommand = std::make_unique< QgsModelUndoCommand >( mModel.get(), text,
id );
438void QgsModelDesignerDialog::endUndoCommand()
440 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
443 mActiveCommand->saveAfterState();
444 mIgnoreUndoStackChanges++;
445 mUndoStack->push( mActiveCommand.release() );
446 mIgnoreUndoStackChanges--;
450QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
455void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
457 mModel.reset( model );
459 mGroupEdit->setText( mModel->group() );
460 mNameEdit->setText( mModel->displayName() );
462 updateVariablesGui();
464 mView->centerOn( 0, 0 );
467 mIgnoreUndoStackChanges++;
469 mIgnoreUndoStackChanges--;
474void QgsModelDesignerDialog::loadModel(
const QString &path )
476 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
477 if ( alg->fromFile( path ) )
480 alg->setSourceFilePath( path );
481 setModel( alg.release() );
486 QMessageBox::critical(
this, tr(
"Open Model" ), tr(
"The selected model could not be loaded.\n"
487 "See the log for more information." ) );
491void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
493 QgsModelGraphicsScene *oldScene = mScene;
496 mScene->setParent(
this );
497 mScene->setLastRunResult( mLastResult );
498 mScene->setModel( mModel.get() );
499 mScene->setMessageBar( mMessageBar );
501 const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
502 mView->setModelScene( mScene );
504 mSelectTool->resetCache();
505 mSelectTool->setScene( mScene );
507 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [ = ]
509 if ( mBlockRepaints )
514 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [ = ](
const QString & description,
int id ) { beginUndoCommand( description,
id ); } );
515 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [ = ] { endUndoCommand(); } );
516 connect( mScene, &QgsModelGraphicsScene::runFromChild,
this, &QgsModelDesignerDialog::runFromChild );
517 connect( mScene, &QgsModelGraphicsScene::runSelected,
this, &QgsModelDesignerDialog::runSelectedSteps );
518 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs,
this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
519 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog,
this, &QgsModelDesignerDialog::showChildAlgorithmLog );
521 mView->centerOn( center );
524 oldScene->deleteLater();
527void QgsModelDesignerDialog::activate()
531 setWindowState( windowState() & ~Qt::WindowMinimized );
535void QgsModelDesignerDialog::updateVariablesGui()
537 mBlockUndoCommands++;
539 std::unique_ptr< QgsExpressionContextScope > variablesScope = std::make_unique< QgsExpressionContextScope >( tr(
"Model Variables" ) );
540 const QVariantMap modelVars = mModel->variables();
541 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
543 variablesScope->setVariable( it.key(), it.value() );
546 variablesContext.
appendScope( variablesScope.release() );
547 mVariablesEditor->setContext( &variablesContext );
548 mVariablesEditor->setEditableScopeIndex( 0 );
550 mBlockUndoCommands--;
553void QgsModelDesignerDialog::setDirty(
bool dirty )
559bool QgsModelDesignerDialog::validateSave( SaveAction action )
563 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
565 case QgsModelDesignerDialog::SaveAction::SaveInProject:
566 if ( mNameEdit->text().trimmed().isEmpty() )
568 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
577bool QgsModelDesignerDialog::checkForUnsavedChanges()
581 QMessageBox::StandardButton ret = QMessageBox::question(
this, tr(
"Save Model?" ),
582 tr(
"There are unsaved changes in this model. Do you want to keep those?" ),
583 QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, QMessageBox::Cancel );
586 case QMessageBox::Save:
587 return saveModel(
false );
589 case QMessageBox::Discard:
604 mLastResult.mergeWith( result );
606 mScene->setLastRunResult( mLastResult );
609void QgsModelDesignerDialog::setModelName(
const QString &name )
611 mNameEdit->setText( name );
614void QgsModelDesignerDialog::zoomIn()
616 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
617 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
619 const double factor = settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
620 mView->scale( factor, factor );
621 mView->centerOn( point );
624void QgsModelDesignerDialog::zoomOut()
626 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
627 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
629 const double factor = 1.0 / settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
630 mView->scale( factor, factor );
631 mView->centerOn( point );
634void QgsModelDesignerDialog::zoomActual()
636 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
637 mView->resetTransform();
638 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
639 mView->centerOn( point );
642void QgsModelDesignerDialog::zoomFull()
644 QRectF totalRect = mView->scene()->itemsBoundingRect();
645 totalRect.adjust( -10, -10, 10, 10 );
646 mView->fitInView( totalRect, Qt::KeepAspectRatio );
649void QgsModelDesignerDialog::newModel()
651 if ( !checkForUnsavedChanges() )
654 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
656 setModel( alg.release() );
659void QgsModelDesignerDialog::exportToImage()
662 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
664 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ),
666 tr(
"PNG files (*.png *.PNG)" ) );
670 if ( filename.isEmpty() )
675 const QFileInfo saveFileInfo( filename );
678 repaintModel(
false );
680 QRectF totalRect = mView->scene()->itemsBoundingRect();
681 totalRect.adjust( -10, -10, 10, 10 );
682 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
684 QImage img( totalRect.width(), totalRect.height(),
685 QImage::Format_ARGB32_Premultiplied );
686 img.fill( Qt::white );
688 painter.setRenderHint( QPainter::Antialiasing );
689 painter.begin( &img );
690 mView->scene()->render( &painter, imageRect, totalRect );
693 img.save( filename );
695 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 );
696 repaintModel(
true );
699void QgsModelDesignerDialog::exportToPdf()
702 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
704 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ),
706 tr(
"PDF files (*.pdf *.PDF)" ) );
710 if ( filename.isEmpty() )
715 const QFileInfo saveFileInfo( filename );
718 repaintModel(
false );
720 QRectF totalRect = mView->scene()->itemsBoundingRect();
721 totalRect.adjust( -10, -10, 10, 10 );
722 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
724 QPdfWriter pdfWriter( filename );
726 const double scaleFactor = 96 / 25.4;
728 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ),
729 QPageLayout::Portrait,
730 QMarginsF( 0, 0, 0, 0 ) );
731 pageLayout.setMode( QPageLayout::FullPageMode );
732 pdfWriter.setPageLayout( pageLayout );
734 QPainter painter( &pdfWriter );
735 mView->scene()->render( &painter, printerRect, totalRect );
738 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as PDF to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(),
740 repaintModel(
true );
743void QgsModelDesignerDialog::exportToSvg()
746 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
748 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ),
750 tr(
"SVG files (*.svg *.SVG)" ) );
754 if ( filename.isEmpty() )
759 const QFileInfo saveFileInfo( filename );
762 repaintModel(
false );
764 QRectF totalRect = mView->scene()->itemsBoundingRect();
765 totalRect.adjust( -10, -10, 10, 10 );
766 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
769 svg.setFileName( filename );
770 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
771 svg.setViewBox( svgRect );
772 svg.setTitle( mModel->displayName() );
774 QPainter painter( &svg );
775 mView->scene()->render( &painter, svgRect, totalRect );
778 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 );
779 repaintModel(
true );
782void QgsModelDesignerDialog::exportAsPython()
785 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
787 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ),
789 tr(
"Processing scripts (*.py *.PY)" ) );
793 if ( filename.isEmpty() )
798 const QFileInfo saveFileInfo( filename );
803 QFile outFile( filename );
804 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
808 QTextStream fout( &outFile );
812 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 );
815void QgsModelDesignerDialog::toggleComments(
bool show )
819 repaintModel(
true );
822void QgsModelDesignerDialog::updateWindowTitle()
824 QString title = tr(
"Model Designer" );
825 if ( !mModel->name().isEmpty() )
826 title = QStringLiteral(
"%1 - %2" ).arg( title, mModel->name() );
829 title.prepend(
'*' );
831 setWindowTitle( title );
834void QgsModelDesignerDialog::deleteSelected()
836 QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
840 if ( items.size() == 1 )
842 items.at( 0 )->deleteComponent();
846 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem * p1, QgsModelComponentGraphicItem * p2 )
849 if ( dynamic_cast< QgsModelCommentGraphicItem *>( p1 ) )
851 else if ( dynamic_cast< QgsModelCommentGraphicItem *>( p2 ) )
853 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p1 ) )
855 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p2 ) )
857 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p1 ) )
859 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p2 ) )
861 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p1 ) )
863 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p2 ) )
869 beginUndoCommand( tr(
"Delete Components" ) );
871 QVariant prevState = mModel->toVariant();
872 mBlockUndoCommands++;
873 mBlockRepaints =
true;
875 while ( !items.empty() )
877 QgsModelComponentGraphicItem *toDelete =
nullptr;
878 for ( QgsModelComponentGraphicItem *item : items )
880 if ( item->canDeleteComponent() )
893 toDelete->deleteComponent();
894 items.removeAll( toDelete );
899 mModel->loadVariant( prevState );
900 QMessageBox::warning(
nullptr, QObject::tr(
"Could not remove components" ),
901 QObject::tr(
"Components depend on the selected items.\n"
902 "Try to remove them before trying deleting these components." ) );
903 mBlockUndoCommands--;
904 mActiveCommand.reset();
908 mBlockUndoCommands--;
912 mBlockRepaints =
false;
916void QgsModelDesignerDialog::populateZoomToMenu()
919 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
921 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
923 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
924 connect( zoomAction, &QAction::triggered,
this, [ = ]
926 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
927 groupRect.adjust( -10, -10, 10, 10 );
928 mView->fitInView( groupRect, Qt::KeepAspectRatio );
929 mView->centerOn( item );
931 mGroupMenu->addAction( zoomAction );
936void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
938 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
939 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
943 mPanelStatus.clear();
945 for ( QDockWidget *dock : docks )
947 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
948 dock->setVisible(
false );
952 for ( QTabBar *tabBar : tabBars )
954 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
955 mPanelStatus[ currentTabTitle ].isActive =
true;
961 for ( QDockWidget *dock : docks )
963 if ( mPanelStatus.contains( dock->windowTitle() ) )
965 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
970 for ( QTabBar *tabBar : tabBars )
973 for (
int i = 0; i < tabBar->count(); ++i )
975 QString tabTitle = tabBar->tabText( i );
976 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
978 tabBar->setCurrentIndex( i );
982 mPanelStatus.clear();
986void QgsModelDesignerDialog::editHelp()
988 QgsProcessingHelpEditorDialog dialog(
this );
989 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
990 dialog.setAlgorithm( mModel.get() );
993 beginUndoCommand( tr(
"Edit Model Help" ) );
994 mModel->setHelpContent( dialog.helpContent() );
999void QgsModelDesignerDialog::runSelectedSteps()
1001 QSet<QString> children;
1002 const QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
1003 for ( QgsModelComponentGraphicItem *item : items )
1005 if ( QgsProcessingModelChildAlgorithm *childAlgorithm =
dynamic_cast< QgsProcessingModelChildAlgorithm *
>( item->component() ) )
1007 children.insert( childAlgorithm->childId() );
1011 if ( children.isEmpty() )
1013 mMessageBar->pushWarning( QString(), tr(
"No steps are selected" ) );
1020void QgsModelDesignerDialog::runFromChild(
const QString &
id )
1022 QSet<QString> children = mModel->dependentChildAlgorithms(
id );
1023 children.insert(
id );
1027void QgsModelDesignerDialog::run(
const QSet<QString> &childAlgorithmSubset )
1030 const bool isValid = model()->validate( errors );
1033 QMessageBox messageBox;
1034 messageBox.setWindowTitle( tr(
"Model is Invalid" ) );
1035 messageBox.setIcon( QMessageBox::Icon::Warning );
1036 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?" ) );
1037 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1038 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1040 QString errorString;
1041 for (
const QString &error : std::as_const( errors ) )
1043 QString cleanedError = error;
1044 const thread_local QRegularExpression re( QStringLiteral(
"<[^>]*>" ) );
1045 cleanedError.replace( re, QString() );
1046 errorString += QStringLiteral(
"• %1\n" ).arg( cleanedError );
1049 messageBox.setDetailedText( errorString );
1050 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1054 if ( !childAlgorithmSubset.isEmpty() )
1056 for (
const QString &child : childAlgorithmSubset )
1059 const QSet< QString > requirements = mModel->dependsOnChildAlgorithms( child );
1060 for (
const QString &requirement : requirements )
1062 if ( !mLastResult.executedChildIds().contains( requirement ) )
1064 QMessageBox messageBox;
1065 messageBox.setWindowTitle( tr(
"Run Model" ) );
1066 messageBox.setIcon( QMessageBox::Icon::Warning );
1067 messageBox.setText( tr(
"Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1068 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1076 std::unique_ptr< QgsProcessingAlgorithmDialogBase > dialog( createExecutionDialog() );
1081 dialog->setParameters( mModel->designerParameterValues() );
1083 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmAboutToRun,
this, [
this, &childAlgorithmSubset](
QgsProcessingContext * context )
1085 if ( ! childAlgorithmSubset.empty() )
1088 std::unique_ptr< QgsProcessingModelInitialRunConfig > modelConfig = std::make_unique< QgsProcessingModelInitialRunConfig >();
1089 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1090 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1091 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1092 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1096 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1097 std::unique_ptr<QgsMapLayerStore> previousResultStore = std::make_unique< QgsMapLayerStore >();
1098 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1100 std::unique_ptr< QgsMapLayer > clone( it.value()->clone() );
1101 clone->setId( it.value()->id() );
1102 previousResultStore->addMapLayer( clone.release() );
1104 previousResultStore->moveToThread( nullptr );
1105 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1106 context->setModelInitialRunConfig( std::move( modelConfig ) );
1110 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmFinished,
this, [
this, &dialog](
bool,
const QVariantMap & )
1112 QgsProcessingContext *context = dialog->processingContext();
1114 setLastRunResult( context->modelResult() );
1116 mModel->setDesignerParameterValues( dialog->createProcessingParameters( QgsProcessingParametersGenerator::Flag::SkipDefaultValueParameters ) );
1119 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1120 mLayerStore.takeResultsFrom( *context );
1126void QgsModelDesignerDialog::showChildAlgorithmOutputs(
const QString &childId )
1128 const QString childDescription = mModel->childAlgorithm( childId ).description();
1131 const QVariantMap childAlgorithmOutputs = result.
outputs();
1132 if ( childAlgorithmOutputs.isEmpty() )
1134 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1141 mMessageBar->pushCritical( QString(), tr(
"Results cannot be shown for an invalid model component" ) );
1146 if ( outputParams.isEmpty() )
1149 QgsDebugError(
"Cannot show results for algorithms with no outputs" );
1153 bool foundResults =
false;
1156 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1157 if ( !output.isValid() )
1160 if ( output.type() == QVariant::String )
1164 QgsDebugMsgLevel( QStringLiteral(
"Loading previous result for %1: %2" ).arg( outputParam->name(), output.toString() ), 2 );
1166 std::unique_ptr< QgsMapLayer > layer( resultLayer->clone() );
1169 if ( outputParams.size() > 1 )
1170 baseName = tr(
"%1 — %2" ).arg( childDescription, outputParam->name() );
1172 baseName = childDescription;
1176 QString name = baseName;
1181 name = tr(
"%1 (%2)" ).arg( baseName ).arg( counter );
1184 layer->setName( name );
1187 foundResults =
true;
1192 QgsDebugError( QStringLiteral(
"Could not load previous result for %1: %2" ).arg( outputParam->name(), output.toString() ) );
1197 if ( !foundResults )
1199 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1204void QgsModelDesignerDialog::showChildAlgorithmLog(
const QString &childId )
1206 const QString childDescription = mModel->childAlgorithm( childId ).description();
1209 if ( result.
htmlLog().isEmpty() )
1211 mMessageBar->pushWarning( QString(), tr(
"No log is available for %1" ).arg( childDescription ) );
1216 m.setWindowTitle( childDescription );
1217 m.setCheckBoxVisible(
false );
1218 m.setMessageAsHtml( result.
htmlLog() );
1222void QgsModelDesignerDialog::validate()
1225 if ( model()->validate( issues ) )
1227 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
1232 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
1233 connect( detailsButton, &QPushButton::clicked, detailsButton, [ = ]
1236 dialog->
setTitle( tr(
"Model is Invalid" ) );
1238 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + QStringLiteral(
"<ul>" );
1239 for (
const QString &issue : issues )
1241 longMessage += QStringLiteral(
"<li>%1</li>" ).arg( issue );
1243 longMessage += QLatin1String(
"</ul>" );
1248 messageWidget->layout()->addWidget( detailsButton );
1249 mMessageBar->clearWidgets();
1254void QgsModelDesignerDialog::reorderInputs()
1256 QgsModelInputReorderDialog dlg(
this );
1257 dlg.setModel( mModel.get() );
1260 const QStringList inputOrder = dlg.inputOrder();
1261 beginUndoCommand( tr(
"Reorder Inputs" ) );
1262 mModel->setParameterOrder( inputOrder );
1267void QgsModelDesignerDialog::reorderOutputs()
1269 QgsModelOutputReorderDialog dlg(
this );
1270 dlg.setModel( mModel.get() );
1273 const QStringList outputOrder = dlg.outputOrder();
1274 beginUndoCommand( tr(
"Reorder Outputs" ) );
1275 mModel->setOutputOrder( outputOrder );
1276 mModel->setOutputGroup( dlg.outputGroup() );
1281bool QgsModelDesignerDialog::isDirty()
const
1283 return mHasChanged && mUndoStack->index() != -1;
1286void QgsModelDesignerDialog::fillInputsTree()
1289 std::unique_ptr< QTreeWidgetItem > parametersItem = std::make_unique< QTreeWidgetItem >();
1290 parametersItem->setText( 0, tr(
"Parameters" ) );
1294 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1301 std::unique_ptr< QTreeWidgetItem > paramItem = std::make_unique< QTreeWidgetItem >();
1302 paramItem->setText( 0, param->name() );
1303 paramItem->setData( 0, Qt::UserRole, param->id() );
1304 paramItem->setIcon( 0, icon );
1305 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1306 paramItem->setToolTip( 0, param->description() );
1307 parametersItem->addChild( paramItem.release() );
1310 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1311 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1319QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1322 , mChildId( childId )
1324 QHBoxLayout *hl =
new QHBoxLayout();
1325 hl->setContentsMargins( 0, 0, 0, 0 );
1327 mLineEdit =
new QLineEdit();
1328 mLineEdit->setEnabled(
false );
1329 hl->addWidget( mLineEdit, 1 );
1331 mToolButton =
new QToolButton();
1332 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1333 hl->addWidget( mToolButton );
1337 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1339 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1342void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1346 updateSummaryText();
1349void QgsModelChildDependenciesWidget::showDialog()
1351 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1353 QVariantList availableOptions;
1354 for (
const QgsProcessingModelChildDependency &dep : available )
1355 availableOptions << QVariant::fromValue( dep );
1356 QVariantList selectedOptions;
1357 for (
const QgsProcessingModelChildDependency &dep : mValue )
1358 selectedOptions << QVariant::fromValue( dep );
1363 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1364 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1366 widget->setValueFormatter( [ = ](
const QVariant & v ) -> QString
1368 const QgsProcessingModelChildDependency dep = v.value< QgsProcessingModelChildDependency >();
1370 const QString description = mModel->childAlgorithm( dep.childId ).description();
1371 if ( dep.conditionalBranch.isEmpty() )
1374 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1377 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [ = ]()
1379 QList< QgsProcessingModelChildDependency > res;
1380 for (
const QVariant &v : widget->selectedOptions() )
1382 res << v.value< QgsProcessingModelChildDependency >();
1391void QgsModelChildDependenciesWidget::updateSummaryText()
1393 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)