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 ) )
72 f = f | Qt::ItemIsDragEnabled;
77Qt::DropActions QgsModelerToolboxModel::supportedDragActions()
const
79 return Qt::CopyAction;
83QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
84 : QMainWindow( parent, flags )
85 , mToolsActionGroup( new QActionGroup( this ) )
93 setAttribute( Qt::WA_DeleteOnClose );
94 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
95 setWindowFlags( Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint );
99 mModel = std::make_unique<QgsProcessingModelAlgorithm>();
102 mUndoStack =
new QUndoStack(
this );
103 connect( mUndoStack, &QUndoStack::indexChanged,
this, [=] {
104 if ( mIgnoreUndoStackChanges )
107 mBlockUndoCommands++;
108 updateVariablesGui();
109 mGroupEdit->setText( mModel->group() );
110 mNameEdit->setText( mModel->displayName() );
111 mBlockUndoCommands--;
115 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
116 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
117 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
118 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
120 mAlgorithmsTree->header()->setVisible(
false );
121 mAlgorithmSearchEdit->setShowSearchIcon(
true );
122 mAlgorithmSearchEdit->setPlaceholderText( tr(
"Search…" ) );
123 connect( mAlgorithmSearchEdit, &QgsFilterLineEdit::textChanged, mAlgorithmsTree, &QgsProcessingToolboxTreeView::setFilterString );
125 mInputsTreeWidget->header()->setVisible(
false );
126 mInputsTreeWidget->setAlternatingRowColors(
true );
127 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
128 mInputsTreeWidget->setDropIndicatorShown(
true );
130 mNameEdit->setPlaceholderText( tr(
"Enter model name here" ) );
131 mGroupEdit->setPlaceholderText( tr(
"Enter group name here" ) );
134 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
135 mainLayout->insertWidget( 0, mMessageBar );
137 mView->setAcceptDrops(
true );
140 connect( mActionClose, &QAction::triggered,
this, &QWidget::close );
141 connect( mActionNew, &QAction::triggered,
this, &QgsModelDesignerDialog::newModel );
142 connect( mActionZoomIn, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomIn );
143 connect( mActionZoomOut, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomOut );
144 connect( mActionZoomActual, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomActual );
145 connect( mActionZoomToItems, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomFull );
146 connect( mActionExportImage, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToImage );
147 connect( mActionExportPdf, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToPdf );
148 connect( mActionExportSvg, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToSvg );
149 connect( mActionExportPython, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsPython );
150 connect( mActionSave, &QAction::triggered,
this, [=] { saveModel(
false ); } );
151 connect( mActionSaveAs, &QAction::triggered,
this, [=] { saveModel(
true ); } );
152 connect( mActionDeleteComponents, &QAction::triggered,
this, &QgsModelDesignerDialog::deleteSelected );
153 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
154 connect( mActionValidate, &QAction::triggered,
this, &QgsModelDesignerDialog::validate );
155 connect( mActionReorderInputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderInputs );
156 connect( mActionReorderOutputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderOutputs );
157 connect( mActionEditHelp, &QAction::triggered,
this, &QgsModelDesignerDialog::editHelp );
158 connect( mReorderInputsButton, &QPushButton::clicked,
this, &QgsModelDesignerDialog::reorderInputs );
159 connect( mActionRun, &QAction::triggered,
this, [
this] { run(); } );
160 connect( mActionRunSelectedSteps, &QAction::triggered,
this, &QgsModelDesignerDialog::runSelectedSteps );
162 mActionSnappingEnabled->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ),
false ).toBool() );
163 connect( mActionSnappingEnabled, &QAction::toggled,
this, [=](
bool enabled ) {
164 mView->snapper()->setSnapToGrid( enabled );
165 QgsSettings().
setValue( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ), enabled );
167 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
169 connect( mActionSelectAll, &QAction::triggered,
this, [=] {
173 QStringList docksTitle = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), QStringList(),
QgsSettings::App ).toStringList();
174 QStringList docksActive = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), QStringList(),
QgsSettings::App ).toStringList();
175 if ( !docksTitle.isEmpty() )
177 for (
const auto &title : docksTitle )
179 mPanelStatus.insert( title, PanelStatus(
true, docksActive.contains( title ) ) );
182 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
183 connect( mActionHidePanels, &QAction::toggled,
this, &QgsModelDesignerDialog::setPanelVisibility );
185 mUndoAction = mUndoStack->createUndoAction(
this );
187 mUndoAction->setShortcuts( QKeySequence::Undo );
188 mRedoAction = mUndoStack->createRedoAction(
this );
190 mRedoAction->setShortcuts( QKeySequence::Redo );
192 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
193 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
194 mMenuEdit->insertSeparator( mActionDeleteComponents );
195 mToolbar->insertAction( mActionZoomIn, mUndoAction );
196 mToolbar->insertAction( mActionZoomIn, mRedoAction );
197 mToolbar->insertSeparator( mActionZoomIn );
199 mGroupMenu =
new QMenu( tr(
"Zoom To" ),
this );
200 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
201 connect( mGroupMenu, &QMenu::aboutToShow,
this, &QgsModelDesignerDialog::populateZoomToMenu );
205 mActionCut =
new QAction( tr(
"Cu&t" ),
this );
206 mActionCut->setShortcuts( QKeySequence::Cut );
207 mActionCut->setStatusTip( tr(
"Cut" ) );
209 connect( mActionCut, &QAction::triggered,
this, [=] {
210 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut );
213 mActionCopy =
new QAction( tr(
"&Copy" ),
this );
214 mActionCopy->setShortcuts( QKeySequence::Copy );
215 mActionCopy->setStatusTip( tr(
"Copy" ) );
217 connect( mActionCopy, &QAction::triggered,
this, [=] {
218 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy );
221 mActionPaste =
new QAction( tr(
"&Paste" ),
this );
222 mActionPaste->setShortcuts( QKeySequence::Paste );
223 mActionPaste->setStatusTip( tr(
"Paste" ) );
225 connect( mActionPaste, &QAction::triggered,
this, [=] {
226 mView->pasteItems( QgsModelGraphicsView::PasteModeCursor );
228 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
229 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
230 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
231 mMenuEdit->insertSeparator( mActionDeleteComponents );
233 mAlgorithmsModel =
new QgsModelerToolboxModel(
this );
234 mAlgorithmsTree->setToolboxProxyModel( mAlgorithmsModel );
237 if ( settings.
value( QStringLiteral(
"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES" ),
false ).toBool() )
241 mAlgorithmsTree->setFilters( filters );
242 mAlgorithmsTree->setDragDropMode( QTreeWidget::DragOnly );
243 mAlgorithmsTree->setDropIndicatorShown(
true );
245 connect( mView, &QgsModelGraphicsView::algorithmDropped,
this, [=](
const QString &algorithmId,
const QPointF &pos ) {
246 addAlgorithm( algorithmId, pos );
248 connect( mAlgorithmsTree, &QgsProcessingToolboxTreeView::doubleClicked,
this, [=]() {
249 if ( mAlgorithmsTree->selectedAlgorithm() )
250 addAlgorithm( mAlgorithmsTree->selectedAlgorithm()->id(), QPointF() );
252 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked,
this, [=](
const QModelIndex & ) {
253 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
254 addInput( parameterType, QPointF() );
257 connect( mView, &QgsModelGraphicsView::inputDropped,
this, &QgsModelDesignerDialog::addInput );
260 QShortcut *ctrlEquals =
new QShortcut( QKeySequence( QStringLiteral(
"Ctrl+=" ) ),
this );
261 connect( ctrlEquals, &QShortcut::activated,
this, &QgsModelDesignerDialog::zoomIn );
264 mUndoDock->setObjectName( QStringLiteral(
"UndoDock" ) );
265 mUndoView =
new QUndoView( mUndoStack,
this );
266 mUndoDock->setWidget( mUndoView );
267 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
268 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
270 tabifyDockWidget( mUndoDock, mPropertiesDock );
271 tabifyDockWidget( mVariablesDock, mPropertiesDock );
272 mPropertiesDock->raise();
273 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
274 mInputsDock->raise();
279 beginUndoCommand( tr(
"Change Model Variables" ) );
280 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
284 connect( mNameEdit, &QLineEdit::textChanged,
this, [=](
const QString &name ) {
287 beginUndoCommand( tr(
"Change Model Name" ), NameChanged );
288 mModel->setName( name );
293 connect( mGroupEdit, &QLineEdit::textChanged,
this, [=](
const QString &group ) {
296 beginUndoCommand( tr(
"Change Model Group" ), GroupChanged );
297 mModel->setGroup( group );
305 QToolButton *toolbuttonExportToScript =
new QToolButton();
306 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
307 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
308 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
309 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
310 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
312 mActionShowComments->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/ShowComments" ),
true ).toBool() );
313 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
316 mPanTool->setAction( mActionPan );
318 mToolsActionGroup->addAction( mActionPan );
319 connect( mActionPan, &QAction::triggered, mPanTool, [=] { mView->setTool( mPanTool ); } );
322 mSelectTool->setAction( mActionSelectMoveItem );
324 mToolsActionGroup->addAction( mActionSelectMoveItem );
325 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [=] { mView->setTool( mSelectTool ); } );
327 mView->setTool( mSelectTool );
330 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [=](
const QString &text ) {
331 mIgnoreUndoStackChanges++;
332 mUndoStack->beginMacro( text );
333 mIgnoreUndoStackChanges--;
335 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [=] {
336 mIgnoreUndoStackChanges++;
337 mUndoStack->endMacro();
338 mIgnoreUndoStackChanges--;
340 connect( mView, &QgsModelGraphicsView::beginCommand,
this, [=](
const QString &text ) {
341 beginUndoCommand( text );
343 connect( mView, &QgsModelGraphicsView::endCommand,
this, [=] {
346 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [=] {
350 connect( mActionAddGroupBox, &QAction::triggered,
this, [=] {
351 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
352 QgsProcessingModelGroupBox group;
353 group.setPosition( viewCenter );
354 group.setDescription( tr(
"New Group" ) );
356 beginUndoCommand( tr(
"Add Group Box" ) );
357 model()->addGroupBox( group );
365 restoreState( settings.
value( QStringLiteral(
"ModelDesigner/state" ), QByteArray(),
QgsSettings::App ).toByteArray() );
368QgsModelDesignerDialog::~QgsModelDesignerDialog()
371 if ( !mPanelStatus.isEmpty() )
373 QStringList docksTitle;
374 QStringList docksActive;
376 for (
const auto &panel : mPanelStatus.toStdMap() )
378 if ( panel.second.isVisible )
379 docksTitle << panel.first;
380 if ( panel.second.isActive )
381 docksActive << panel.first;
395 mIgnoreUndoStackChanges++;
399void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
401 if ( checkForUnsavedChanges() )
407void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
int id )
409 if ( mBlockUndoCommands || !mUndoStack )
412 if ( mActiveCommand )
415 mActiveCommand = std::make_unique<QgsModelUndoCommand>( mModel.get(), text,
id );
418void QgsModelDesignerDialog::endUndoCommand()
420 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
423 mActiveCommand->saveAfterState();
424 mIgnoreUndoStackChanges++;
425 mUndoStack->push( mActiveCommand.release() );
426 mIgnoreUndoStackChanges--;
430QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
435void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
437 mModel.reset( model );
439 mGroupEdit->setText( mModel->group() );
440 mNameEdit->setText( mModel->displayName() );
442 updateVariablesGui();
444 mView->centerOn( 0, 0 );
447 mIgnoreUndoStackChanges++;
449 mIgnoreUndoStackChanges--;
454void QgsModelDesignerDialog::loadModel(
const QString &path )
456 std::unique_ptr<QgsProcessingModelAlgorithm> alg = std::make_unique<QgsProcessingModelAlgorithm>();
457 if ( alg->fromFile( path ) )
460 alg->setSourceFilePath( path );
461 setModel( alg.release() );
466 QMessageBox::critical(
this, tr(
"Open Model" ), tr(
"The selected model could not be loaded.\n"
467 "See the log for more information." ) );
471void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
473 QgsModelGraphicsScene *oldScene = mScene;
476 mScene->setParent(
this );
477 mScene->setLastRunResult( mLastResult );
478 mScene->setModel( mModel.get() );
479 mScene->setMessageBar( mMessageBar );
481 const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
482 mView->setModelScene( mScene );
484 mSelectTool->resetCache();
485 mSelectTool->setScene( mScene );
487 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [=] {
488 if ( mBlockRepaints )
493 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [=](
const QString &description,
int id ) { beginUndoCommand( description,
id ); } );
494 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [=] { endUndoCommand(); } );
495 connect( mScene, &QgsModelGraphicsScene::runFromChild,
this, &QgsModelDesignerDialog::runFromChild );
496 connect( mScene, &QgsModelGraphicsScene::runSelected,
this, &QgsModelDesignerDialog::runSelectedSteps );
497 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs,
this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
498 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog,
this, &QgsModelDesignerDialog::showChildAlgorithmLog );
500 mView->centerOn( center );
503 oldScene->deleteLater();
506void QgsModelDesignerDialog::activate()
510 setWindowState( windowState() & ~Qt::WindowMinimized );
514void QgsModelDesignerDialog::updateVariablesGui()
516 mBlockUndoCommands++;
518 std::unique_ptr<QgsExpressionContextScope> variablesScope = std::make_unique<QgsExpressionContextScope>( tr(
"Model Variables" ) );
519 const QVariantMap modelVars = mModel->variables();
520 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
522 variablesScope->setVariable( it.key(), it.value() );
525 variablesContext.
appendScope( variablesScope.release() );
526 mVariablesEditor->setContext( &variablesContext );
527 mVariablesEditor->setEditableScopeIndex( 0 );
529 mBlockUndoCommands--;
532void QgsModelDesignerDialog::setDirty(
bool dirty )
538bool QgsModelDesignerDialog::validateSave( SaveAction action )
542 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
544 case QgsModelDesignerDialog::SaveAction::SaveInProject:
545 if ( mNameEdit->text().trimmed().isEmpty() )
547 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
556bool QgsModelDesignerDialog::checkForUnsavedChanges()
560 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 );
563 case QMessageBox::Save:
564 return saveModel(
false );
566 case QMessageBox::Discard:
581 mLastResult.mergeWith( result );
583 mScene->setLastRunResult( mLastResult );
586void QgsModelDesignerDialog::setModelName(
const QString &name )
588 mNameEdit->setText( name );
591void QgsModelDesignerDialog::zoomIn()
593 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
594 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
596 const double factor = settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
597 mView->scale( factor, factor );
598 mView->centerOn( point );
601void QgsModelDesignerDialog::zoomOut()
603 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
604 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
606 const double factor = 1.0 / settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
607 mView->scale( factor, factor );
608 mView->centerOn( point );
611void QgsModelDesignerDialog::zoomActual()
613 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
614 mView->resetTransform();
615 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
616 mView->centerOn( point );
619void QgsModelDesignerDialog::zoomFull()
621 QRectF totalRect = mView->scene()->itemsBoundingRect();
622 totalRect.adjust( -10, -10, 10, 10 );
623 mView->fitInView( totalRect, Qt::KeepAspectRatio );
626void QgsModelDesignerDialog::newModel()
628 if ( !checkForUnsavedChanges() )
631 std::unique_ptr<QgsProcessingModelAlgorithm> alg = std::make_unique<QgsProcessingModelAlgorithm>();
633 setModel( alg.release() );
636void QgsModelDesignerDialog::exportToImage()
639 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
641 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ), lastExportDir, tr(
"PNG files (*.png *.PNG)" ) );
645 if ( filename.isEmpty() )
650 const QFileInfo saveFileInfo( filename );
653 repaintModel(
false );
655 QRectF totalRect = mView->scene()->itemsBoundingRect();
656 totalRect.adjust( -10, -10, 10, 10 );
657 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
659 QImage img( totalRect.width(), totalRect.height(), QImage::Format_ARGB32_Premultiplied );
660 img.fill( Qt::white );
662 painter.setRenderHint( QPainter::Antialiasing );
663 painter.begin( &img );
664 mView->scene()->render( &painter, imageRect, totalRect );
667 img.save( filename );
669 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 );
670 repaintModel(
true );
673void QgsModelDesignerDialog::exportToPdf()
676 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
678 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ), lastExportDir, tr(
"PDF files (*.pdf *.PDF)" ) );
682 if ( filename.isEmpty() )
687 const QFileInfo saveFileInfo( filename );
690 repaintModel(
false );
692 QRectF totalRect = mView->scene()->itemsBoundingRect();
693 totalRect.adjust( -10, -10, 10, 10 );
694 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
696 QPdfWriter pdfWriter( filename );
698 const double scaleFactor = 96 / 25.4;
700 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ), QPageLayout::Portrait, QMarginsF( 0, 0, 0, 0 ) );
701 pageLayout.setMode( QPageLayout::FullPageMode );
702 pdfWriter.setPageLayout( pageLayout );
704 QPainter painter( &pdfWriter );
705 mView->scene()->render( &painter, printerRect, totalRect );
708 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 );
709 repaintModel(
true );
712void QgsModelDesignerDialog::exportToSvg()
715 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
717 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ), lastExportDir, tr(
"SVG files (*.svg *.SVG)" ) );
721 if ( filename.isEmpty() )
726 const QFileInfo saveFileInfo( filename );
729 repaintModel(
false );
731 QRectF totalRect = mView->scene()->itemsBoundingRect();
732 totalRect.adjust( -10, -10, 10, 10 );
733 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
736 svg.setFileName( filename );
737 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
738 svg.setViewBox( svgRect );
739 svg.setTitle( mModel->displayName() );
741 QPainter painter( &svg );
742 mView->scene()->render( &painter, svgRect, totalRect );
745 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 );
746 repaintModel(
true );
749void QgsModelDesignerDialog::exportAsPython()
752 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
754 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ), lastExportDir, tr(
"Processing scripts (*.py *.PY)" ) );
758 if ( filename.isEmpty() )
763 const QFileInfo saveFileInfo( filename );
768 QFile outFile( filename );
769 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
773 QTextStream fout( &outFile );
777 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 );
780void QgsModelDesignerDialog::toggleComments(
bool show )
784 repaintModel(
true );
787void QgsModelDesignerDialog::updateWindowTitle()
789 QString title = tr(
"Model Designer" );
790 if ( !mModel->name().isEmpty() )
791 title = mModel->group().isEmpty()
792 ? QStringLiteral(
"%1: %2" ).arg( title, mModel->name() )
793 : QStringLiteral(
"%1: %2 - %3" ).arg( title, mModel->group(), mModel->name() );
796 title.prepend(
'*' );
798 setWindowTitle( title );
801void QgsModelDesignerDialog::deleteSelected()
803 QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
807 if ( items.size() == 1 )
809 items.at( 0 )->deleteComponent();
813 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem *p1, QgsModelComponentGraphicItem *p2 ) {
815 if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) )
817 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
819 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) )
821 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
823 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) )
825 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
827 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) )
829 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
835 beginUndoCommand( tr(
"Delete Components" ) );
837 QVariant prevState = mModel->toVariant();
838 mBlockUndoCommands++;
839 mBlockRepaints =
true;
841 while ( !items.empty() )
843 QgsModelComponentGraphicItem *toDelete =
nullptr;
844 for ( QgsModelComponentGraphicItem *item : items )
846 if ( item->canDeleteComponent() )
859 toDelete->deleteComponent();
860 items.removeAll( toDelete );
865 mModel->loadVariant( prevState );
866 QMessageBox::warning(
nullptr, QObject::tr(
"Could not remove components" ), QObject::tr(
"Components depend on the selected items.\n"
867 "Try to remove them before trying deleting these components." ) );
868 mBlockUndoCommands--;
869 mActiveCommand.reset();
873 mBlockUndoCommands--;
877 mBlockRepaints =
false;
881void QgsModelDesignerDialog::populateZoomToMenu()
884 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
886 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
888 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
889 connect( zoomAction, &QAction::triggered,
this, [=] {
890 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
891 groupRect.adjust( -10, -10, 10, 10 );
892 mView->fitInView( groupRect, Qt::KeepAspectRatio );
893 mView->centerOn( item );
895 mGroupMenu->addAction( zoomAction );
900void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
902 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
903 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
907 mPanelStatus.clear();
909 for ( QDockWidget *dock : docks )
911 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
912 dock->setVisible(
false );
916 for ( QTabBar *tabBar : tabBars )
918 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
919 mPanelStatus[currentTabTitle].isActive =
true;
925 for ( QDockWidget *dock : docks )
927 if ( mPanelStatus.contains( dock->windowTitle() ) )
929 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
934 for ( QTabBar *tabBar : tabBars )
937 for (
int i = 0; i < tabBar->count(); ++i )
939 QString tabTitle = tabBar->tabText( i );
940 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
942 tabBar->setCurrentIndex( i );
946 mPanelStatus.clear();
950void QgsModelDesignerDialog::editHelp()
952 QgsProcessingHelpEditorDialog dialog(
this );
953 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
954 dialog.setAlgorithm( mModel.get() );
957 beginUndoCommand( tr(
"Edit Model Help" ) );
958 mModel->setHelpContent( dialog.helpContent() );
963void QgsModelDesignerDialog::runSelectedSteps()
965 QSet<QString> children;
966 const QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
967 for ( QgsModelComponentGraphicItem *item : items )
969 if ( QgsProcessingModelChildAlgorithm *childAlgorithm =
dynamic_cast<QgsProcessingModelChildAlgorithm *
>( item->component() ) )
971 children.insert( childAlgorithm->childId() );
975 if ( children.isEmpty() )
977 mMessageBar->pushWarning( QString(), tr(
"No steps are selected" ) );
984void QgsModelDesignerDialog::runFromChild(
const QString &
id )
986 QSet<QString> children = mModel->dependentChildAlgorithms(
id );
987 children.insert(
id );
991void QgsModelDesignerDialog::run(
const QSet<QString> &childAlgorithmSubset )
994 const bool isValid = model()->validate( errors );
997 QMessageBox messageBox;
998 messageBox.setWindowTitle( tr(
"Model is Invalid" ) );
999 messageBox.setIcon( QMessageBox::Icon::Warning );
1000 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?" ) );
1001 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1002 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1004 QString errorString;
1005 for (
const QString &error : std::as_const( errors ) )
1007 QString cleanedError = error;
1008 const thread_local QRegularExpression re( QStringLiteral(
"<[^>]*>" ) );
1009 cleanedError.replace( re, QString() );
1010 errorString += QStringLiteral(
"• %1\n" ).arg( cleanedError );
1013 messageBox.setDetailedText( errorString );
1014 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1018 if ( !childAlgorithmSubset.isEmpty() )
1020 for (
const QString &child : childAlgorithmSubset )
1023 const QSet<QString> requirements = mModel->dependsOnChildAlgorithms( child );
1024 for (
const QString &requirement : requirements )
1026 if ( !mLastResult.executedChildIds().contains( requirement ) )
1028 QMessageBox messageBox;
1029 messageBox.setWindowTitle( tr(
"Run Model" ) );
1030 messageBox.setIcon( QMessageBox::Icon::Warning );
1031 messageBox.setText( tr(
"Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1032 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1040 std::unique_ptr<QgsProcessingAlgorithmDialogBase> dialog( createExecutionDialog() );
1045 dialog->setParameters( mModel->designerParameterValues() );
1047 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmAboutToRun,
this, [
this, &childAlgorithmSubset](
QgsProcessingContext *context ) {
1048 if ( !childAlgorithmSubset.empty() )
1051 std::unique_ptr<QgsProcessingModelInitialRunConfig> modelConfig = std::make_unique<QgsProcessingModelInitialRunConfig>();
1052 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1053 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1054 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1055 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1059 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1060 std::unique_ptr<QgsMapLayerStore> previousResultStore = std::make_unique<QgsMapLayerStore>();
1061 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1063 std::unique_ptr<QgsMapLayer> clone( it.value()->clone() );
1064 clone->setId( it.value()->id() );
1065 previousResultStore->addMapLayer( clone.release() );
1067 previousResultStore->moveToThread( nullptr );
1068 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1069 context->setModelInitialRunConfig( std::move( modelConfig ) );
1073 connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmFinished,
this, [
this, &dialog](
bool,
const QVariantMap & ) {
1074 QgsProcessingContext *context = dialog->processingContext();
1076 setLastRunResult( context->modelResult() );
1078 mModel->setDesignerParameterValues( dialog->createProcessingParameters( QgsProcessingParametersGenerator::Flag::SkipDefaultValueParameters ) );
1081 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1082 mLayerStore.takeResultsFrom( *context );
1088void QgsModelDesignerDialog::showChildAlgorithmOutputs(
const QString &childId )
1090 const QString childDescription = mModel->childAlgorithm( childId ).description();
1093 const QVariantMap childAlgorithmOutputs = result.
outputs();
1094 if ( childAlgorithmOutputs.isEmpty() )
1096 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1103 mMessageBar->pushCritical( QString(), tr(
"Results cannot be shown for an invalid model component" ) );
1108 if ( outputParams.isEmpty() )
1111 QgsDebugError(
"Cannot show results for algorithms with no outputs" );
1115 bool foundResults =
false;
1118 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1119 if ( !output.isValid() )
1122 if ( output.type() == QVariant::String )
1126 QgsDebugMsgLevel( QStringLiteral(
"Loading previous result for %1: %2" ).arg( outputParam->name(), output.toString() ), 2 );
1128 std::unique_ptr<QgsMapLayer> layer( resultLayer->clone() );
1131 if ( outputParams.size() > 1 )
1132 baseName = tr(
"%1 — %2" ).arg( childDescription, outputParam->name() );
1134 baseName = childDescription;
1138 QString name = baseName;
1143 name = tr(
"%1 (%2)" ).arg( baseName ).arg( counter );
1146 layer->setName( name );
1149 foundResults =
true;
1154 QgsDebugError( QStringLiteral(
"Could not load previous result for %1: %2" ).arg( outputParam->name(), output.toString() ) );
1159 if ( !foundResults )
1161 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1166void QgsModelDesignerDialog::showChildAlgorithmLog(
const QString &childId )
1168 const QString childDescription = mModel->childAlgorithm( childId ).description();
1171 if ( result.
htmlLog().isEmpty() )
1173 mMessageBar->pushWarning( QString(), tr(
"No log is available for %1" ).arg( childDescription ) );
1178 m.setWindowTitle( childDescription );
1179 m.setCheckBoxVisible(
false );
1180 m.setMessageAsHtml( result.
htmlLog() );
1184void QgsModelDesignerDialog::validate()
1187 if ( model()->validate( issues ) )
1189 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
1194 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
1195 connect( detailsButton, &QPushButton::clicked, detailsButton, [=] {
1197 dialog->
setTitle( tr(
"Model is Invalid" ) );
1199 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + QStringLiteral(
"<ul>" );
1200 for (
const QString &issue : issues )
1202 longMessage += QStringLiteral(
"<li>%1</li>" ).arg( issue );
1204 longMessage += QLatin1String(
"</ul>" );
1209 messageWidget->layout()->addWidget( detailsButton );
1210 mMessageBar->clearWidgets();
1215void QgsModelDesignerDialog::reorderInputs()
1217 QgsModelInputReorderDialog dlg(
this );
1218 dlg.setModel( mModel.get() );
1221 const QStringList inputOrder = dlg.inputOrder();
1222 beginUndoCommand( tr(
"Reorder Inputs" ) );
1223 mModel->setParameterOrder( inputOrder );
1228void QgsModelDesignerDialog::reorderOutputs()
1230 QgsModelOutputReorderDialog dlg(
this );
1231 dlg.setModel( mModel.get() );
1234 const QStringList outputOrder = dlg.outputOrder();
1235 beginUndoCommand( tr(
"Reorder Outputs" ) );
1236 mModel->setOutputOrder( outputOrder );
1237 mModel->setOutputGroup( dlg.outputGroup() );
1242bool QgsModelDesignerDialog::isDirty()
const
1244 return mHasChanged && mUndoStack->index() != -1;
1247void QgsModelDesignerDialog::fillInputsTree()
1250 std::unique_ptr<QTreeWidgetItem> parametersItem = std::make_unique<QTreeWidgetItem>();
1251 parametersItem->setText( 0, tr(
"Parameters" ) );
1254 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1261 std::unique_ptr<QTreeWidgetItem> paramItem = std::make_unique<QTreeWidgetItem>();
1262 paramItem->setText( 0, param->name() );
1263 paramItem->setData( 0, Qt::UserRole, param->id() );
1264 paramItem->setIcon( 0, icon );
1265 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1266 paramItem->setToolTip( 0, param->description() );
1267 parametersItem->addChild( paramItem.release() );
1270 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1271 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1279QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1282 , mChildId( childId )
1284 QHBoxLayout *hl =
new QHBoxLayout();
1285 hl->setContentsMargins( 0, 0, 0, 0 );
1287 mLineEdit =
new QLineEdit();
1288 mLineEdit->setEnabled(
false );
1289 hl->addWidget( mLineEdit, 1 );
1291 mToolButton =
new QToolButton();
1292 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1293 hl->addWidget( mToolButton );
1297 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1299 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1302void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1306 updateSummaryText();
1309void QgsModelChildDependenciesWidget::showDialog()
1311 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1313 QVariantList availableOptions;
1314 for (
const QgsProcessingModelChildDependency &dep : available )
1315 availableOptions << QVariant::fromValue( dep );
1316 QVariantList selectedOptions;
1317 for (
const QgsProcessingModelChildDependency &dep : mValue )
1318 selectedOptions << QVariant::fromValue( dep );
1323 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1324 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1326 widget->setValueFormatter( [=](
const QVariant &v ) -> QString {
1327 const QgsProcessingModelChildDependency dep = v.value<QgsProcessingModelChildDependency>();
1329 const QString description = mModel->childAlgorithm( dep.childId ).description();
1330 if ( dep.conditionalBranch.isEmpty() )
1333 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1336 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [=]() {
1337 QList<QgsProcessingModelChildDependency> res;
1338 for (
const QVariant &v : widget->selectedOptions() )
1340 res << v.value<QgsProcessingModelChildDependency>();
1349void QgsModelChildDependenciesWidget::updateSummaryText()
1351 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)