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