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