QGIS API Documentation 4.3.0-Master (e17dae1dea8)
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 context.setModelDesignerDialog( this );
583 return context;
584}
585
586void QgsModelDesignerDialog::activate()
587{
588 show();
589 raise();
590 setWindowState( windowState() & ~Qt::WindowMinimized );
591 activateWindow();
592}
593
594void QgsModelDesignerDialog::registerProcessingContextGenerator( QgsProcessingContextGenerator *generator )
595{
596 mProcessingContextGenerator = generator;
597}
598
599QgsProcessingContext *QgsModelDesignerDialog::processingContext() const
600{
601 if ( mProcessingContextGenerator )
602 {
603 return mProcessingContextGenerator->processingContext();
604 }
605 return nullptr;
606}
607
608void QgsModelDesignerDialog::updateVariablesGui()
609{
610 mBlockUndoCommands++;
611
612 auto variablesScope = std::make_unique<QgsExpressionContextScope>( tr( "Model Variables" ) );
613 const QVariantMap modelVars = mModel->variables();
614 for ( auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
615 {
616 variablesScope->setVariable( it.key(), it.value() );
617 }
618 QgsExpressionContext variablesContext;
619 variablesContext.appendScope( variablesScope.release() );
620 mVariablesEditor->setContext( &variablesContext );
621 mVariablesEditor->setEditableScopeIndex( 0 );
622
623 mBlockUndoCommands--;
624}
625
626void QgsModelDesignerDialog::setDirty( bool dirty )
627{
628 mHasChanged = dirty;
629 updateWindowTitle();
630 if ( mAlgorithmWidget )
631 {
632 if ( QgsMessageBar *messageBar = mAlgorithmWidget->messageBar() )
633 {
634 QgsMessageBarItem *messageBarItem = messageBar->createMessage( QString(), tr( "The model has changed, this panel should be reloaded." ) );
635 auto reloadButton = new QPushButton( tr( "Reload Now" ) );
636 connect( reloadButton, &QPushButton::clicked, reloadButton, [this] {
637 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
638 {
639 QMessageBox messageBox;
640 messageBox.setIcon( QMessageBox::Icon::Warning );
641 messageBox.setWindowTitle( tr( "Run Model" ) );
642 messageBox.setText( tr( "This model is currently running." ) );
643 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults );
644
645 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
646 buttonReRun->setText( tr( "Terminate and Reload" ) );
647
648 int r = messageBox.exec();
649
650 switch ( r )
651 {
652 case QMessageBox::StandardButton::Cancel:
653 return;
654 case QMessageBox::StandardButton::RestoreDefaults:
655 break;
656 default:
657 break;
658 }
659 }
660 cancelRunningModel();
661 run();
662 } );
663 messageBarItem->layout()->addWidget( reloadButton );
664 messageBar->pushWidget( messageBarItem, Qgis::MessageLevel::Warning );
665 }
666 }
667}
668
669bool QgsModelDesignerDialog::validateSave( SaveAction action )
670{
671 switch ( action )
672 {
673 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
674 break;
675 case QgsModelDesignerDialog::SaveAction::SaveInProject:
676 if ( mNameEdit->text().trimmed().isEmpty() )
677 {
678 mMessageBar->pushWarning( QString(), tr( "Please enter a model name before saving" ) );
679 return false;
680 }
681 break;
682 }
683
684 return true;
685}
686
687bool QgsModelDesignerDialog::checkForUnsavedChanges()
688{
689 if ( isDirty() )
690 {
691 QMessageBox::StandardButton ret = QMessageBox::
692 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 );
693 switch ( ret )
694 {
695 case QMessageBox::Save:
696 return saveModel( false );
697
698 case QMessageBox::Discard:
699 return true;
700
701 default:
702 return false;
703 }
704 }
705 else
706 {
707 return true;
708 }
709}
710
711void QgsModelDesignerDialog::setLastRunResult( const QgsProcessingModelResult &result )
712{
713 mLastResult.mergeWith( result );
714 if ( mScene )
715 mScene->setLastRunResult( mLastResult, mLayerStore );
716}
717
718void QgsModelDesignerDialog::setModelName( const QString &name )
719{
720 mNameEdit->setText( name );
721}
722
723void QgsModelDesignerDialog::zoomIn()
724{
725 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
726 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
727 QgsSettings settings;
728 const double factor = settings.value( u"/qgis/zoom_favor"_s, 2.0 ).toDouble();
729 mView->scale( factor, factor );
730 mView->centerOn( point );
731}
732
733void QgsModelDesignerDialog::zoomOut()
734{
735 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
736 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
737 QgsSettings settings;
738 const double factor = 1.0 / settings.value( u"/qgis/zoom_favor"_s, 2.0 ).toDouble();
739 mView->scale( factor, factor );
740 mView->centerOn( point );
741}
742
743void QgsModelDesignerDialog::zoomActual()
744{
745 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
746 mView->resetTransform();
747 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
748 mView->centerOn( point );
749}
750
751void QgsModelDesignerDialog::zoomFull()
752{
753 QRectF totalRect = mView->scene()->itemsBoundingRect();
754 totalRect.adjust( -10, -10, 10, 10 );
755 mView->fitInView( totalRect, Qt::KeepAspectRatio );
756}
757
758void QgsModelDesignerDialog::newModel()
759{
760 if ( !checkForUnsavedChanges() )
761 return;
762
763 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
764 alg->setProvider( QgsApplication::processingRegistry()->providerById( u"model"_s ) );
765 setModel( alg.release() );
766}
767
768void QgsModelDesignerDialog::exportToImage()
769{
770 QgsSettings settings;
771 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
772
773 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Image" ), lastExportDir, tr( "PNG files (*.png *.PNG)" ) );
774 // return dialog focus on Mac
775 activateWindow();
776 raise();
777 if ( filename.isEmpty() )
778 return;
779
780 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"png"_s );
781
782 const QFileInfo saveFileInfo( filename );
783 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
784
785 repaintModel( false );
786
787 QRectF totalRect = mView->scene()->itemsBoundingRect();
788 totalRect.adjust( -10, -10, 10, 10 );
789 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
790
791 QImage img( totalRect.width(), totalRect.height(), QImage::Format_ARGB32_Premultiplied );
792 img.fill( Qt::white );
793 QPainter painter;
794 painter.setRenderHint( QPainter::Antialiasing );
795 painter.begin( &img );
796 mView->scene()->render( &painter, imageRect, totalRect );
797 painter.end();
798
799 img.save( filename );
800
801 mMessageBar
802 ->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 );
803 repaintModel( true );
804}
805
806void QgsModelDesignerDialog::exportToPdf()
807{
808 QgsSettings settings;
809 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
810
811 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as PDF" ), lastExportDir, tr( "PDF files (*.pdf *.PDF)" ) );
812 // return dialog focus on Mac
813 activateWindow();
814 raise();
815 if ( filename.isEmpty() )
816 return;
817
818 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"pdf"_s );
819
820 const QFileInfo saveFileInfo( filename );
821 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
822
823 repaintModel( false );
824
825 QRectF totalRect = mView->scene()->itemsBoundingRect();
826 totalRect.adjust( -10, -10, 10, 10 );
827 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
828
829 QPdfWriter pdfWriter( filename );
830
831 const double scaleFactor = 96 / 25.4; // based on 96 dpi sizes
832
833 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ), QPageLayout::Portrait, QMarginsF( 0, 0, 0, 0 ) );
834 pageLayout.setMode( QPageLayout::FullPageMode );
835 pdfWriter.setPageLayout( pageLayout );
836
837 QPainter painter( &pdfWriter );
838 mView->scene()->render( &painter, printerRect, totalRect );
839 painter.end();
840
841 mMessageBar
842 ->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 );
843 repaintModel( true );
844}
845
846void QgsModelDesignerDialog::exportToSvg()
847{
848 QgsSettings settings;
849 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
850
851 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as SVG" ), lastExportDir, tr( "SVG files (*.svg *.SVG)" ) );
852 // return dialog focus on Mac
853 activateWindow();
854 raise();
855 if ( filename.isEmpty() )
856 return;
857
858 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"svg"_s );
859
860 const QFileInfo saveFileInfo( filename );
861 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
862
863 repaintModel( false );
864
865 QRectF totalRect = mView->scene()->itemsBoundingRect();
866 totalRect.adjust( -10, -10, 10, 10 );
867 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
868
869 QSvgGenerator svg;
870 svg.setFileName( filename );
871 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
872 svg.setViewBox( svgRect );
873 svg.setTitle( mModel->displayName() );
874
875 QPainter painter( &svg );
876 mView->scene()->render( &painter, svgRect, totalRect );
877 painter.end();
878
879 mMessageBar
880 ->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 );
881 repaintModel( true );
882}
883
884void QgsModelDesignerDialog::exportAsPython()
885{
886 QgsSettings settings;
887 QString lastExportDir = settings.value( u"lastModelDesignerExportDir"_s, QDir::homePath(), QgsSettings::App ).toString();
888
889 QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Python Script" ), lastExportDir, tr( "Processing scripts (*.py *.PY)" ) );
890 // return dialog focus on Mac
891 activateWindow();
892 raise();
893 if ( filename.isEmpty() )
894 return;
895
896 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"py"_s );
897
898 const QFileInfo saveFileInfo( filename );
899 settings.setValue( u"lastModelDesignerExportDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
900
901 const QString text = mModel->asPythonCode( QgsProcessing::PythonOutputType::PythonQgsProcessingAlgorithmSubclass, 4 ).join( '\n' );
902
903 QFile outFile( filename );
904 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
905 {
906 return;
907 }
908 QTextStream fout( &outFile );
909 fout << text;
910 outFile.close();
911
912 mMessageBar
913 ->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 );
914}
915
916void QgsModelDesignerDialog::toggleComments( bool show )
917{
918 QgsSettings().setValue( u"/Processing/Modeler/ShowComments"_s, show );
919
920 repaintModel( true );
921}
922
923void QgsModelDesignerDialog::toggleFeatureCount( bool show )
924{
925 QgsSettings().setValue( u"/Processing/Modeler/ShowFeatureCount"_s, show );
926
927 repaintModel( true );
928}
929
930void QgsModelDesignerDialog::updateWindowTitle()
931{
932 QString title = tr( "Model Designer" );
933 if ( !mModel->name().isEmpty() )
934 title = mModel->group().isEmpty() ? u"%1: %2"_s.arg( title, mModel->name() ) : u"%1: %2 - %3"_s.arg( title, mModel->group(), mModel->name() );
935
936 if ( isDirty() )
937 title.prepend( '*' );
938
939 setWindowTitle( title );
940}
941
942void QgsModelDesignerDialog::deleteSelected()
943{
944 QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
945 if ( items.empty() )
946 return;
947
948 if ( items.size() == 1 )
949 {
950 items.at( 0 )->deleteComponent();
951 return;
952 }
953
954 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem *p1, QgsModelComponentGraphicItem *p2 ) {
955 // try to delete the easy stuff first, so comments, then outputs, as nothing will depend on these...
956 // NOLINTBEGIN(bugprone-branch-clone)
957
958 // 1. comments
959 if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) && dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
960 return false;
961 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) )
962 return true;
963 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
964 return false;
965 // 2. group boxes
966 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) && dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
967 return false;
968 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) )
969 return true;
970 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
971 return false;
972 // 3. outputs
973 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) && dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
974 return false;
975 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) )
976 return true;
977 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
978 return false;
979 // 4. child algorithms
980 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) && dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
981 return false;
982 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) )
983 return true;
984 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
985 return false;
986 return false;
987 // NOLINTEND(bugprone-branch-clone)
988 } );
989
990
991 beginUndoCommand( tr( "Delete Components" ) );
992
993 QVariant prevState = mModel->toVariant();
994 mBlockUndoCommands++;
995 mBlockRepaints = true;
996 bool failed = false;
997 while ( !items.empty() )
998 {
999 QgsModelComponentGraphicItem *toDelete = nullptr;
1000 for ( QgsModelComponentGraphicItem *item : items )
1001 {
1002 if ( item->canDeleteComponent() )
1003 {
1004 toDelete = item;
1005 break;
1006 }
1007 }
1008
1009 if ( !toDelete )
1010 {
1011 failed = true;
1012 break;
1013 }
1014
1015 toDelete->deleteComponent();
1016 items.removeAll( toDelete );
1017 }
1018
1019 if ( failed )
1020 {
1021 mModel->loadVariant( prevState );
1022 QMessageBox::warning(
1023 nullptr,
1024 QObject::tr( "Could not remove components" ),
1025 QObject::tr(
1026 "Components depend on the selected items.\n"
1027 "Try to remove them before trying deleting these components."
1028 )
1029 );
1030 mBlockUndoCommands--;
1031 mActiveCommand.reset();
1032 }
1033 else
1034 {
1035 mBlockUndoCommands--;
1036 endUndoCommand();
1037 }
1038
1039 mBlockRepaints = false;
1040 repaintModel();
1041}
1042
1043void QgsModelDesignerDialog::populateZoomToMenu()
1044{
1045 mGroupMenu->clear();
1046 for ( const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
1047 {
1048 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
1049 {
1050 QAction *zoomAction = new QAction( box.description(), mGroupMenu );
1051 connect( zoomAction, &QAction::triggered, this, [this, item] {
1052 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
1053 groupRect.adjust( -10, -10, 10, 10 );
1054 mView->fitInView( groupRect, Qt::KeepAspectRatio );
1055 mView->centerOn( item );
1056 } );
1057 mGroupMenu->addAction( zoomAction );
1058 }
1059 }
1060}
1061
1062void QgsModelDesignerDialog::setPanelVisibility( bool hidden )
1063{
1064 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
1065 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
1066
1067 if ( hidden )
1068 {
1069 mPanelStatus.clear();
1070 //record status of all docks
1071 for ( QDockWidget *dock : docks )
1072 {
1073 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(), false ) );
1074 dock->setVisible( false );
1075 }
1076
1077 //record active dock tabs
1078 for ( QTabBar *tabBar : tabBars )
1079 {
1080 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
1081 mPanelStatus[currentTabTitle].isActive = true;
1082 }
1083 }
1084 else
1085 {
1086 //restore visibility of all docks
1087 for ( QDockWidget *dock : docks )
1088 {
1089 if ( mPanelStatus.contains( dock->windowTitle() ) )
1090 {
1091 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
1092 }
1093 }
1094
1095 //restore previously active dock tabs
1096 for ( QTabBar *tabBar : tabBars )
1097 {
1098 //loop through all tabs in tab bar
1099 for ( int i = 0; i < tabBar->count(); ++i )
1100 {
1101 QString tabTitle = tabBar->tabText( i );
1102 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
1103 {
1104 tabBar->setCurrentIndex( i );
1105 }
1106 }
1107 }
1108 mPanelStatus.clear();
1109 }
1110}
1111
1112void QgsModelDesignerDialog::editHelp()
1113{
1114 QgsProcessingHelpEditorDialog dialog( this );
1115 dialog.setWindowTitle( tr( "Edit Model Help" ) );
1116 dialog.setAlgorithm( mModel.get() );
1117 if ( dialog.exec() )
1118 {
1119 beginUndoCommand( tr( "Edit Model Help" ) );
1120 mModel->setHelpContent( dialog.helpContent() );
1121 endUndoCommand();
1122 }
1123}
1124
1125void QgsModelDesignerDialog::runSelectedSteps()
1126{
1127 QSet<QString> children;
1128 const QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
1129 for ( QgsModelComponentGraphicItem *item : items )
1130 {
1131 if ( QgsProcessingModelChildAlgorithm *childAlgorithm = dynamic_cast<QgsProcessingModelChildAlgorithm *>( item->component() ) )
1132 {
1133 children.insert( childAlgorithm->childId() );
1134 }
1135 }
1136
1137 if ( children.isEmpty() )
1138 {
1139 mMessageBar->pushWarning( QString(), tr( "No steps are selected" ) );
1140 return;
1141 }
1142
1143 run( children );
1144}
1145
1146void QgsModelDesignerDialog::runFromChild( const QString &id )
1147{
1148 QSet<QString> children = mModel->dependentChildAlgorithms( id );
1149 children.insert( id );
1150 run( children );
1151}
1152
1153void QgsModelDesignerDialog::cancelRunningModel()
1154{
1155 if ( !mAlgorithmWidget )
1156 return;
1157
1158 // these checks are wrong - mAlgorithmWidget is a QPointer, and we explicitly want to check
1159 // if it gets deleted in the cancel/forceClose dance!
1160 // cppcheck-suppress nullPointerRedundantCheck
1161 mAlgorithmWidget->cancel();
1162 // cppcheck-suppress nullPointerRedundantCheck
1163 mAlgorithmWidget->forceClose();
1164
1165 //Stop tracking change to the previous dialog in the QPointer
1166 if ( mAlgorithmWidget )
1167 {
1168 // this is a work around for the MESSY ownership issues associated with the python subclass
1169 // of QgsProcessingAlgorithmWidgetBase. We have to FORCE all widgets to be deleted prior
1170 // to destruction of this window, and we can't be sure that python will have actually
1171 // deleted the widget when we asked...
1172 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1173 }
1174 mAlgorithmWidget.clear();
1175}
1176
1177void QgsModelDesignerDialog::run( const QSet<QString> &childAlgorithmSubset )
1178{
1179 QStringList errors;
1180 const bool isValid = model()->validate( errors );
1181 if ( !isValid )
1182 {
1183 QMessageBox messageBox;
1184 messageBox.setWindowTitle( tr( "Model is Invalid" ) );
1185 messageBox.setIcon( QMessageBox::Icon::Warning );
1186 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?" ) );
1187 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1188 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1189
1190 QString errorString;
1191 for ( const QString &error : std::as_const( errors ) )
1192 {
1193 QString cleanedError = error;
1194 const thread_local QRegularExpression re( u"<[^>]*>"_s );
1195 cleanedError.replace( re, QString() );
1196 errorString += u"• %1\n"_s.arg( cleanedError );
1197 }
1198
1199 messageBox.setDetailedText( errorString );
1200 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1201 return;
1202 }
1203
1204 if ( !childAlgorithmSubset.isEmpty() )
1205 {
1206 for ( const QString &child : childAlgorithmSubset )
1207 {
1208 // has user previously run all requirements for this step?
1209 const QSet<QString> requirements = mModel->dependsOnChildAlgorithms( child );
1210 for ( const QString &requirement : requirements )
1211 {
1212 if ( !mLastResult.executedChildIds().contains( requirement ) )
1213 {
1214 QMessageBox messageBox;
1215 messageBox.setWindowTitle( tr( "Run Model" ) );
1216 messageBox.setIcon( QMessageBox::Icon::Warning );
1217 messageBox.setText( tr( "Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1218 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1219 messageBox.exec();
1220 return;
1221 }
1222 }
1223 }
1224 }
1225
1226 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
1227 {
1228 QMessageBox messageBox;
1229 messageBox.setIcon( QMessageBox::Icon::Warning );
1230 messageBox.setWindowTitle( tr( "Run Model" ) );
1231 messageBox.setText( tr( "This model is already running." ) );
1232 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults | QMessageBox::StandardButton::Ok );
1233
1234 QAbstractButton *buttonShowRunningAlg = messageBox.button( QMessageBox::StandardButton::Ok );
1235 buttonShowRunningAlg->setText( tr( "Show Progress" ) );
1236
1237 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
1238 buttonReRun->setText( tr( "Cancel and Restart Model" ) );
1239
1240 int r = messageBox.exec();
1241
1242 switch ( r )
1243 {
1244 case QMessageBox::StandardButton::Cancel:
1245 return;
1246 case QMessageBox::StandardButton::RestoreDefaults:
1247 cancelRunningModel();
1248 break;
1249 case QMessageBox::StandardButton::Ok:
1250 mAlgorithmWidget->showWidget();
1251 return;
1252 default:
1253 break;
1254 }
1255 }
1256 else if ( mAlgorithmWidget )
1257 {
1258 // Close and create a new one
1259 mAlgorithmWidget->close();
1260 if ( mAlgorithmWidget )
1261 {
1262 // this is a work around for the MESSY ownership issues associated with the python subclass
1263 // of QgsProcessingAlgorithmWidgetBase. We have to FORCE all widgets to be deleted prior
1264 // to destruction of this window, and we can't be sure that python will have actually
1265 // deleted the widget when we asked...
1266 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1267 }
1268 //Stop tracking change to the previous widget in the QPointer
1269 mAlgorithmWidget.clear();
1270 }
1271
1272 if ( !mAlgorithmWidget )
1273 {
1274 mAlgorithmWidget = createExecutionWidget();
1275 mAlgorithmWidget->hideShortHelp();
1276 mAlgorithmWidget->setTitle( tr( "Run Model" ) );
1277
1278 mAlgorithmWidget->setLogLevel( Qgis::ProcessingLogLevel::ModelDebug );
1279 mAlgorithmWidget->setParameters( mModel->designerParameterValues() );
1280
1281 if ( !childAlgorithmSubset.isEmpty() )
1282 {
1283 mAlgorithmWidget->runButton()->setText( tr( "Run Subset" ) );
1284 mAlgorithmWidget->runButton()->setToolTip( tr( "Runs a subset of the child algorithms from this model" ) );
1285 }
1286
1287 connect( mAlgorithmWidget.get(), &QgsProcessingAlgorithmWidgetBase::algorithmAboutToRun, this, [this, childAlgorithmSubset]( QgsProcessingContext *context ) {
1288 if ( !childAlgorithmSubset.empty() )
1289 {
1290 // start from previous state
1291 auto modelConfig = std::make_unique<QgsProcessingModelInitialRunConfig>();
1292 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1293 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1294 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1295 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1296
1297 // add copies of layers from previous runs to context's layer store, so that they can be used
1298 // when running the subset
1299 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1300 auto previousResultStore = std::make_unique<QgsMapLayerStore>();
1301 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1302 {
1303 std::unique_ptr<QgsMapLayer> clone( it.value()->clone() );
1304 clone->setId( it.value()->id() );
1305 previousResultStore->addMapLayer( clone.release() );
1306 }
1307 previousResultStore->moveToThread( nullptr );
1308 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1309 context->setModelInitialRunConfig( std::move( modelConfig ) );
1310
1311 mScene->resetChildAlgorithmItems( childAlgorithmSubset );
1312
1313 // for all algorithms downstream of the subset which won't be re-run, flag their old results as outdated.
1314 for ( const QString &child : childAlgorithmSubset )
1315 {
1316 const QSet< QString > outdated = mModel->dependentChildAlgorithms( child );
1317 mScene->flagChildrenAsOutdated( outdated );
1318 mOutdatedChildResults.unite( outdated );
1319 }
1320 }
1321 else
1322 {
1323 // reset all child algorithm results
1324 mScene->resetChildAlgorithmItems();
1325 }
1326 } );
1327
1328 connect( mAlgorithmWidget, &QgsProcessingAlgorithmWidgetBase::algorithmFinished, this, [this]( bool, const QVariantMap & ) {
1329 QgsProcessingContext *context = mAlgorithmWidget->processingContext();
1330 // take child output layers
1331 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1332 mLayerStore.takeResultsFrom( *context );
1333
1334 mModel->setDesignerParameterValues( mAlgorithmWidget->createProcessingParameters( QgsProcessingParametersGenerator::Flag::SkipDefaultValueParameters ) );
1335 setLastRunResult( context->modelResult() );
1336 } );
1337 }
1338}
1339
1340void QgsModelDesignerDialog::showChildAlgorithmOutputs( const QString &childId )
1341{
1342 const bool isOutdated = mOutdatedChildResults.contains( childId );
1343 const QString childDescription = mModel->childAlgorithm( childId ).description();
1344
1345 const QgsProcessingModelChildAlgorithmResult result = mLastResult.childResults().value( childId );
1346 const QVariantMap childAlgorithmOutputs = result.outputs();
1347 if ( childAlgorithmOutputs.isEmpty() )
1348 {
1349 mMessageBar->pushWarning( QString(), tr( "No results are available for %1" ).arg( childDescription ) );
1350 return;
1351 }
1352
1353 const QgsProcessingAlgorithm *algorithm = mModel->childAlgorithm( childId ).algorithm();
1354 if ( !algorithm )
1355 {
1356 mMessageBar->pushCritical( QString(), tr( "Results cannot be shown for an invalid model component" ) );
1357 return;
1358 }
1359
1360 const QList<const QgsProcessingParameterDefinition *> outputParams = algorithm->destinationParameterDefinitions();
1361 if ( outputParams.isEmpty() )
1362 {
1363 // this situation should not arise in normal use, we don't show the action in this case
1364 QgsDebugError( "Cannot show results for algorithms with no outputs" );
1365 return;
1366 }
1367
1368 bool foundResults = false;
1369 for ( const QgsProcessingParameterDefinition *outputParam : outputParams )
1370 {
1371 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1372 if ( !output.isValid() )
1373 continue;
1374
1375 if ( output.type() == QVariant::String )
1376 {
1377 if ( QgsMapLayer *resultLayer = QgsProcessingUtils::mapLayerFromString( output.toString(), mLayerStore ) )
1378 {
1379 QgsDebugMsgLevel( u"Loading previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ), 2 );
1380
1381 std::unique_ptr<QgsMapLayer> layer( resultLayer->clone() );
1382
1383 QString baseName;
1384 if ( outputParams.size() > 1 )
1385 baseName = tr( "%1 — %2" ).arg( childDescription, outputParam->name() );
1386 else
1387 baseName = childDescription;
1388
1389 // make name unique, so that's it's easy to see which is the most recent result.
1390 // (this helps when running the model multiple times.)
1391 QString name = baseName;
1392 int counter = 1;
1393 while ( !QgsProject::instance()->mapLayersByName( name ).empty() )
1394 {
1395 counter += 1;
1396 name = tr( "%1 (%2)" ).arg( baseName ).arg( counter );
1397 }
1398
1399 layer->setName( name );
1400
1401 QgsProject::instance()->addMapLayer( layer.release() );
1402 foundResults = true;
1403 }
1404 else
1405 {
1406 // should not happen in normal operation
1407 QgsDebugError( u"Could not load previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ) );
1408 }
1409 }
1410 }
1411
1412 if ( !foundResults )
1413 {
1414 mMessageBar->pushWarning( QString(), tr( "No results are available for %1" ).arg( childDescription ) );
1415 return;
1416 }
1417 else if ( isOutdated )
1418 {
1419 mMessageBar->pushWarning( QString(), tr( "These results are outdated, and may not reflect the most recent model execution" ) );
1420 return;
1421 }
1422}
1423
1424void QgsModelDesignerDialog::showChildAlgorithmLog( const QString &childId )
1425{
1426 const QString childDescription = mModel->childAlgorithm( childId ).description();
1427
1429 // prefer to fetch the log from the item itself -- if we are currently mid-way through
1430 // running the model, it will have the LATEST log available
1431 if ( QgsModelChildAlgorithmGraphicItem *item = mScene->childAlgorithmItem( childId ) )
1432 {
1433 result = item->results();
1434 }
1435 if ( result.htmlLog().isEmpty() )
1436 {
1437 result = mLastResult.childResults().value( childId );
1438 }
1439
1440 if ( result.htmlLog().isEmpty() )
1441 {
1442 mMessageBar->pushWarning( QString(), tr( "No log is available for %1" ).arg( childDescription ) );
1443 return;
1444 }
1445
1446 QgsMessageViewer m( this, QgsGuiUtils::ModalDialogFlags, false );
1447 m.setWindowTitle( childDescription );
1448 m.setCheckBoxVisible( false );
1449 m.setMessageAsHtml( result.htmlLog() );
1450 m.exec();
1451}
1452
1453void QgsModelDesignerDialog::onItemFocused( QgsModelComponentGraphicItem *item )
1454{
1455 QgsProcessingParameterWidgetContext widgetContext = createWidgetContext();
1456 widgetContext.registerProcessingContextGenerator( mProcessingContextGenerator );
1457 widgetContext.setModelDesignerDialog( this );
1458 QgsProcessingContext *context = mProcessingContextGenerator->processingContext();
1459
1460 if ( !item || !item->component() )
1461 {
1462 mConfigWidget->showComponentConfig( nullptr, *context, widgetContext );
1463 }
1464 else
1465 {
1466 mConfigWidget->showComponentConfig( item->component(), *context, widgetContext );
1467
1468 if ( auto childAlgorithmItem = qobject_cast< QgsModelChildAlgorithmGraphicItem * >( item ) )
1469 {
1470 connect( childAlgorithmItem, &QgsModelChildAlgorithmGraphicItem::rebuildConfigurationDockWidget, childAlgorithmItem, [this] {
1471 QgsProcessingParameterWidgetContext widgetContext = createWidgetContext();
1472 widgetContext.registerProcessingContextGenerator( mProcessingContextGenerator );
1473 widgetContext.setModelDesignerDialog( this );
1474 QgsProcessingContext *context = mProcessingContextGenerator->processingContext();
1475 mConfigWidget->showComponentConfig( nullptr, *context, widgetContext );
1476 } );
1477 }
1478 }
1479}
1480
1481void QgsModelDesignerDialog::validate()
1482{
1483 QStringList issues;
1484 if ( model()->validate( issues ) )
1485 {
1486 mMessageBar->pushSuccess( QString(), tr( "Model is valid!" ) );
1487 }
1488 else
1489 {
1490 QgsMessageBarItem *messageWidget = QgsMessageBar::createMessage( QString(), tr( "Model is invalid!" ) );
1491 QPushButton *detailsButton = new QPushButton( tr( "Details" ) );
1492 connect( detailsButton, &QPushButton::clicked, detailsButton, [detailsButton, issues] {
1493 QgsMessageViewer *dialog = new QgsMessageViewer( detailsButton );
1494 dialog->setTitle( tr( "Model is Invalid" ) );
1495
1496 QString longMessage = tr( "<p>This model is not valid:</p>" ) + u"<ul>"_s;
1497 for ( const QString &issue : issues )
1498 {
1499 longMessage += u"<li>%1</li>"_s.arg( issue );
1500 }
1501 longMessage += "</ul>"_L1;
1502
1503 dialog->setMessage( longMessage, Qgis::StringFormat::Html );
1504 dialog->showMessage();
1505 } );
1506 messageWidget->layout()->addWidget( detailsButton );
1507 mMessageBar->clearWidgets();
1508 mMessageBar->pushWidget( messageWidget, Qgis::MessageLevel::Warning, 0 );
1509 }
1510}
1511
1512void QgsModelDesignerDialog::reorderInputs()
1513{
1514 QgsModelInputReorderDialog dlg( this );
1515 dlg.setModel( mModel.get() );
1516 if ( dlg.exec() )
1517 {
1518 const QStringList inputOrder = dlg.inputOrder();
1519 beginUndoCommand( tr( "Reorder Inputs" ) );
1520 mModel->setParameterOrder( inputOrder );
1521 endUndoCommand();
1522 }
1523}
1524
1525void QgsModelDesignerDialog::reorderOutputs()
1526{
1527 QgsModelOutputReorderDialog dlg( this );
1528 dlg.setModel( mModel.get() );
1529 if ( dlg.exec() )
1530 {
1531 const QStringList outputOrder = dlg.outputOrder();
1532 beginUndoCommand( tr( "Reorder Outputs" ) );
1533 mModel->setOutputOrder( outputOrder );
1534 mModel->setOutputGroup( dlg.outputGroup() );
1535 endUndoCommand();
1536 }
1537}
1538
1539bool QgsModelDesignerDialog::isDirty() const
1540{
1541 return mHasChanged && mUndoStack->index() != -1;
1542}
1543
1544void QgsModelDesignerDialog::fillInputsTree()
1545{
1546 const QIcon icon = QgsApplication::getThemeIcon( u"mIconModelInput.svg"_s );
1547 auto parametersItem = std::make_unique<QTreeWidgetItem>();
1548 parametersItem->setText( 0, tr( "Parameters" ) );
1549 QList<QgsProcessingParameterType *> available = QgsApplication::processingRegistry()->parameterTypes();
1550 std::sort( available.begin(), available.end(), []( const QgsProcessingParameterType *a, const QgsProcessingParameterType *b ) -> bool {
1551 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1552 } );
1553
1554 for ( QgsProcessingParameterType *param : std::as_const( available ) )
1555 {
1557 {
1558 auto paramItem = std::make_unique<QTreeWidgetItem>();
1559 paramItem->setText( 0, param->name() );
1560 paramItem->setData( 0, Qt::UserRole, param->id() );
1561 paramItem->setIcon( 0, icon );
1562 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1563 paramItem->setToolTip( 0, param->description() );
1564 parametersItem->addChild( paramItem.release() );
1565 }
1566 }
1567 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1568 mInputsTreeWidget->topLevelItem( 0 )->setExpanded( true );
1569}
1570
1571
1572//
1573// QgsModelChildDependenciesWidget
1574//
1575
1576QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model, const QString &childId )
1577 : QWidget( parent )
1578 , mModel( model )
1579 , mChildId( childId )
1580{
1581 QHBoxLayout *hl = new QHBoxLayout();
1582 hl->setContentsMargins( 0, 0, 0, 0 );
1583
1584 mLineEdit = new QLineEdit();
1585 mLineEdit->setEnabled( false );
1586 hl->addWidget( mLineEdit, 1 );
1587
1588 mToolButton = new QToolButton();
1589 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1590 hl->addWidget( mToolButton );
1591
1592 setLayout( hl );
1593
1594 mLineEdit->setText( tr( "%1 dependencies selected" ).arg( 0 ) );
1595
1596 connect( mToolButton, &QToolButton::clicked, this, &QgsModelChildDependenciesWidget::showDialog );
1597}
1598
1599void QgsModelChildDependenciesWidget::setValue( const QList<QgsProcessingModelChildDependency> &value )
1600{
1601 const bool hasChanged = value != mValue;
1602 mValue = value;
1603
1604 updateSummaryText();
1605 if ( hasChanged )
1606 {
1607 emit changed();
1608 }
1609}
1610
1611void QgsModelChildDependenciesWidget::showDialog()
1612{
1613 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1614
1615 QVariantList availableOptions;
1616 for ( const QgsProcessingModelChildDependency &dep : available )
1617 availableOptions << QVariant::fromValue( dep );
1618 QVariantList selectedOptions;
1619 for ( const QgsProcessingModelChildDependency &dep : mValue )
1620 selectedOptions << QVariant::fromValue( dep );
1621
1623 if ( panel )
1624 {
1625 QgsProcessingMultipleSelectionPanelWidget *widget = new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1626 widget->setPanelTitle( tr( "Algorithm Dependencies" ) );
1627
1628 widget->setValueFormatter( [this]( const QVariant &v ) -> QString {
1629 const QgsProcessingModelChildDependency dep = v.value<QgsProcessingModelChildDependency>();
1630
1631 const QString description = mModel->childAlgorithm( dep.childId ).description();
1632 if ( dep.conditionalBranch.isEmpty() )
1633 return description;
1634 else
1635 return tr( "Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1636 } );
1637
1638 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged, this, [this, widget]() {
1639 QList<QgsProcessingModelChildDependency> res;
1640 for ( const QVariant &v : widget->selectedOptions() )
1641 {
1642 res << v.value<QgsProcessingModelChildDependency>();
1643 }
1644 setValue( res );
1645 } );
1646 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::acceptClicked, widget, &QgsPanelWidget::acceptPanel );
1647 panel->openPanel( widget );
1648 }
1649}
1650
1651void QgsModelChildDependenciesWidget::updateSummaryText()
1652{
1653 mLineEdit->setText( tr( "%n dependencies selected", nullptr, mValue.count() ) );
1654}
1655
@ 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.