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 );
320 QToolButton *toolbuttonExportToScript =
new QToolButton();
321 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
322 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
323 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
324 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
325 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
327 mActionShowComments->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/ShowComments" ),
true ).toBool() );
328 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
331 mPanTool->setAction( mActionPan );
333 mToolsActionGroup->addAction( mActionPan );
334 connect( mActionPan, &QAction::triggered, mPanTool, [ = ] { mView->setTool( mPanTool ); } );
337 mSelectTool->setAction( mActionSelectMoveItem );
339 mToolsActionGroup->addAction( mActionSelectMoveItem );
340 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
342 mView->setTool( mSelectTool );
345 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [ = ](
const QString & text )
347 mIgnoreUndoStackChanges++;
348 mUndoStack->beginMacro( text );
349 mIgnoreUndoStackChanges--;
351 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [ = ]
353 mIgnoreUndoStackChanges++;
354 mUndoStack->endMacro();
355 mIgnoreUndoStackChanges--;
357 connect( mView, &QgsModelGraphicsView::beginCommand,
this, [ = ](
const QString & text )
359 beginUndoCommand( text );
361 connect( mView, &QgsModelGraphicsView::endCommand,
this, [ = ]
365 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [ = ]
370 connect( mActionAddGroupBox, &QAction::triggered,
this, [ = ]
372 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
373 QgsProcessingModelGroupBox group;
374 group.setPosition( viewCenter );
375 group.setDescription( tr(
"New Group" ) );
377 beginUndoCommand( tr(
"Add Group Box" ) );
378 model()->addGroupBox( group );
386 restoreState( settings.
value( QStringLiteral(
"ModelDesigner/state" ), QByteArray(),
QgsSettings::App ).toByteArray() );
389QgsModelDesignerDialog::~QgsModelDesignerDialog()
392 if ( !mPanelStatus.isEmpty() )
394 QStringList docksTitle;
395 QStringList docksActive;
397 for (
const auto &panel : mPanelStatus.toStdMap() )
399 if ( panel.second.isVisible )
400 docksTitle << panel.first;
401 if ( panel.second.isActive )
402 docksActive << panel.first;
416 mIgnoreUndoStackChanges++;
420void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
422 if ( checkForUnsavedChanges() )
428void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
int id )
430 if ( mBlockUndoCommands || !mUndoStack )
433 if ( mActiveCommand )
436 mActiveCommand = std::make_unique< QgsModelUndoCommand >( mModel.get(), text,
id );
439void QgsModelDesignerDialog::endUndoCommand()
441 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
444 mActiveCommand->saveAfterState();
445 mIgnoreUndoStackChanges++;
446 mUndoStack->push( mActiveCommand.release() );
447 mIgnoreUndoStackChanges--;
451QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
456void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
458 mModel.reset( model );
460 mGroupEdit->setText( mModel->group() );
461 mNameEdit->setText( mModel->displayName() );
463 updateVariablesGui();
465 mView->centerOn( 0, 0 );
468 mIgnoreUndoStackChanges++;
470 mIgnoreUndoStackChanges--;
475void QgsModelDesignerDialog::loadModel(
const QString &path )
477 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
478 if ( alg->fromFile( path ) )
481 alg->setSourceFilePath( path );
482 setModel( alg.release() );
487 QMessageBox::critical(
this, tr(
"Open Model" ), tr(
"The selected model could not be loaded.\n"
488 "See the log for more information." ) );
492void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
494 QgsModelGraphicsScene *oldScene = mScene;
497 mScene->setParent(
this );
498 mScene->setLastRunResult( mLastResult );
499 mScene->setModel( mModel.get() );
500 mScene->setMessageBar( mMessageBar );
502 const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
503 mView->setModelScene( mScene );
505 mSelectTool->resetCache();
506 mSelectTool->setScene( mScene );
508 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [ = ]
510 if ( mBlockRepaints )
515 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [ = ](
const QString & description,
int id ) { beginUndoCommand( description,
id ); } );
516 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [ = ] { endUndoCommand(); } );
517 connect( mScene, &QgsModelGraphicsScene::runFromChild,
this, &QgsModelDesignerDialog::runFromChild );
518 connect( mScene, &QgsModelGraphicsScene::runSelected,
this, &QgsModelDesignerDialog::runSelectedSteps );
519 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs,
this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
520 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog,
this, &QgsModelDesignerDialog::showChildAlgorithmLog );
522 mView->centerOn( center );
525 oldScene->deleteLater();
528void QgsModelDesignerDialog::activate()
532 setWindowState( windowState() & ~Qt::WindowMinimized );
536void QgsModelDesignerDialog::updateVariablesGui()
538 mBlockUndoCommands++;
540 std::unique_ptr< QgsExpressionContextScope > variablesScope = std::make_unique< QgsExpressionContextScope >( tr(
"Model Variables" ) );
541 const QVariantMap modelVars = mModel->variables();
542 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
544 variablesScope->setVariable( it.key(), it.value() );
547 variablesContext.
appendScope( variablesScope.release() );
548 mVariablesEditor->setContext( &variablesContext );
549 mVariablesEditor->setEditableScopeIndex( 0 );
551 mBlockUndoCommands--;
554void QgsModelDesignerDialog::setDirty(
bool dirty )
560bool QgsModelDesignerDialog::validateSave( SaveAction action )
564 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
566 case QgsModelDesignerDialog::SaveAction::SaveInProject:
567 if ( mNameEdit->text().trimmed().isEmpty() )
569 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
578bool QgsModelDesignerDialog::checkForUnsavedChanges()
582 QMessageBox::StandardButton ret = QMessageBox::question(
this, tr(
"Save Model?" ),
583 tr(
"There are unsaved changes in this model. Do you want to keep those?" ),
584 QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, QMessageBox::Cancel );
587 case QMessageBox::Save:
588 return saveModel(
false );
590 case QMessageBox::Discard:
605 mLastResult.mergeWith( result );
607 mScene->setLastRunResult( mLastResult );
610void QgsModelDesignerDialog::setModelName(
const QString &name )
612 mNameEdit->setText( name );
615void QgsModelDesignerDialog::zoomIn()
617 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
618 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
620 const double factor = settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
621 mView->scale( factor, factor );
622 mView->centerOn( point );
625void QgsModelDesignerDialog::zoomOut()
627 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
628 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
630 const double factor = 1.0 / settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
631 mView->scale( factor, factor );
632 mView->centerOn( point );
635void QgsModelDesignerDialog::zoomActual()
637 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
638 mView->resetTransform();
639 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
640 mView->centerOn( point );
643void QgsModelDesignerDialog::zoomFull()
645 QRectF totalRect = mView->scene()->itemsBoundingRect();
646 totalRect.adjust( -10, -10, 10, 10 );
647 mView->fitInView( totalRect, Qt::KeepAspectRatio );
650void QgsModelDesignerDialog::newModel()
652 if ( !checkForUnsavedChanges() )
655 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
657 setModel( alg.release() );
660void QgsModelDesignerDialog::exportToImage()
663 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
665 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ),
667 tr(
"PNG files (*.png *.PNG)" ) );
671 if ( filename.isEmpty() )
676 const QFileInfo saveFileInfo( filename );
679 repaintModel(
false );
681 QRectF totalRect = mView->scene()->itemsBoundingRect();
682 totalRect.adjust( -10, -10, 10, 10 );
683 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
685 QImage img( totalRect.width(), totalRect.height(),
686 QImage::Format_ARGB32_Premultiplied );
687 img.fill( Qt::white );
689 painter.setRenderHint( QPainter::Antialiasing );
690 painter.begin( &img );
691 mView->scene()->render( &painter, imageRect, totalRect );
694 img.save( filename );
696 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 );
697 repaintModel(
true );
700void QgsModelDesignerDialog::exportToPdf()
703 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
705 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ),
707 tr(
"PDF files (*.pdf *.PDF)" ) );
711 if ( filename.isEmpty() )
716 const QFileInfo saveFileInfo( filename );
719 repaintModel(
false );
721 QRectF totalRect = mView->scene()->itemsBoundingRect();
722 totalRect.adjust( -10, -10, 10, 10 );
723 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
725 QPdfWriter pdfWriter( filename );
727 const double scaleFactor = 96 / 25.4;
729 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ),
730 QPageLayout::Portrait,
731 QMarginsF( 0, 0, 0, 0 ) );
732 pageLayout.setMode( QPageLayout::FullPageMode );
733 pdfWriter.setPageLayout( pageLayout );
735 QPainter painter( &pdfWriter );
736 mView->scene()->render( &painter, printerRect, totalRect );
739 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as PDF to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(),
741 repaintModel(
true );
744void QgsModelDesignerDialog::exportToSvg()
747 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
749 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ),
751 tr(
"SVG files (*.svg *.SVG)" ) );
755 if ( filename.isEmpty() )
760 const QFileInfo saveFileInfo( filename );
763 repaintModel(
false );
765 QRectF totalRect = mView->scene()->itemsBoundingRect();
766 totalRect.adjust( -10, -10, 10, 10 );
767 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
770 svg.setFileName( filename );
771 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
772 svg.setViewBox( svgRect );
773 svg.setTitle( mModel->displayName() );
775 QPainter painter( &svg );
776 mView->scene()->render( &painter, svgRect, totalRect );
779 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 );
780 repaintModel(
true );
783void QgsModelDesignerDialog::exportAsPython()
786 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
788 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ),
790 tr(
"Processing scripts (*.py *.PY)" ) );
794 if ( filename.isEmpty() )
799 const QFileInfo saveFileInfo( filename );
804 QFile outFile( filename );
805 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
809 QTextStream fout( &outFile );
813 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 );
816void QgsModelDesignerDialog::toggleComments(
bool show )
820 repaintModel(
true );
823void QgsModelDesignerDialog::updateWindowTitle()
825 QString title = tr(
"Model Designer" );
826 if ( !mModel->name().isEmpty() )
827 title = mModel->group().isEmpty()
828 ? QStringLiteral(
"%1: %2" ).arg( title, mModel->name() )
829 : QStringLiteral(
"%1: %2 - %3" ).arg( title, mModel->group(), mModel->name() );
832 title.prepend(
'*' );
834 setWindowTitle( title );
837void QgsModelDesignerDialog::deleteSelected()
839 QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
843 if ( items.size() == 1 )
845 items.at( 0 )->deleteComponent();
849 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem * p1, QgsModelComponentGraphicItem * p2 )
852 if ( dynamic_cast< QgsModelCommentGraphicItem *>( p1 ) )
854 else if ( dynamic_cast< QgsModelCommentGraphicItem *>( p2 ) )
856 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p1 ) )
858 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p2 ) )
860 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p1 ) )
862 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p2 ) )
864 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p1 ) )
866 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p2 ) )
872 beginUndoCommand( tr(
"Delete Components" ) );
874 QVariant prevState = mModel->toVariant();
875 mBlockUndoCommands++;
876 mBlockRepaints =
true;
878 while ( !items.empty() )
880 QgsModelComponentGraphicItem *toDelete =
nullptr;
881 for ( QgsModelComponentGraphicItem *item : items )
883 if ( item->canDeleteComponent() )
896 toDelete->deleteComponent();
897 items.removeAll( toDelete );
902 mModel->loadVariant( prevState );
903 QMessageBox::warning(
nullptr, QObject::tr(
"Could not remove components" ),
904 QObject::tr(
"Components depend on the selected items.\n"
905 "Try to remove them before trying deleting these components." ) );
906 mBlockUndoCommands--;
907 mActiveCommand.reset();
911 mBlockUndoCommands--;
915 mBlockRepaints =
false;
919void QgsModelDesignerDialog::populateZoomToMenu()
922 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
924 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
926 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
927 connect( zoomAction, &QAction::triggered,
this, [ = ]
929 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
930 groupRect.adjust( -10, -10, 10, 10 );
931 mView->fitInView( groupRect, Qt::KeepAspectRatio );
932 mView->centerOn( item );
934 mGroupMenu->addAction( zoomAction );
939void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
941 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
942 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
946 mPanelStatus.clear();
948 for ( QDockWidget *dock : docks )
950 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
951 dock->setVisible(
false );
955 for ( QTabBar *tabBar : tabBars )
957 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
958 mPanelStatus[ currentTabTitle ].isActive =
true;
964 for ( QDockWidget *dock : docks )
966 if ( mPanelStatus.contains( dock->windowTitle() ) )
968 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
973 for ( QTabBar *tabBar : tabBars )
976 for (
int i = 0; i < tabBar->count(); ++i )
978 QString tabTitle = tabBar->tabText( i );
979 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
981 tabBar->setCurrentIndex( i );
985 mPanelStatus.clear();
989void QgsModelDesignerDialog::editHelp()
991 QgsProcessingHelpEditorDialog dialog(
this );
992 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
993 dialog.setAlgorithm( mModel.get() );
996 beginUndoCommand( tr(
"Edit Model Help" ) );
997 mModel->setHelpContent( dialog.helpContent() );
1002void QgsModelDesignerDialog::runSelectedSteps()
1004 QSet<QString> children;
1005 const QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
1006 for ( QgsModelComponentGraphicItem *item : items )
1008 if ( QgsProcessingModelChildAlgorithm *childAlgorithm =
dynamic_cast< QgsProcessingModelChildAlgorithm *
>( item->component() ) )
1010 children.insert( childAlgorithm->childId() );
1014 if ( children.isEmpty() )
1016 mMessageBar->pushWarning( QString(), tr(
"No steps are selected" ) );
1023void QgsModelDesignerDialog::runFromChild(
const QString &
id )
1025 QSet<QString> children = mModel->dependentChildAlgorithms(
id );
1026 children.insert(
id );
1030void QgsModelDesignerDialog::run(
const QSet<QString> &childAlgorithmSubset )
1033 const bool isValid = model()->validate( errors );
1036 QMessageBox messageBox;
1037 messageBox.setWindowTitle( tr(
"Model is Invalid" ) );
1038 messageBox.setIcon( QMessageBox::Icon::Warning );
1039 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?" ) );
1040 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1041 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1043 QString errorString;
1044 for (
const QString &error : std::as_const( errors ) )
1046 QString cleanedError = error;
1047 const thread_local QRegularExpression re( QStringLiteral(
"<[^>]*>" ) );
1048 cleanedError.replace( re, QString() );
1049 errorString += QStringLiteral(
"• %1\n" ).arg( cleanedError );
1052 messageBox.setDetailedText( errorString );
1053 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1057 if ( !childAlgorithmSubset.isEmpty() )
1059 for (
const QString &child : childAlgorithmSubset )
1062 const QSet< QString > requirements = mModel->dependsOnChildAlgorithms( child );
1063 for (
const QString &requirement : requirements )
1065 if ( !mLastResult.executedChildIds().contains( requirement ) )
1067 QMessageBox messageBox;
1068 messageBox.setWindowTitle( tr(
"Run Model" ) );
1069 messageBox.setIcon( QMessageBox::Icon::Warning );
1070 messageBox.setText( tr(
"Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1071 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1079 std::unique_ptr< QgsProcessingAlgorithmDialogBase > dialog( createExecutionDialog() );
1084 dialog->setParameters( mModel->designerParameterValues() );
1086 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmAboutToRun,
this, [
this, &childAlgorithmSubset](
QgsProcessingContext * context )
1088 if ( ! childAlgorithmSubset.empty() )
1091 std::unique_ptr< QgsProcessingModelInitialRunConfig > modelConfig = std::make_unique< QgsProcessingModelInitialRunConfig >();
1092 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1093 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1094 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1095 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1099 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1100 std::unique_ptr<QgsMapLayerStore> previousResultStore = std::make_unique< QgsMapLayerStore >();
1101 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1103 std::unique_ptr< QgsMapLayer > clone( it.value()->clone() );
1104 clone->setId( it.value()->id() );
1105 previousResultStore->addMapLayer( clone.release() );
1107 previousResultStore->moveToThread( nullptr );
1108 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1109 context->setModelInitialRunConfig( std::move( modelConfig ) );
1113 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmFinished,
this, [
this, &dialog](
bool,
const QVariantMap & )
1115 QgsProcessingContext *context = dialog->processingContext();
1117 setLastRunResult( context->modelResult() );
1119 mModel->setDesignerParameterValues( dialog->createProcessingParameters( QgsProcessingParametersGenerator::Flag::SkipDefaultValueParameters ) );
1122 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1123 mLayerStore.takeResultsFrom( *context );
1129void QgsModelDesignerDialog::showChildAlgorithmOutputs(
const QString &childId )
1131 const QString childDescription = mModel->childAlgorithm( childId ).description();
1134 const QVariantMap childAlgorithmOutputs = result.
outputs();
1135 if ( childAlgorithmOutputs.isEmpty() )
1137 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1144 mMessageBar->pushCritical( QString(), tr(
"Results cannot be shown for an invalid model component" ) );
1149 if ( outputParams.isEmpty() )
1152 QgsDebugError(
"Cannot show results for algorithms with no outputs" );
1156 bool foundResults =
false;
1159 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1160 if ( !output.isValid() )
1163 if ( output.type() == QVariant::String )
1167 QgsDebugMsgLevel( QStringLiteral(
"Loading previous result for %1: %2" ).arg( outputParam->name(), output.toString() ), 2 );
1169 std::unique_ptr< QgsMapLayer > layer( resultLayer->clone() );
1172 if ( outputParams.size() > 1 )
1173 baseName = tr(
"%1 — %2" ).arg( childDescription, outputParam->name() );
1175 baseName = childDescription;
1179 QString name = baseName;
1184 name = tr(
"%1 (%2)" ).arg( baseName ).arg( counter );
1187 layer->setName( name );
1190 foundResults =
true;
1195 QgsDebugError( QStringLiteral(
"Could not load previous result for %1: %2" ).arg( outputParam->name(), output.toString() ) );
1200 if ( !foundResults )
1202 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1207void QgsModelDesignerDialog::showChildAlgorithmLog(
const QString &childId )
1209 const QString childDescription = mModel->childAlgorithm( childId ).description();
1212 if ( result.
htmlLog().isEmpty() )
1214 mMessageBar->pushWarning( QString(), tr(
"No log is available for %1" ).arg( childDescription ) );
1219 m.setWindowTitle( childDescription );
1220 m.setCheckBoxVisible(
false );
1221 m.setMessageAsHtml( result.
htmlLog() );
1225void QgsModelDesignerDialog::validate()
1228 if ( model()->validate( issues ) )
1230 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
1235 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
1236 connect( detailsButton, &QPushButton::clicked, detailsButton, [ = ]
1239 dialog->
setTitle( tr(
"Model is Invalid" ) );
1241 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + QStringLiteral(
"<ul>" );
1242 for (
const QString &issue : issues )
1244 longMessage += QStringLiteral(
"<li>%1</li>" ).arg( issue );
1246 longMessage += QLatin1String(
"</ul>" );
1251 messageWidget->layout()->addWidget( detailsButton );
1252 mMessageBar->clearWidgets();
1257void QgsModelDesignerDialog::reorderInputs()
1259 QgsModelInputReorderDialog dlg(
this );
1260 dlg.setModel( mModel.get() );
1263 const QStringList inputOrder = dlg.inputOrder();
1264 beginUndoCommand( tr(
"Reorder Inputs" ) );
1265 mModel->setParameterOrder( inputOrder );
1270void QgsModelDesignerDialog::reorderOutputs()
1272 QgsModelOutputReorderDialog dlg(
this );
1273 dlg.setModel( mModel.get() );
1276 const QStringList outputOrder = dlg.outputOrder();
1277 beginUndoCommand( tr(
"Reorder Outputs" ) );
1278 mModel->setOutputOrder( outputOrder );
1279 mModel->setOutputGroup( dlg.outputGroup() );
1284bool QgsModelDesignerDialog::isDirty()
const
1286 return mHasChanged && mUndoStack->index() != -1;
1289void QgsModelDesignerDialog::fillInputsTree()
1292 std::unique_ptr< QTreeWidgetItem > parametersItem = std::make_unique< QTreeWidgetItem >();
1293 parametersItem->setText( 0, tr(
"Parameters" ) );
1297 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1304 std::unique_ptr< QTreeWidgetItem > paramItem = std::make_unique< QTreeWidgetItem >();
1305 paramItem->setText( 0, param->name() );
1306 paramItem->setData( 0, Qt::UserRole, param->id() );
1307 paramItem->setIcon( 0, icon );
1308 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1309 paramItem->setToolTip( 0, param->description() );
1310 parametersItem->addChild( paramItem.release() );
1313 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1314 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1322QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1325 , mChildId( childId )
1327 QHBoxLayout *hl =
new QHBoxLayout();
1328 hl->setContentsMargins( 0, 0, 0, 0 );
1330 mLineEdit =
new QLineEdit();
1331 mLineEdit->setEnabled(
false );
1332 hl->addWidget( mLineEdit, 1 );
1334 mToolButton =
new QToolButton();
1335 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1336 hl->addWidget( mToolButton );
1340 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1342 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1345void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1349 updateSummaryText();
1352void QgsModelChildDependenciesWidget::showDialog()
1354 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1356 QVariantList availableOptions;
1357 for (
const QgsProcessingModelChildDependency &dep : available )
1358 availableOptions << QVariant::fromValue( dep );
1359 QVariantList selectedOptions;
1360 for (
const QgsProcessingModelChildDependency &dep : mValue )
1361 selectedOptions << QVariant::fromValue( dep );
1366 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1367 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1369 widget->setValueFormatter( [ = ](
const QVariant & v ) -> QString
1371 const QgsProcessingModelChildDependency dep = v.value< QgsProcessingModelChildDependency >();
1373 const QString description = mModel->childAlgorithm( dep.childId ).description();
1374 if ( dep.conditionalBranch.isEmpty() )
1377 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1380 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [ = ]()
1382 QList< QgsProcessingModelChildDependency > res;
1383 for (
const QVariant &v : widget->selectedOptions() )
1385 res << v.value< QgsProcessingModelChildDependency >();
1394void QgsModelChildDependenciesWidget::updateSummaryText()
1396 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)