QGIS API Documentation 4.1.0-Master (26185ffb827)
Loading...
Searching...
No Matches
qgsmodeldesignerdialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmodeldesignerdialog.cpp
3 ------------------------
4 Date : March 2020
5 Copyright : (C) 2020 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
21#include "qgsapplication.h"
22#include "qgsfileutils.h"
23#include "qgsgui.h"
24#include "qgsmessagebar.h"
25#include "qgsmessagebaritem.h"
26#include "qgsmessagelog.h"
27#include "qgsmessageviewer.h"
32#include "qgsmodelundocommand.h"
33#include "qgsmodelviewtoolpan.h"
35#include "qgspanelwidget.h"
45#include "qgsproject.h"
46#include "qgsscreenhelper.h"
47#include "qgssettings.h"
48
49#include <QActionGroup>
50#include <QCloseEvent>
51#include <QFileDialog>
52#include <QKeySequence>
53#include <QMessageBox>
54#include <QPdfWriter>
55#include <QPushButton>
56#include <QShortcut>
57#include <QString>
58#include <QSvgGenerator>
59#include <QTextStream>
60#include <QTimer>
61#include <QToolButton>
62#include <QUndoView>
63#include <QUrl>
64
65#include "moc_qgsmodeldesignerdialog.cpp"
66
67using namespace Qt::StringLiterals;
68
70
71
72QgsModelerToolboxModel::QgsModelerToolboxModel( QObject *parent )
74{}
75
76Qt::ItemFlags QgsModelerToolboxModel::flags( const QModelIndex &index ) const
77{
78 Qt::ItemFlags f = QgsProcessingToolboxProxyModel::flags( index );
79 const QModelIndex sourceIndex = mapToSource( index );
80 if ( toolboxModel()->isAlgorithm( sourceIndex ) || toolboxModel()->isParameter( sourceIndex ) )
81 {
82 f = f | Qt::ItemIsDragEnabled;
83 }
84 return f;
85}
86
87Qt::DropActions QgsModelerToolboxModel::supportedDragActions() const
88{
89 return Qt::CopyAction;
90}
91
92QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
93 : QMainWindow( parent, flags )
94 , mToolsActionGroup( new QActionGroup( this ) )
95{
96 setupUi( this );
97
98 mLayerStore.setProject( QgsProject::instance() );
99
100 mScreenHelper = new QgsScreenHelper( this );
101
102 setAttribute( Qt::WA_DeleteOnClose );
103 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
104 setWindowFlags( Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint );
105
107
108 mModel = std::make_unique<QgsProcessingModelAlgorithm>();
109 mModel->setProvider( QgsApplication::processingRegistry()->providerById( u"model"_s ) );
110
111 mUndoStack = new QUndoStack( this );
112 connect( mUndoStack, &QUndoStack::indexChanged, this, [this] {
113 if ( mIgnoreUndoStackChanges )
114 return;
115
116 mBlockUndoCommands++;
117 updateVariablesGui();
118 mGroupEdit->setText( mModel->group() );
119 mNameEdit->setText( mModel->displayName() );
120 mBlockUndoCommands--;
121 repaintModel();
122 } );
123
124 mConfigWidgetDock = new QgsDockWidget( this );
125 mConfigWidgetDock->setWindowTitle( tr( "Configuration" ) );
126 mConfigWidgetDock->setObjectName( u"ModelConfigDock"_s );
127
128 mConfigWidget = new QgsModelDesignerConfigDockWidget();
129 mConfigWidgetDock->setWidget( mConfigWidget );
130 mConfigWidgetDock->setFeatures( QDockWidget::NoDockWidgetFeatures );
131 addDockWidget( Qt::RightDockWidgetArea, mConfigWidgetDock );
132
133 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
134 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
135 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
136 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
137
138 mToolboxTree->header()->setVisible( false );
139 mToolboxSearchEdit->setShowSearchIcon( true );
140 mToolboxSearchEdit->setPlaceholderText( tr( "Search…" ) );
141 connect( mToolboxSearchEdit, &QgsFilterLineEdit::textChanged, mToolboxTree, &QgsProcessingToolboxTreeView::setFilterString );
142
143 mInputsTreeWidget->header()->setVisible( false );
144 mInputsTreeWidget->setAlternatingRowColors( true );
145 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
146 mInputsTreeWidget->setDropIndicatorShown( true );
147
148 mNameEdit->setPlaceholderText( tr( "Enter model name here" ) );
149 mGroupEdit->setPlaceholderText( tr( "Enter group name here" ) );
150
151 mMessageBar = new QgsMessageBar();
152 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
153 mainLayout->insertWidget( 0, mMessageBar );
154
155 mView->setAcceptDrops( true );
156 QgsSettings settings;
157
158 connect( mActionClose, &QAction::triggered, this, &QWidget::close );
159 connect( mActionNew, &QAction::triggered, this, &QgsModelDesignerDialog::newModel );
160 connect( mActionZoomIn, &QAction::triggered, this, &QgsModelDesignerDialog::zoomIn );
161 connect( mActionZoomOut, &QAction::triggered, this, &QgsModelDesignerDialog::zoomOut );
162 connect( mActionZoomActual, &QAction::triggered, this, &QgsModelDesignerDialog::zoomActual );
163 connect( mActionZoomToItems, &QAction::triggered, this, &QgsModelDesignerDialog::zoomFull );
164 connect( mActionExportImage, &QAction::triggered, this, &QgsModelDesignerDialog::exportToImage );
165 connect( mActionExportPdf, &QAction::triggered, this, &QgsModelDesignerDialog::exportToPdf );
166 connect( mActionExportSvg, &QAction::triggered, this, &QgsModelDesignerDialog::exportToSvg );
167 connect( mActionExportPython, &QAction::triggered, this, &QgsModelDesignerDialog::exportAsPython );
168 connect( mActionSave, &QAction::triggered, this, [this] { saveModel( false ); } );
169 connect( mActionSaveAs, &QAction::triggered, this, [this] { saveModel( true ); } );
170 connect( mActionDeleteComponents, &QAction::triggered, this, &QgsModelDesignerDialog::deleteSelected );
171 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
172 connect( mActionValidate, &QAction::triggered, this, &QgsModelDesignerDialog::validate );
173 connect( mActionReorderInputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderInputs );
174 connect( mActionReorderOutputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderOutputs );
175 connect( mActionEditHelp, &QAction::triggered, this, &QgsModelDesignerDialog::editHelp );
176 connect( mReorderInputsButton, &QPushButton::clicked, this, &QgsModelDesignerDialog::reorderInputs );
177 connect( mActionRun, &QAction::triggered, this, [this] { run(); } );
178 connect( mActionRunSelectedSteps, &QAction::triggered, this, &QgsModelDesignerDialog::runSelectedSteps );
179
180 mActionSnappingEnabled->setChecked( settings.value( u"/Processing/Modeler/enableSnapToGrid"_s, false ).toBool() );
181 connect( mActionSnappingEnabled, &QAction::toggled, this, [this]( bool enabled ) {
182 mView->snapper()->setSnapToGrid( enabled );
183 QgsSettings().setValue( u"/Processing/Modeler/enableSnapToGrid"_s, enabled );
184 } );
185 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
186
187 connect( mView, &QgsModelGraphicsView::itemFocused, this, &QgsModelDesignerDialog::onItemFocused );
188
189 connect( mActionSelectAll, &QAction::triggered, this, [this] { mScene->selectAll(); } );
190
191 QStringList docksTitle = settings.value( u"ModelDesigner/hiddenDocksTitle"_s, QStringList(), QgsSettings::App ).toStringList();
192 QStringList docksActive = settings.value( u"ModelDesigner/hiddenDocksActive"_s, QStringList(), QgsSettings::App ).toStringList();
193 if ( !docksTitle.isEmpty() )
194 {
195 for ( const auto &title : docksTitle )
196 {
197 mPanelStatus.insert( title, PanelStatus( true, docksActive.contains( title ) ) );
198 }
199 }
200 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
201 connect( mActionHidePanels, &QAction::toggled, this, &QgsModelDesignerDialog::setPanelVisibility );
202
203 mUndoAction = mUndoStack->createUndoAction( this );
204 mUndoAction->setIcon( QgsApplication::getThemeIcon( u"/mActionUndo.svg"_s ) );
205 mUndoAction->setShortcuts( QKeySequence::Undo );
206 mRedoAction = mUndoStack->createRedoAction( this );
207 mRedoAction->setIcon( QgsApplication::getThemeIcon( u"/mActionRedo.svg"_s ) );
208 mRedoAction->setShortcuts( QKeySequence::Redo );
209
210 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
211 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
212 mMenuEdit->insertSeparator( mActionDeleteComponents );
213 mToolbar->insertAction( mActionZoomIn, mUndoAction );
214 mToolbar->insertAction( mActionZoomIn, mRedoAction );
215 mToolbar->insertSeparator( mActionZoomIn );
216
217 mGroupMenu = new QMenu( tr( "Zoom To" ), this );
218 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
219 connect( mGroupMenu, &QMenu::aboutToShow, this, &QgsModelDesignerDialog::populateZoomToMenu );
220
221 //cut/copy/paste actions. Note these are not included in the ui file
222 //as ui files have no support for QKeySequence shortcuts
223 mActionCut = new QAction( tr( "Cu&t" ), this );
224 mActionCut->setShortcuts( QKeySequence::Cut );
225 mActionCut->setStatusTip( tr( "Cut" ) );
226 mActionCut->setIcon( QgsApplication::getThemeIcon( u"/mActionEditCut.svg"_s ) );
227 connect( mActionCut, &QAction::triggered, this, [this] { mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut ); } );
228
229 mActionCopy = new QAction( tr( "&Copy" ), this );
230 mActionCopy->setShortcuts( QKeySequence::Copy );
231 mActionCopy->setStatusTip( tr( "Copy" ) );
232 mActionCopy->setIcon( QgsApplication::getThemeIcon( u"/mActionEditCopy.svg"_s ) );
233 connect( mActionCopy, &QAction::triggered, this, [this] { mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy ); } );
234
235 mActionPaste = new QAction( tr( "&Paste" ), this );
236 mActionPaste->setShortcuts( QKeySequence::Paste );
237 mActionPaste->setStatusTip( tr( "Paste" ) );
238 mActionPaste->setIcon( QgsApplication::getThemeIcon( u"/mActionEditPaste.svg"_s ) );
239 connect( mActionPaste, &QAction::triggered, this, [this] { mView->pasteItems( QgsModelGraphicsView::PasteModeCursor ); } );
240 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
241 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
242 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
243 mMenuEdit->insertSeparator( mActionDeleteComponents );
244
245 mAlgorithmsModel = new QgsModelerToolboxModel( this );
246 mToolboxTree->setToolboxProxyModel( mAlgorithmsModel );
247
249 if ( settings.value( u"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES"_s, false ).toBool() )
250 {
252 }
253 mToolboxTree->setFilters( filters );
254 mToolboxTree->setDragDropMode( QTreeWidget::DragOnly );
255 mToolboxTree->setDropIndicatorShown( true );
256
257 connect( mView, &QgsModelGraphicsView::algorithmDropped, this, [this]( const QString &algorithmId, const QPointF &pos ) { addAlgorithm( algorithmId, pos ); } );
258 connect( mView, &QgsModelGraphicsView::inputDropped, this, &QgsModelDesignerDialog::addInput );
259
260 connect( mToolboxTree, &QgsProcessingToolboxTreeView::doubleClicked, this, [this]( const QModelIndex & ) {
261 if ( mToolboxTree->selectedAlgorithm() )
262 addAlgorithm( mToolboxTree->selectedAlgorithm()->id(), QPointF() );
263 if ( mToolboxTree->selectedParameterType() )
264 addInput( mToolboxTree->selectedParameterType()->id(), QPointF() );
265 } );
266
267 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked, this, [this]( const QModelIndex & ) {
268 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
269 addInput( parameterType, QPointF() );
270 } );
271
272 // Ctrl+= should also trigger a zoom in action
273 QShortcut *ctrlEquals = new QShortcut( QKeySequence( u"Ctrl+="_s ), this );
274 connect( ctrlEquals, &QShortcut::activated, this, &QgsModelDesignerDialog::zoomIn );
275
276 mUndoDock = new QgsDockWidget( tr( "Undo History" ), this );
277 mUndoDock->setObjectName( u"UndoDock"_s );
278 mUndoView = new QUndoView( mUndoStack, this );
279 mUndoDock->setWidget( mUndoView );
280 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
281 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
282
283 tabifyDockWidget( mUndoDock, mPropertiesDock );
284 tabifyDockWidget( mVariablesDock, mPropertiesDock );
285 mPropertiesDock->raise();
286 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
287 mInputsDock->raise();
288
289 connect( mVariablesEditor, &QgsVariableEditorWidget::scopeChanged, this, [this] {
290 if ( mModel )
291 {
292 beginUndoCommand( tr( "Change Model Variables" ) );
293 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
294 endUndoCommand();
295 }
296 } );
297 connect( mNameEdit, &QLineEdit::textChanged, this, [this]( const QString &name ) {
298 if ( mModel )
299 {
300 beginUndoCommand( tr( "Change Model Name" ), QString(), QgsModelUndoCommand::CommandOperation::NameChanged );
301 mModel->setName( name );
302 endUndoCommand();
303 updateWindowTitle();
304 }
305 } );
306 connect( mGroupEdit, &QLineEdit::textChanged, this, [this]( const QString &group ) {
307 if ( mModel )
308 {
309 beginUndoCommand( tr( "Change Model Group" ), QString(), QgsModelUndoCommand::CommandOperation::GroupChanged );
310 mModel->setGroup( group );
311 endUndoCommand();
312 updateWindowTitle();
313 }
314 } );
315
316 fillInputsTree();
317
318 QToolButton *toolbuttonExportToScript = new QToolButton();
319 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
320 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
321 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
322 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
323 connect( mActionExportAsScriptAlgorithm, &QAction::triggered, this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
324
325 mActionShowComments->setChecked( settings.value( u"/Processing/Modeler/ShowComments"_s, true ).toBool() );
326 connect( mActionShowComments, &QAction::toggled, this, &QgsModelDesignerDialog::toggleComments );
327
328 mActionShowFeatureCount->setChecked( settings.value( u"/Processing/Modeler/ShowFeatureCount"_s, true ).toBool() );
329 connect( mActionShowFeatureCount, &QAction::toggled, this, &QgsModelDesignerDialog::toggleFeatureCount );
330
331 mPanTool = new QgsModelViewToolPan( mView );
332 mPanTool->setAction( mActionPan );
333
334 mToolsActionGroup->addAction( mActionPan );
335 connect( mActionPan, &QAction::triggered, mPanTool, [this] { mView->setTool( mPanTool ); } );
336
337 // We use a QObjectUniquePtr here because we want to delete QgsModelViewToolSelect
338 // mouse handles before everything else and don't want to wait for QObject destructor to destroy it
339 mSelectTool = make_qobject_unique<QgsModelViewToolSelect>( mView );
340 mSelectTool->setAction( mActionSelectMoveItem );
341
342 mToolsActionGroup->addAction( mActionSelectMoveItem );
343 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [this] { mView->setTool( mSelectTool ); } );
344
345 mView->setTool( mSelectTool );
346 mView->setFocus();
347
348 connect( mView, &QgsModelGraphicsView::macroCommandStarted, this, [this]( const QString &text ) {
349 mIgnoreUndoStackChanges++;
350 mUndoStack->beginMacro( text );
351 mIgnoreUndoStackChanges--;
352 } );
353 connect( mView, &QgsModelGraphicsView::macroCommandEnded, this, [this] {
354 mIgnoreUndoStackChanges++;
355 mUndoStack->endMacro();
356 mIgnoreUndoStackChanges--;
357 } );
358 connect( mView, &QgsModelGraphicsView::commandBegun, this, [this]( const QString &text ) { beginUndoCommand( text ); } );
359 connect( mView, &QgsModelGraphicsView::commandEnded, this, [this] { endUndoCommand(); } );
360 connect( mView, &QgsModelGraphicsView::commandAborted, this, [this] { abortUndoCommand(); } );
361 connect( mView, &QgsModelGraphicsView::deleteSelectedItems, this, [this] { deleteSelected(); } );
362
363 connect( mActionAddGroupBox, &QAction::triggered, this, [this] {
364 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
365 QgsProcessingModelGroupBox group;
366 group.setPosition( viewCenter );
367 group.setDescription( tr( "New Group" ) );
368
369 beginUndoCommand( tr( "Add Group Box" ) );
370 model()->addGroupBox( group );
371 repaintModel();
372 endUndoCommand();
373 } );
374
375 updateWindowTitle();
376
377 // restore the toolbar and dock widgets positions using Qt settings API
378 restoreState( settings.value( u"ModelDesigner/state"_s, QByteArray(), QgsSettings::App ).toByteArray() );
379}
380
381QgsModelDesignerDialog::~QgsModelDesignerDialog()
382{
383 if ( mAlgorithmWidget )
384 {
385 delete mAlgorithmWidget;
386 }
387 for ( const QPointer<QgsProcessingAlgorithmWidgetBase> &widget : std::as_const( mAlgorithmWidgetsToCleanUp ) )
388 {
389 // this is a work around for the MESSY ownership issues associated with the python subclass
390 // of QgsProcessingAlgorithmWidgetBase. We have to FORCE all widgets to be deleted prior
391 // to destruction of this window, and we can't be sure that python will have actually
392 // deleted the widget when we asked...
393 if ( widget )
394 {
395 delete widget;
396 }
397 }
398
399 QgsSettings settings;
400 if ( !mPanelStatus.isEmpty() )
401 {
402 QStringList docksTitle;
403 QStringList docksActive;
404
405 for ( const auto &panel : mPanelStatus.toStdMap() )
406 {
407 if ( panel.second.isVisible )
408 docksTitle << panel.first;
409 if ( panel.second.isActive )
410 docksActive << panel.first;
411 }
412 settings.setValue( u"ModelDesigner/hiddenDocksTitle"_s, docksTitle, QgsSettings::App );
413 settings.setValue( u"ModelDesigner/hiddenDocksActive"_s, docksActive, QgsSettings::App );
414 }
415 else
416 {
417 settings.remove( u"ModelDesigner/hiddenDocksTitle"_s, QgsSettings::App );
418 settings.remove( u"ModelDesigner/hiddenDocksActive"_s, QgsSettings::App );
419 }
420
421 // store the toolbar/dock widget settings using Qt settings API
422 settings.setValue( u"ModelDesigner/state"_s, saveState(), QgsSettings::App );
423
424 mIgnoreUndoStackChanges++;
425}
426
427void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
428{
429 if ( checkForUnsavedChanges() )
430 event->accept();
431 else
432 event->ignore();
433}
434
435void QgsModelDesignerDialog::beginUndoCommand( const QString &text, const QString &id, QgsModelUndoCommand::CommandOperation operation )
436{
437 if ( mBlockUndoCommands || !mUndoStack )
438 return;
439
440 if ( mActiveCommand )
441 endUndoCommand();
442
443 if ( !id.isEmpty() )
444 {
445 mActiveCommand = std::make_unique<QgsModelUndoCommand>( mModel.get(), text, id );
446 }
447 else
448 {
449 mActiveCommand = std::make_unique<QgsModelUndoCommand>( mModel.get(), text, operation );
450 }
451}
452
453void QgsModelDesignerDialog::endUndoCommand()
454{
455 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
456 return;
457
458 mActiveCommand->saveAfterState();
459 mIgnoreUndoStackChanges++;
460 mUndoStack->push( mActiveCommand.release() );
461 mIgnoreUndoStackChanges--;
462 setDirty( true );
463}
464
465void QgsModelDesignerDialog::abortUndoCommand()
466{
467 if ( mActiveCommand )
468 mActiveCommand->setObsolete( true );
469}
470
471QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
472{
473 return mModel.get();
474}
475
476void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
477{
478 mModel.reset( model );
479
480 mGroupEdit->setText( mModel->group() );
481 mNameEdit->setText( mModel->displayName() );
482 repaintModel( true );
483 updateVariablesGui();
484
485 setDirty( false );
486
487 mIgnoreUndoStackChanges++;
488 mUndoStack->clear();
489 mIgnoreUndoStackChanges--;
490
491 updateWindowTitle();
492
493 // Delay zoom to the full model to ensure the scene has been properly set
494 // and that the itemsBoundingRect returns the correct value.
495 QTimer::singleShot( 100, this, [this] { zoomFull(); } );
496}
497
498void QgsModelDesignerDialog::loadModel( const QString &path )
499{
500 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
501 if ( alg->fromFile( path ) )
502 {
503 alg->setProvider( QgsApplication::processingRegistry()->providerById( u"model"_s ) );
504 alg->setSourceFilePath( path );
505 setModel( alg.release() );
506 }
507 else
508 {
509 QgsMessageLog::logMessage( tr( "Could not load model %1" ).arg( path ), tr( "Processing" ), Qgis::MessageLevel::Critical );
510 QMessageBox::critical(
511 this,
512 tr( "Open Model" ),
513 tr(
514 "The selected model could not be loaded.\n"
515 "See the log for more information."
516 )
517 );
518 }
519}
520
521void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
522{
523 QgsModelGraphicsScene *oldScene = mScene;
524
525 mScene = scene;
526 mScene->setParent( this );
527 mScene->setLastRunResult( mLastResult, mLayerStore );
528 mScene->setModel( mModel.get() );
529 mScene->setMessageBar( mMessageBar );
530
531 QgsSettings settings;
532 const bool showFeatureCount = settings.value( u"/Processing/Modeler/ShowFeatureCount"_s, true ).toBool();
533 if ( !showFeatureCount )
534 mScene->setFlag( QgsModelGraphicsScene::FlagHideFeatureCount );
535
536 mView->setModelScene( mScene );
537
538 mSelectTool->resetCache();
539 mSelectTool->setScene( mScene );
540
541 connect( mScene, &QgsModelGraphicsScene::rebuildRequired, this, [this] {
542 if ( mBlockRepaints )
543 return;
544
545 repaintModel();
546 mScene->flagChildrenAsOutdated( mOutdatedChildResults );
547 } );
548 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange, this, [this]( const QString &description, const QString &id ) { beginUndoCommand( description, id ); } );
549 connect( mScene, &QgsModelGraphicsScene::componentChanged, this, [this] { endUndoCommand(); } );
550 connect( mScene, &QgsModelGraphicsScene::runFromChild, this, &QgsModelDesignerDialog::runFromChild );
551 connect( mScene, &QgsModelGraphicsScene::runSelected, this, &QgsModelDesignerDialog::runSelectedSteps );
552 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs, this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
553 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog, this, &QgsModelDesignerDialog::showChildAlgorithmLog );
554
555 if ( oldScene )
556 oldScene->deleteLater();
557}
558
559QgsModelGraphicsScene *QgsModelDesignerDialog::modelScene()
560{
561 return mScene;
562}
563
564QgsProcessingFeedback *QgsModelDesignerDialog::createFeedback()
565{
566 auto result = std::make_unique< QgsProcessingModelFeedback >();
567 mScene->setupFeedbackConnections( result.get() );
568 connect( result.get(), &QgsProcessingModelFeedback::childResultReported, this, [this]( const QString &childId, const QgsProcessingModelChildAlgorithmResult & ) {
569 mOutdatedChildResults.remove( childId );
570 } );
571 return result.release();
572}
573
574void QgsModelDesignerDialog::activate()
575{
576 show();
577 raise();
578 setWindowState( windowState() & ~Qt::WindowMinimized );
579 activateWindow();
580}
581
582void QgsModelDesignerDialog::registerProcessingContextGenerator( QgsProcessingContextGenerator *generator )
583{
584 mProcessingContextGenerator = generator;
585}
586
587void QgsModelDesignerDialog::updateVariablesGui()
588{
589 mBlockUndoCommands++;
590
591 auto variablesScope = std::make_unique<QgsExpressionContextScope>( tr( "Model Variables" ) );
592 const QVariantMap modelVars = mModel->variables();
593 for ( auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
594 {
595 variablesScope->setVariable( it.key(), it.value() );
596 }
597 QgsExpressionContext variablesContext;
598 variablesContext.appendScope( variablesScope.release() );
599 mVariablesEditor->setContext( &variablesContext );
600 mVariablesEditor->setEditableScopeIndex( 0 );
601
602 mBlockUndoCommands--;
603}
604
605void QgsModelDesignerDialog::setDirty( bool dirty )
606{
607 mHasChanged = dirty;
608 updateWindowTitle();
609 if ( mAlgorithmWidget )
610 {
611 if ( QgsMessageBar *messageBar = mAlgorithmWidget->messageBar() )
612 {
613 QgsMessageBarItem *messageBarItem = messageBar->createMessage( QString(), tr( "The model has changed, this panel should be reloaded." ) );
614 auto reloadButton = new QPushButton( tr( "Reload Now" ) );
615 connect( reloadButton, &QPushButton::clicked, reloadButton, [this] {
616 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
617 {
618 QMessageBox messageBox;
619 messageBox.setIcon( QMessageBox::Icon::Warning );
620 messageBox.setWindowTitle( tr( "Run Model" ) );
621 messageBox.setText( tr( "This model is currently running." ) );
622 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults );
623
624 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
625 buttonReRun->setText( tr( "Terminate and Reload" ) );
626
627 int r = messageBox.exec();
628
629 switch ( r )
630 {
631 case QMessageBox::StandardButton::Cancel:
632 return;
633 case QMessageBox::StandardButton::RestoreDefaults:
634 break;
635 default:
636 break;
637 }
638 }
639 cancelRunningModel();
640 run();
641 } );
642 messageBarItem->layout()->addWidget( reloadButton );
643 messageBar->pushWidget( messageBarItem, Qgis::MessageLevel::Warning );
644 }
645 }
646}
647
648bool QgsModelDesignerDialog::validateSave( SaveAction action )
649{
650 switch ( action )
651 {
652 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
653 break;
654 case QgsModelDesignerDialog::SaveAction::SaveInProject:
655 if ( mNameEdit->text().trimmed().isEmpty() )
656 {
657 mMessageBar->pushWarning( QString(), tr( "Please enter a model name before saving" ) );
658 return false;
659 }
660 break;
661 }
662
663 return true;
664}
665
666bool QgsModelDesignerDialog::checkForUnsavedChanges()
667{
668 if ( isDirty() )
669 {
670 QMessageBox::StandardButton ret = QMessageBox::
671 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 );
672 switch ( ret )
673 {
674 case QMessageBox::Save:
675 return saveModel( false );
676
677 case QMessageBox::Discard:
678 return true;
679
680 default:
681 return false;
682 }
683 }
684 else
685 {
686 return true;
687 }
688}
689
690void QgsModelDesignerDialog::setLastRunResult( const QgsProcessingModelResult &result )
691{
692 mLastResult.mergeWith( result );
693 if ( mScene )
694 mScene->setLastRunResult( mLastResult, mLayerStore );
695}
696
697void QgsModelDesignerDialog::setModelName( const QString &name )
698{
699 mNameEdit->setText( name );
700}
701
702void QgsModelDesignerDialog::zoomIn()
703{
704 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
705 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
706 QgsSettings settings;
707 const double factor = settings.value( u"/qgis/zoom_favor"_s, 2.0 ).toDouble();
708 mView->scale( factor, factor );
709 mView->centerOn( point );
710}
711
712void QgsModelDesignerDialog::zoomOut()
713{
714 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
715 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
716 QgsSettings settings;
717 const double factor = 1.0 / settings.value( u"/qgis/zoom_favor"_s, 2.0 ).toDouble();
718 mView->scale( factor, factor );
719 mView->centerOn( point );
720}
721
722void QgsModelDesignerDialog::zoomActual()
723{
724 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
725 mView->resetTransform();
726 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
727 mView->centerOn( point );
728}
729
730void QgsModelDesignerDialog::zoomFull()
731{
732 QRectF totalRect = mView->scene()->itemsBoundingRect();
733 totalRect.adjust( -10, -10, 10, 10 );
734 mView->fitInView( totalRect, Qt::KeepAspectRatio );
735}
736
737void QgsModelDesignerDialog::newModel()
738{
739 if ( !checkForUnsavedChanges() )
740 return;
741
742 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
743 alg->setProvider( QgsApplication::processingRegistry()->providerById( u"model"_s ) );
744 setModel( alg.release() );
745}
746
747void QgsModelDesignerDialog::exportToImage()
748{
749 QgsSettings settings;
750 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
751
752 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Image" ), lastExportDir, tr( "PNG files (*.png *.PNG)" ) );
753 // return dialog focus on Mac
754 activateWindow();
755 raise();
756 if ( filename.isEmpty() )
757 return;
758
759 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"png"_s );
760
761 const QFileInfo saveFileInfo( filename );
762 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
763
764 repaintModel( false );
765
766 QRectF totalRect = mView->scene()->itemsBoundingRect();
767 totalRect.adjust( -10, -10, 10, 10 );
768 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
769
770 QImage img( totalRect.width(), totalRect.height(), QImage::Format_ARGB32_Premultiplied );
771 img.fill( Qt::white );
772 QPainter painter;
773 painter.setRenderHint( QPainter::Antialiasing );
774 painter.begin( &img );
775 mView->scene()->render( &painter, imageRect, totalRect );
776 painter.end();
777
778 img.save( filename );
779
780 mMessageBar
781 ->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 );
782 repaintModel( true );
783}
784
785void QgsModelDesignerDialog::exportToPdf()
786{
787 QgsSettings settings;
788 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
789
790 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as PDF" ), lastExportDir, tr( "PDF files (*.pdf *.PDF)" ) );
791 // return dialog focus on Mac
792 activateWindow();
793 raise();
794 if ( filename.isEmpty() )
795 return;
796
797 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"pdf"_s );
798
799 const QFileInfo saveFileInfo( filename );
800 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
801
802 repaintModel( false );
803
804 QRectF totalRect = mView->scene()->itemsBoundingRect();
805 totalRect.adjust( -10, -10, 10, 10 );
806 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
807
808 QPdfWriter pdfWriter( filename );
809
810 const double scaleFactor = 96 / 25.4; // based on 96 dpi sizes
811
812 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ), QPageLayout::Portrait, QMarginsF( 0, 0, 0, 0 ) );
813 pageLayout.setMode( QPageLayout::FullPageMode );
814 pdfWriter.setPageLayout( pageLayout );
815
816 QPainter painter( &pdfWriter );
817 mView->scene()->render( &painter, printerRect, totalRect );
818 painter.end();
819
820 mMessageBar
821 ->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 );
822 repaintModel( true );
823}
824
825void QgsModelDesignerDialog::exportToSvg()
826{
827 QgsSettings settings;
828 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
829
830 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as SVG" ), lastExportDir, tr( "SVG files (*.svg *.SVG)" ) );
831 // return dialog focus on Mac
832 activateWindow();
833 raise();
834 if ( filename.isEmpty() )
835 return;
836
837 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"svg"_s );
838
839 const QFileInfo saveFileInfo( filename );
840 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
841
842 repaintModel( false );
843
844 QRectF totalRect = mView->scene()->itemsBoundingRect();
845 totalRect.adjust( -10, -10, 10, 10 );
846 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
847
848 QSvgGenerator svg;
849 svg.setFileName( filename );
850 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
851 svg.setViewBox( svgRect );
852 svg.setTitle( mModel->displayName() );
853
854 QPainter painter( &svg );
855 mView->scene()->render( &painter, svgRect, totalRect );
856 painter.end();
857
858 mMessageBar
859 ->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 );
860 repaintModel( true );
861}
862
863void QgsModelDesignerDialog::exportAsPython()
864{
865 QgsSettings settings;
866 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
867
868 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Python Script" ), lastExportDir, tr( "Processing scripts (*.py *.PY)" ) );
869 // return dialog focus on Mac
870 activateWindow();
871 raise();
872 if ( filename.isEmpty() )
873 return;
874
875 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"py"_s );
876
877 const QFileInfo saveFileInfo( filename );
878 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
879
880 const QString text = mModel->asPythonCode( QgsProcessing::PythonOutputType::PythonQgsProcessingAlgorithmSubclass, 4 ).join( '\n' );
881
882 QFile outFile( filename );
883 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
884 {
885 return;
886 }
887 QTextStream fout( &outFile );
888 fout << text;
889 outFile.close();
890
891 mMessageBar
892 ->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 );
893}
894
895void QgsModelDesignerDialog::toggleComments( bool show )
896{
897 QgsSettings().setValue( u"/Processing/Modeler/ShowComments"_s, show );
898
899 repaintModel( true );
900}
901
902void QgsModelDesignerDialog::toggleFeatureCount( bool show )
903{
904 QgsSettings().setValue( u"/Processing/Modeler/ShowFeatureCount"_s, show );
905
906 repaintModel( true );
907}
908
909void QgsModelDesignerDialog::updateWindowTitle()
910{
911 QString title = tr( "Model Designer" );
912 if ( !mModel->name().isEmpty() )
913 title = mModel->group().isEmpty() ? u"%1: %2"_s.arg( title, mModel->name() ) : u"%1: %2 - %3"_s.arg( title, mModel->group(), mModel->name() );
914
915 if ( isDirty() )
916 title.prepend( '*' );
917
918 setWindowTitle( title );
919}
920
921void QgsModelDesignerDialog::deleteSelected()
922{
923 QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
924 if ( items.empty() )
925 return;
926
927 if ( items.size() == 1 )
928 {
929 items.at( 0 )->deleteComponent();
930 return;
931 }
932
933 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem *p1, QgsModelComponentGraphicItem *p2 ) {
934 // try to delete the easy stuff first, so comments, then outputs, as nothing will depend on these...
935 // NOLINTBEGIN(bugprone-branch-clone)
936
937 // 1. comments
938 if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) && dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
939 return false;
940 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) )
941 return true;
942 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
943 return false;
944 // 2. group boxes
945 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) && dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
946 return false;
947 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) )
948 return true;
949 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
950 return false;
951 // 3. outputs
952 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) && dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
953 return false;
954 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) )
955 return true;
956 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
957 return false;
958 // 4. child algorithms
959 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) && dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
960 return false;
961 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) )
962 return true;
963 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
964 return false;
965 return false;
966 // NOLINTEND(bugprone-branch-clone)
967 } );
968
969
970 beginUndoCommand( tr( "Delete Components" ) );
971
972 QVariant prevState = mModel->toVariant();
973 mBlockUndoCommands++;
974 mBlockRepaints = true;
975 bool failed = false;
976 while ( !items.empty() )
977 {
978 QgsModelComponentGraphicItem *toDelete = nullptr;
979 for ( QgsModelComponentGraphicItem *item : items )
980 {
981 if ( item->canDeleteComponent() )
982 {
983 toDelete = item;
984 break;
985 }
986 }
987
988 if ( !toDelete )
989 {
990 failed = true;
991 break;
992 }
993
994 toDelete->deleteComponent();
995 items.removeAll( toDelete );
996 }
997
998 if ( failed )
999 {
1000 mModel->loadVariant( prevState );
1001 QMessageBox::warning(
1002 nullptr,
1003 QObject::tr( "Could not remove components" ),
1004 QObject::tr(
1005 "Components depend on the selected items.\n"
1006 "Try to remove them before trying deleting these components."
1007 )
1008 );
1009 mBlockUndoCommands--;
1010 mActiveCommand.reset();
1011 }
1012 else
1013 {
1014 mBlockUndoCommands--;
1015 endUndoCommand();
1016 }
1017
1018 mBlockRepaints = false;
1019 repaintModel();
1020}
1021
1022void QgsModelDesignerDialog::populateZoomToMenu()
1023{
1024 mGroupMenu->clear();
1025 for ( const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
1026 {
1027 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
1028 {
1029 QAction *zoomAction = new QAction( box.description(), mGroupMenu );
1030 connect( zoomAction, &QAction::triggered, this, [this, item] {
1031 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
1032 groupRect.adjust( -10, -10, 10, 10 );
1033 mView->fitInView( groupRect, Qt::KeepAspectRatio );
1034 mView->centerOn( item );
1035 } );
1036 mGroupMenu->addAction( zoomAction );
1037 }
1038 }
1039}
1040
1041void QgsModelDesignerDialog::setPanelVisibility( bool hidden )
1042{
1043 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
1044 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
1045
1046 if ( hidden )
1047 {
1048 mPanelStatus.clear();
1049 //record status of all docks
1050 for ( QDockWidget *dock : docks )
1051 {
1052 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(), false ) );
1053 dock->setVisible( false );
1054 }
1055
1056 //record active dock tabs
1057 for ( QTabBar *tabBar : tabBars )
1058 {
1059 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
1060 mPanelStatus[currentTabTitle].isActive = true;
1061 }
1062 }
1063 else
1064 {
1065 //restore visibility of all docks
1066 for ( QDockWidget *dock : docks )
1067 {
1068 if ( mPanelStatus.contains( dock->windowTitle() ) )
1069 {
1070 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
1071 }
1072 }
1073
1074 //restore previously active dock tabs
1075 for ( QTabBar *tabBar : tabBars )
1076 {
1077 //loop through all tabs in tab bar
1078 for ( int i = 0; i < tabBar->count(); ++i )
1079 {
1080 QString tabTitle = tabBar->tabText( i );
1081 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
1082 {
1083 tabBar->setCurrentIndex( i );
1084 }
1085 }
1086 }
1087 mPanelStatus.clear();
1088 }
1089}
1090
1091void QgsModelDesignerDialog::editHelp()
1092{
1093 QgsProcessingHelpEditorDialog dialog( this );
1094 dialog.setWindowTitle( tr( "Edit Model Help" ) );
1095 dialog.setAlgorithm( mModel.get() );
1096 if ( dialog.exec() )
1097 {
1098 beginUndoCommand( tr( "Edit Model Help" ) );
1099 mModel->setHelpContent( dialog.helpContent() );
1100 endUndoCommand();
1101 }
1102}
1103
1104void QgsModelDesignerDialog::runSelectedSteps()
1105{
1106 QSet<QString> children;
1107 const QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
1108 for ( QgsModelComponentGraphicItem *item : items )
1109 {
1110 if ( QgsProcessingModelChildAlgorithm *childAlgorithm = dynamic_cast<QgsProcessingModelChildAlgorithm *>( item->component() ) )
1111 {
1112 children.insert( childAlgorithm->childId() );
1113 }
1114 }
1115
1116 if ( children.isEmpty() )
1117 {
1118 mMessageBar->pushWarning( QString(), tr( "No steps are selected" ) );
1119 return;
1120 }
1121
1122 run( children );
1123}
1124
1125void QgsModelDesignerDialog::runFromChild( const QString &id )
1126{
1127 QSet<QString> children = mModel->dependentChildAlgorithms( id );
1128 children.insert( id );
1129 run( children );
1130}
1131
1132void QgsModelDesignerDialog::cancelRunningModel()
1133{
1134 if ( !mAlgorithmWidget )
1135 return;
1136
1137 // these checks are wrong - mAlgorithmWidget is a QPointer, and we explicitly want to check
1138 // if it gets deleted in the cancel/forceClose dance!
1139 // cppcheck-suppress nullPointerRedundantCheck
1140 mAlgorithmWidget->cancel();
1141 // cppcheck-suppress nullPointerRedundantCheck
1142 mAlgorithmWidget->forceClose();
1143
1144 //Stop tracking change to the previous dialog in the QPointer
1145 if ( mAlgorithmWidget )
1146 {
1147 // this is a work around for the MESSY ownership issues associated with the python subclass
1148 // of QgsProcessingAlgorithmWidgetBase. We have to FORCE all widgets to be deleted prior
1149 // to destruction of this window, and we can't be sure that python will have actually
1150 // deleted the widget when we asked...
1151 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1152 }
1153 mAlgorithmWidget.clear();
1154}
1155
1156void QgsModelDesignerDialog::run( const QSet<QString> &childAlgorithmSubset )
1157{
1158 QStringList errors;
1159 const bool isValid = model()->validate( errors );
1160 if ( !isValid )
1161 {
1162 QMessageBox messageBox;
1163 messageBox.setWindowTitle( tr( "Model is Invalid" ) );
1164 messageBox.setIcon( QMessageBox::Icon::Warning );
1165 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?" ) );
1166 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1167 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1168
1169 QString errorString;
1170 for ( const QString &error : std::as_const( errors ) )
1171 {
1172 QString cleanedError = error;
1173 const thread_local QRegularExpression re( u"<[^>]*>"_s );
1174 cleanedError.replace( re, QString() );
1175 errorString += u"• %1\n"_s.arg( cleanedError );
1176 }
1177
1178 messageBox.setDetailedText( errorString );
1179 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1180 return;
1181 }
1182
1183 if ( !childAlgorithmSubset.isEmpty() )
1184 {
1185 for ( const QString &child : childAlgorithmSubset )
1186 {
1187 // has user previously run all requirements for this step?
1188 const QSet<QString> requirements = mModel->dependsOnChildAlgorithms( child );
1189 for ( const QString &requirement : requirements )
1190 {
1191 if ( !mLastResult.executedChildIds().contains( requirement ) )
1192 {
1193 QMessageBox messageBox;
1194 messageBox.setWindowTitle( tr( "Run Model" ) );
1195 messageBox.setIcon( QMessageBox::Icon::Warning );
1196 messageBox.setText( tr( "Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1197 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1198 messageBox.exec();
1199 return;
1200 }
1201 }
1202 }
1203 }
1204
1205 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
1206 {
1207 QMessageBox messageBox;
1208 messageBox.setIcon( QMessageBox::Icon::Warning );
1209 messageBox.setWindowTitle( tr( "Run Model" ) );
1210 messageBox.setText( tr( "This model is already running." ) );
1211 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults | QMessageBox::StandardButton::Ok );
1212
1213 QAbstractButton *buttonShowRunningAlg = messageBox.button( QMessageBox::StandardButton::Ok );
1214 buttonShowRunningAlg->setText( tr( "Show Progress" ) );
1215
1216 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
1217 buttonReRun->setText( tr( "Cancel and Restart Model" ) );
1218
1219 int r = messageBox.exec();
1220
1221 switch ( r )
1222 {
1223 case QMessageBox::StandardButton::Cancel:
1224 return;
1225 case QMessageBox::StandardButton::RestoreDefaults:
1226 cancelRunningModel();
1227 break;
1228 case QMessageBox::StandardButton::Ok:
1229 mAlgorithmWidget->showWidget();
1230 return;
1231 default:
1232 break;
1233 }
1234 }
1235 else if ( mAlgorithmWidget )
1236 {
1237 // Close and create a new one
1238 mAlgorithmWidget->close();
1239 if ( mAlgorithmWidget )
1240 {
1241 // this is a work around for the MESSY ownership issues associated with the python subclass
1242 // of QgsProcessingAlgorithmWidgetBase. We have to FORCE all widgets to be deleted prior
1243 // to destruction of this window, and we can't be sure that python will have actually
1244 // deleted the widget when we asked...
1245 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1246 }
1247 //Stop tracking change to the previous widget in the QPointer
1248 mAlgorithmWidget.clear();
1249 }
1250
1251 if ( !mAlgorithmWidget )
1252 {
1253 mAlgorithmWidget = createExecutionWidget();
1254 mAlgorithmWidget->hideShortHelp();
1255 mAlgorithmWidget->setTitle( tr( "Run Model" ) );
1256
1257 mAlgorithmWidget->setLogLevel( Qgis::ProcessingLogLevel::ModelDebug );
1258 mAlgorithmWidget->setParameters( mModel->designerParameterValues() );
1259
1260 if ( !childAlgorithmSubset.isEmpty() )
1261 {
1262 mAlgorithmWidget->runButton()->setText( tr( "Run Subset" ) );
1263 mAlgorithmWidget->runButton()->setToolTip( tr( "Runs a subset of the child algorithms from this model" ) );
1264 }
1265
1266 connect( mAlgorithmWidget.get(), &QgsProcessingAlgorithmWidgetBase::algorithmAboutToRun, this, [this, childAlgorithmSubset]( QgsProcessingContext *context ) {
1267 if ( !childAlgorithmSubset.empty() )
1268 {
1269 // start from previous state
1270 auto modelConfig = std::make_unique<QgsProcessingModelInitialRunConfig>();
1271 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1272 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1273 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1274 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1275
1276 // add copies of layers from previous runs to context's layer store, so that they can be used
1277 // when running the subset
1278 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1279 auto previousResultStore = std::make_unique<QgsMapLayerStore>();
1280 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1281 {
1282 std::unique_ptr<QgsMapLayer> clone( it.value()->clone() );
1283 clone->setId( it.value()->id() );
1284 previousResultStore->addMapLayer( clone.release() );
1285 }
1286 previousResultStore->moveToThread( nullptr );
1287 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1288 context->setModelInitialRunConfig( std::move( modelConfig ) );
1289
1290 mScene->resetChildAlgorithmItems( childAlgorithmSubset );
1291
1292 // for all algorithms downstream of the subset which won't be re-run, flag their old results as outdated.
1293 for ( const QString &child : childAlgorithmSubset )
1294 {
1295 const QSet< QString > outdated = mModel->dependentChildAlgorithms( child );
1296 mScene->flagChildrenAsOutdated( outdated );
1297 mOutdatedChildResults.unite( outdated );
1298 }
1299 }
1300 else
1301 {
1302 // reset all child algorithm results
1303 mScene->resetChildAlgorithmItems();
1304 }
1305 } );
1306
1307 connect( mAlgorithmWidget, &QgsProcessingAlgorithmWidgetBase::algorithmFinished, this, [this]( bool, const QVariantMap & ) {
1308 QgsProcessingContext *context = mAlgorithmWidget->processingContext();
1309 // take child output layers
1310 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1311 mLayerStore.takeResultsFrom( *context );
1312
1313 mModel->setDesignerParameterValues( mAlgorithmWidget->createProcessingParameters( QgsProcessingParametersGenerator::Flag::SkipDefaultValueParameters ) );
1314 setLastRunResult( context->modelResult() );
1315 } );
1316 }
1317}
1318
1319void QgsModelDesignerDialog::showChildAlgorithmOutputs( const QString &childId )
1320{
1321 const bool isOutdated = mOutdatedChildResults.contains( childId );
1322 const QString childDescription = mModel->childAlgorithm( childId ).description();
1323
1324 const QgsProcessingModelChildAlgorithmResult result = mLastResult.childResults().value( childId );
1325 const QVariantMap childAlgorithmOutputs = result.outputs();
1326 if ( childAlgorithmOutputs.isEmpty() )
1327 {
1328 mMessageBar->pushWarning( QString(), tr( "No results are available for %1" ).arg( childDescription ) );
1329 return;
1330 }
1331
1332 const QgsProcessingAlgorithm *algorithm = mModel->childAlgorithm( childId ).algorithm();
1333 if ( !algorithm )
1334 {
1335 mMessageBar->pushCritical( QString(), tr( "Results cannot be shown for an invalid model component" ) );
1336 return;
1337 }
1338
1339 const QList<const QgsProcessingParameterDefinition *> outputParams = algorithm->destinationParameterDefinitions();
1340 if ( outputParams.isEmpty() )
1341 {
1342 // this situation should not arise in normal use, we don't show the action in this case
1343 QgsDebugError( "Cannot show results for algorithms with no outputs" );
1344 return;
1345 }
1346
1347 bool foundResults = false;
1348 for ( const QgsProcessingParameterDefinition *outputParam : outputParams )
1349 {
1350 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1351 if ( !output.isValid() )
1352 continue;
1353
1354 if ( output.type() == QVariant::String )
1355 {
1356 if ( QgsMapLayer *resultLayer = QgsProcessingUtils::mapLayerFromString( output.toString(), mLayerStore ) )
1357 {
1358 QgsDebugMsgLevel( u"Loading previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ), 2 );
1359
1360 std::unique_ptr<QgsMapLayer> layer( resultLayer->clone() );
1361
1362 QString baseName;
1363 if ( outputParams.size() > 1 )
1364 baseName = tr( "%1 — %2" ).arg( childDescription, outputParam->name() );
1365 else
1366 baseName = childDescription;
1367
1368 // make name unique, so that's it's easy to see which is the most recent result.
1369 // (this helps when running the model multiple times.)
1370 QString name = baseName;
1371 int counter = 1;
1372 while ( !QgsProject::instance()->mapLayersByName( name ).empty() )
1373 {
1374 counter += 1;
1375 name = tr( "%1 (%2)" ).arg( baseName ).arg( counter );
1376 }
1377
1378 layer->setName( name );
1379
1380 QgsProject::instance()->addMapLayer( layer.release() );
1381 foundResults = true;
1382 }
1383 else
1384 {
1385 // should not happen in normal operation
1386 QgsDebugError( u"Could not load previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ) );
1387 }
1388 }
1389 }
1390
1391 if ( !foundResults )
1392 {
1393 mMessageBar->pushWarning( QString(), tr( "No results are available for %1" ).arg( childDescription ) );
1394 return;
1395 }
1396 else if ( isOutdated )
1397 {
1398 mMessageBar->pushWarning( QString(), tr( "These results are outdated, and may not reflect the most recent model execution" ) );
1399 return;
1400 }
1401}
1402
1403void QgsModelDesignerDialog::showChildAlgorithmLog( const QString &childId )
1404{
1405 const QString childDescription = mModel->childAlgorithm( childId ).description();
1406
1408 // prefer to fetch the log from the item itself -- if we are currently mid-way through
1409 // running the model, it will have the LATEST log available
1410 if ( QgsModelChildAlgorithmGraphicItem *item = mScene->childAlgorithmItem( childId ) )
1411 {
1412 result = item->results();
1413 }
1414 if ( result.htmlLog().isEmpty() )
1415 {
1416 result = mLastResult.childResults().value( childId );
1417 }
1418
1419 if ( result.htmlLog().isEmpty() )
1420 {
1421 mMessageBar->pushWarning( QString(), tr( "No log is available for %1" ).arg( childDescription ) );
1422 return;
1423 }
1424
1425 QgsMessageViewer m( this, QgsGuiUtils::ModalDialogFlags, false );
1426 m.setWindowTitle( childDescription );
1427 m.setCheckBoxVisible( false );
1428 m.setMessageAsHtml( result.htmlLog() );
1429 m.exec();
1430}
1431
1432void QgsModelDesignerDialog::onItemFocused( QgsModelComponentGraphicItem *item )
1433{
1434 QgsProcessingParameterWidgetContext widgetContext = createWidgetContext();
1435 widgetContext.registerProcessingContextGenerator( mProcessingContextGenerator );
1436 widgetContext.setModelDesignerDialog( this );
1437 QgsProcessingContext *context = mProcessingContextGenerator->processingContext();
1438
1439 if ( !item || !item->component() )
1440 {
1441 mConfigWidget->showComponentConfig( nullptr, *context, widgetContext );
1442 }
1443 else
1444 {
1445 mConfigWidget->showComponentConfig( item->component(), *context, widgetContext );
1446
1447 if ( auto childAlgorithmItem = qobject_cast< QgsModelChildAlgorithmGraphicItem * >( item ) )
1448 {
1449 connect( childAlgorithmItem, &QgsModelChildAlgorithmGraphicItem::rebuildConfigurationDockWidget, childAlgorithmItem, [this] {
1450 QgsProcessingParameterWidgetContext widgetContext = createWidgetContext();
1451 widgetContext.registerProcessingContextGenerator( mProcessingContextGenerator );
1452 widgetContext.setModelDesignerDialog( this );
1453 QgsProcessingContext *context = mProcessingContextGenerator->processingContext();
1454 mConfigWidget->showComponentConfig( nullptr, *context, widgetContext );
1455 } );
1456 }
1457 }
1458}
1459
1460void QgsModelDesignerDialog::validate()
1461{
1462 QStringList issues;
1463 if ( model()->validate( issues ) )
1464 {
1465 mMessageBar->pushSuccess( QString(), tr( "Model is valid!" ) );
1466 }
1467 else
1468 {
1469 QgsMessageBarItem *messageWidget = QgsMessageBar::createMessage( QString(), tr( "Model is invalid!" ) );
1470 QPushButton *detailsButton = new QPushButton( tr( "Details" ) );
1471 connect( detailsButton, &QPushButton::clicked, detailsButton, [detailsButton, issues] {
1472 QgsMessageViewer *dialog = new QgsMessageViewer( detailsButton );
1473 dialog->setTitle( tr( "Model is Invalid" ) );
1474
1475 QString longMessage = tr( "<p>This model is not valid:</p>" ) + u"<ul>"_s;
1476 for ( const QString &issue : issues )
1477 {
1478 longMessage += u"<li>%1</li>"_s.arg( issue );
1479 }
1480 longMessage += "</ul>"_L1;
1481
1482 dialog->setMessage( longMessage, Qgis::StringFormat::Html );
1483 dialog->showMessage();
1484 } );
1485 messageWidget->layout()->addWidget( detailsButton );
1486 mMessageBar->clearWidgets();
1487 mMessageBar->pushWidget( messageWidget, Qgis::MessageLevel::Warning, 0 );
1488 }
1489}
1490
1491void QgsModelDesignerDialog::reorderInputs()
1492{
1493 QgsModelInputReorderDialog dlg( this );
1494 dlg.setModel( mModel.get() );
1495 if ( dlg.exec() )
1496 {
1497 const QStringList inputOrder = dlg.inputOrder();
1498 beginUndoCommand( tr( "Reorder Inputs" ) );
1499 mModel->setParameterOrder( inputOrder );
1500 endUndoCommand();
1501 }
1502}
1503
1504void QgsModelDesignerDialog::reorderOutputs()
1505{
1506 QgsModelOutputReorderDialog dlg( this );
1507 dlg.setModel( mModel.get() );
1508 if ( dlg.exec() )
1509 {
1510 const QStringList outputOrder = dlg.outputOrder();
1511 beginUndoCommand( tr( "Reorder Outputs" ) );
1512 mModel->setOutputOrder( outputOrder );
1513 mModel->setOutputGroup( dlg.outputGroup() );
1514 endUndoCommand();
1515 }
1516}
1517
1518bool QgsModelDesignerDialog::isDirty() const
1519{
1520 return mHasChanged && mUndoStack->index() != -1;
1521}
1522
1523void QgsModelDesignerDialog::fillInputsTree()
1524{
1525 const QIcon icon = QgsApplication::getThemeIcon( u"mIconModelInput.svg"_s );
1526 auto parametersItem = std::make_unique<QTreeWidgetItem>();
1527 parametersItem->setText( 0, tr( "Parameters" ) );
1528 QList<QgsProcessingParameterType *> available = QgsApplication::processingRegistry()->parameterTypes();
1529 std::sort( available.begin(), available.end(), []( const QgsProcessingParameterType *a, const QgsProcessingParameterType *b ) -> bool {
1530 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1531 } );
1532
1533 for ( QgsProcessingParameterType *param : std::as_const( available ) )
1534 {
1536 {
1537 auto paramItem = std::make_unique<QTreeWidgetItem>();
1538 paramItem->setText( 0, param->name() );
1539 paramItem->setData( 0, Qt::UserRole, param->id() );
1540 paramItem->setIcon( 0, icon );
1541 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1542 paramItem->setToolTip( 0, param->description() );
1543 parametersItem->addChild( paramItem.release() );
1544 }
1545 }
1546 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1547 mInputsTreeWidget->topLevelItem( 0 )->setExpanded( true );
1548}
1549
1550
1551//
1552// QgsModelChildDependenciesWidget
1553//
1554
1555QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model, const QString &childId )
1556 : QWidget( parent )
1557 , mModel( model )
1558 , mChildId( childId )
1559{
1560 QHBoxLayout *hl = new QHBoxLayout();
1561 hl->setContentsMargins( 0, 0, 0, 0 );
1562
1563 mLineEdit = new QLineEdit();
1564 mLineEdit->setEnabled( false );
1565 hl->addWidget( mLineEdit, 1 );
1566
1567 mToolButton = new QToolButton();
1568 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1569 hl->addWidget( mToolButton );
1570
1571 setLayout( hl );
1572
1573 mLineEdit->setText( tr( "%1 dependencies selected" ).arg( 0 ) );
1574
1575 connect( mToolButton, &QToolButton::clicked, this, &QgsModelChildDependenciesWidget::showDialog );
1576}
1577
1578void QgsModelChildDependenciesWidget::setValue( const QList<QgsProcessingModelChildDependency> &value )
1579{
1580 mValue = value;
1581
1582 updateSummaryText();
1583}
1584
1585void QgsModelChildDependenciesWidget::showDialog()
1586{
1587 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1588
1589 QVariantList availableOptions;
1590 for ( const QgsProcessingModelChildDependency &dep : available )
1591 availableOptions << QVariant::fromValue( dep );
1592 QVariantList selectedOptions;
1593 for ( const QgsProcessingModelChildDependency &dep : mValue )
1594 selectedOptions << QVariant::fromValue( dep );
1595
1597 if ( panel )
1598 {
1599 QgsProcessingMultipleSelectionPanelWidget *widget = new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1600 widget->setPanelTitle( tr( "Algorithm Dependencies" ) );
1601
1602 widget->setValueFormatter( [this]( const QVariant &v ) -> QString {
1603 const QgsProcessingModelChildDependency dep = v.value<QgsProcessingModelChildDependency>();
1604
1605 const QString description = mModel->childAlgorithm( dep.childId ).description();
1606 if ( dep.conditionalBranch.isEmpty() )
1607 return description;
1608 else
1609 return tr( "Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1610 } );
1611
1612 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged, this, [this, widget]() {
1613 QList<QgsProcessingModelChildDependency> res;
1614 for ( const QVariant &v : widget->selectedOptions() )
1615 {
1616 res << v.value<QgsProcessingModelChildDependency>();
1617 }
1618 setValue( res );
1619 } );
1620 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::acceptClicked, widget, &QgsPanelWidget::acceptPanel );
1621 panel->openPanel( widget );
1622 }
1623}
1624
1625void QgsModelChildDependenciesWidget::updateSummaryText()
1626{
1627 mLineEdit->setText( tr( "%n dependencies selected", nullptr, mValue.count() ) );
1628}
1629
@ ExposeToModeler
Is this parameter available in the modeler. Is set to on by default.
Definition qgis.h:3921
@ Warning
Warning message.
Definition qgis.h:162
@ Critical
Critical/error message.
Definition qgis.h:163
@ Success
Used for reporting a successful operation.
Definition qgis.h:164
@ Html
HTML message.
Definition qgis.h:177
@ ModelDebug
Model debug level logging. Includes verbose logging and other outputs useful for debugging models.
Definition qgis.h:3841
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.
A QDockWidget subclass with more fine-grained control over how the widget is closed or opened.
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...
Definition qgsgui.cpp:224
void removeAllMapLayers()
Removes all registered layers.
Base class for all map layer types.
Definition qgsmaplayer.h:83
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
A generic message view for displaying QGIS messages.
void setMessage(const QString &message, Qgis::StringFormat format) override
Sets message, it won't be displayed until.
void setTitle(const QString &title) override
Sets title for the messages.
void showMessage(bool blocking=true) override
display the message to the user and deletes itself
A dockable panel widget stack which allows users to specify the properties of a Processing model comp...
Model designer view tool for panning a model.
Base class for any widget that can be shown as an inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void acceptPanel()
Accept the panel.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
Abstract base class for processing algorithms.
An interface for objects which can create Processing contexts.
Contains information about the context in which a processing algorithm is executed.
QgsProcessingModelResult modelResult() const
Returns the model results, populated when the context is used to run a model algorithm.
QgsMapLayerStore * temporaryLayerStore()
Returns a reference to the layer store used for storing temporary layers during algorithm execution.
Base class for providing feedback from a processing algorithm.
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.
void childResultReported(const QString &childId, const QgsProcessingModelChildAlgorithmResult &result)
Emitted when the result of a child algorithm has been reported.
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.
Contains settings which reflect the context in which a Processing parameter widget is shown.
void setModelDesignerDialog(QgsModelDesignerDialog *dialog)
Sets the associated model designer dialog, if applicable.
void registerProcessingContextGenerator(QgsProcessingContextGenerator *generator)
Registers a Processing context generator class that will be used to retrieve a Processing context for...
@ SkipDefaultValueParameters
Parameters which are unchanged from their default values should not be included.
QList< QgsProcessingParameterType * > parameterTypes() const
Returns a list with all known parameter types.
A proxy model for providers and algorithms shown within the Processing toolbox.
@ ShowKnownIssues
Show algorithms with known issues (hidden by default).
@ Modeler
Filters out any algorithms and content which should not be shown in the modeler.
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.
Definition qgssettings.h:68
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.
void scopeChanged()
Emitted when the user has modified a scope using the widget.
void addDockWidget(QMainWindow *window, Qt::DockWidgetArea area, QDockWidget *dockwidget)
Add a dock widget to a main window.
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)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
constexpr QObjectUniquePtr< Tp > make_qobject_unique(Args &&...args)
Create an object owned by a QObjectUniquePtr.