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