QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
16 #include "qgsmodeldesignerdialog.h"
17 #include "qgssettings.h"
18 #include "qgsapplication.h"
19 #include "qgsfileutils.h"
20 #include "qgsmessagebar.h"
22 #include "qgsprocessingregistry.h"
23 #include "qgsprocessingalgorithm.h"
24 #include "qgsgui.h"
26 #include "qgsmodelundocommand.h"
27 #include "qgsmodelviewtoolselect.h"
28 #include "qgsmodelviewtoolpan.h"
29 #include "qgsmodelgraphicsscene.h"
33 #include "qgsmessageviewer.h"
34 #include "qgsmessagebaritem.h"
35 #include "qgspanelwidget.h"
37 
38 #include <QShortcut>
39 #include <QDesktopWidget>
40 #include <QKeySequence>
41 #include <QFileDialog>
42 #include <QPrinter>
43 #include <QSvgGenerator>
44 #include <QToolButton>
45 #include <QCloseEvent>
46 #include <QMessageBox>
47 #include <QUndoView>
48 #include <QPushButton>
49 #include <QUrl>
50 #include <QTextStream>
51 #include <QActionGroup>
52 
54 
55 
56 QgsModelerToolboxModel::QgsModelerToolboxModel( QObject *parent )
58 {
59 
60 }
61 
62 Qt::ItemFlags QgsModelerToolboxModel::flags( const QModelIndex &index ) const
63 {
64  Qt::ItemFlags f = QgsProcessingToolboxProxyModel::flags( index );
65  const QModelIndex sourceIndex = mapToSource( index );
66  if ( toolboxModel()->isAlgorithm( sourceIndex ) )
67  {
68  f = f | Qt::ItemIsDragEnabled;
69  }
70  return f;
71 }
72 
73 Qt::DropActions QgsModelerToolboxModel::supportedDragActions() const
74 {
75  return Qt::CopyAction;
76 }
77 
78 
79 
80 QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
81  : QMainWindow( parent, flags )
82  , mToolsActionGroup( new QActionGroup( this ) )
83 {
84  setupUi( this );
85 
86  setAttribute( Qt::WA_DeleteOnClose );
87  setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
88  setWindowFlags( Qt::WindowMinimizeButtonHint |
89  Qt::WindowMaximizeButtonHint |
90  Qt::WindowCloseButtonHint );
91 
93 
94  mModel = std::make_unique< QgsProcessingModelAlgorithm >();
95  mModel->setProvider( QgsApplication::processingRegistry()->providerById( QStringLiteral( "model" ) ) );
96 
97  mUndoStack = new QUndoStack( this );
98  connect( mUndoStack, &QUndoStack::indexChanged, this, [ = ]
99  {
100  if ( mIgnoreUndoStackChanges )
101  return;
102 
103  mBlockUndoCommands++;
104  updateVariablesGui();
105  mGroupEdit->setText( mModel->group() );
106  mNameEdit->setText( mModel->displayName() );
107  mBlockUndoCommands--;
108  repaintModel();
109  } );
110 
111  mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
112  mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
113  mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
114  mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
115 
116  mAlgorithmsTree->header()->setVisible( false );
117  mAlgorithmSearchEdit->setShowSearchIcon( true );
118  mAlgorithmSearchEdit->setPlaceholderText( tr( "Search…" ) );
119  connect( mAlgorithmSearchEdit, &QgsFilterLineEdit::textChanged, mAlgorithmsTree, &QgsProcessingToolboxTreeView::setFilterString );
120 
121  mInputsTreeWidget->header()->setVisible( false );
122  mInputsTreeWidget->setAlternatingRowColors( true );
123  mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
124  mInputsTreeWidget->setDropIndicatorShown( true );
125 
126  mNameEdit->setPlaceholderText( tr( "Enter model name here" ) );
127  mGroupEdit->setPlaceholderText( tr( "Enter group name here" ) );
128 
129  mMessageBar = new QgsMessageBar();
130  mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
131  mainLayout->insertWidget( 0, mMessageBar );
132 
133  mView->setAcceptDrops( true );
134  QgsSettings settings;
135 
136  connect( mActionClose, &QAction::triggered, this, &QWidget::close );
137  connect( mActionNew, &QAction::triggered, this, &QgsModelDesignerDialog::newModel );
138  connect( mActionZoomIn, &QAction::triggered, this, &QgsModelDesignerDialog::zoomIn );
139  connect( mActionZoomOut, &QAction::triggered, this, &QgsModelDesignerDialog::zoomOut );
140  connect( mActionZoomActual, &QAction::triggered, this, &QgsModelDesignerDialog::zoomActual );
141  connect( mActionZoomToItems, &QAction::triggered, this, &QgsModelDesignerDialog::zoomFull );
142  connect( mActionExportImage, &QAction::triggered, this, &QgsModelDesignerDialog::exportToImage );
143  connect( mActionExportPdf, &QAction::triggered, this, &QgsModelDesignerDialog::exportToPdf );
144  connect( mActionExportSvg, &QAction::triggered, this, &QgsModelDesignerDialog::exportToSvg );
145  connect( mActionExportPython, &QAction::triggered, this, &QgsModelDesignerDialog::exportAsPython );
146  connect( mActionSave, &QAction::triggered, this, [ = ] { saveModel( false ); } );
147  connect( mActionSaveAs, &QAction::triggered, this, [ = ] { saveModel( true ); } );
148  connect( mActionDeleteComponents, &QAction::triggered, this, &QgsModelDesignerDialog::deleteSelected );
149  connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
150  connect( mActionValidate, &QAction::triggered, this, &QgsModelDesignerDialog::validate );
151  connect( mActionReorderInputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderInputs );
152  connect( mReorderInputsButton, &QPushButton::clicked, this, &QgsModelDesignerDialog::reorderInputs );
153 
154  mActionSnappingEnabled->setChecked( settings.value( QStringLiteral( "/Processing/Modeler/enableSnapToGrid" ), false ).toBool() );
155  connect( mActionSnappingEnabled, &QAction::toggled, this, [ = ]( bool enabled )
156  {
157  mView->snapper()->setSnapToGrid( enabled );
158  QgsSettings().setValue( QStringLiteral( "/Processing/Modeler/enableSnapToGrid" ), enabled );
159  } );
160  mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
161 
162  connect( mActionSelectAll, &QAction::triggered, this, [ = ]
163  {
164  mScene->selectAll();
165  } );
166 
167  QStringList docksTitle = settings.value( QStringLiteral( "ModelDesigner/hiddenDocksTitle" ), QStringList(), QgsSettings::App ).toStringList();
168  QStringList docksActive = settings.value( QStringLiteral( "ModelDesigner/hiddenDocksActive" ), QStringList(), QgsSettings::App ).toStringList();
169  if ( !docksTitle.isEmpty() )
170  {
171  for ( const auto &title : docksTitle )
172  {
173  mPanelStatus.insert( title, PanelStatus( true, docksActive.contains( title ) ) );
174  }
175  }
176  mActionHidePanels->setChecked( !docksTitle.isEmpty() );
177  connect( mActionHidePanels, &QAction::toggled, this, &QgsModelDesignerDialog::setPanelVisibility );
178 
179  mUndoAction = mUndoStack->createUndoAction( this );
180  mUndoAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUndo.svg" ) ) );
181  mUndoAction->setShortcuts( QKeySequence::Undo );
182  mRedoAction = mUndoStack->createRedoAction( this );
183  mRedoAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRedo.svg" ) ) );
184  mRedoAction->setShortcuts( QKeySequence::Redo );
185 
186  mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
187  mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
188  mMenuEdit->insertSeparator( mActionDeleteComponents );
189  mToolbar->insertAction( mActionZoomIn, mUndoAction );
190  mToolbar->insertAction( mActionZoomIn, mRedoAction );
191  mToolbar->insertSeparator( mActionZoomIn );
192 
193  mGroupMenu = new QMenu( tr( "Zoom To" ), this );
194  mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
195  connect( mGroupMenu, &QMenu::aboutToShow, this, &QgsModelDesignerDialog::populateZoomToMenu );
196 
197  //cut/copy/paste actions. Note these are not included in the ui file
198  //as ui files have no support for QKeySequence shortcuts
199  mActionCut = new QAction( tr( "Cu&t" ), this );
200  mActionCut->setShortcuts( QKeySequence::Cut );
201  mActionCut->setStatusTip( tr( "Cut" ) );
202  mActionCut->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCut.svg" ) ) );
203  connect( mActionCut, &QAction::triggered, this, [ = ]
204  {
205  mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut );
206  } );
207 
208  mActionCopy = new QAction( tr( "&Copy" ), this );
209  mActionCopy->setShortcuts( QKeySequence::Copy );
210  mActionCopy->setStatusTip( tr( "Copy" ) );
211  mActionCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
212  connect( mActionCopy, &QAction::triggered, this, [ = ]
213  {
214  mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy );
215  } );
216 
217  mActionPaste = new QAction( tr( "&Paste" ), this );
218  mActionPaste->setShortcuts( QKeySequence::Paste );
219  mActionPaste->setStatusTip( tr( "Paste" ) );
220  mActionPaste->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditPaste.svg" ) ) );
221  connect( mActionPaste, &QAction::triggered, this, [ = ]
222  {
223  mView->pasteItems( QgsModelGraphicsView::PasteModeCursor );
224  } );
225  mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
226  mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
227  mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
228  mMenuEdit->insertSeparator( mActionDeleteComponents );
229 
230  QgsProcessingToolboxProxyModel::Filters filters = QgsProcessingToolboxProxyModel::FilterModeler;
231  if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES" ), false ).toBool() )
232  {
234  }
235  mAlgorithmsTree->setFilters( filters );
236  mAlgorithmsTree->setDragDropMode( QTreeWidget::DragOnly );
237  mAlgorithmsTree->setDropIndicatorShown( true );
238 
239  mAlgorithmsModel = new QgsModelerToolboxModel( this );
240  mAlgorithmsTree->setToolboxProxyModel( mAlgorithmsModel );
241 
242  connect( mView, &QgsModelGraphicsView::algorithmDropped, this, [ = ]( const QString & algorithmId, const QPointF & pos )
243  {
244  addAlgorithm( algorithmId, pos );
245  } );
246  connect( mAlgorithmsTree, &QgsProcessingToolboxTreeView::doubleClicked, this, [ = ]()
247  {
248  if ( mAlgorithmsTree->selectedAlgorithm() )
249  addAlgorithm( mAlgorithmsTree->selectedAlgorithm()->id(), QPointF() );
250  } );
251  connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked, this, [ = ]( const QModelIndex & )
252  {
253  const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
254  addInput( parameterType, QPointF() );
255  } );
256 
257  connect( mView, &QgsModelGraphicsView::inputDropped, this, &QgsModelDesignerDialog::addInput );
258 
259  // Ctrl+= should also trigger a zoom in action
260  QShortcut *ctrlEquals = new QShortcut( QKeySequence( QStringLiteral( "Ctrl+=" ) ), this );
261  connect( ctrlEquals, &QShortcut::activated, this, &QgsModelDesignerDialog::zoomIn );
262 
263  mUndoDock = new QgsDockWidget( tr( "Undo History" ), this );
264  mUndoDock->setObjectName( QStringLiteral( "UndoDock" ) );
265  mUndoView = new QUndoView( mUndoStack, this );
266  mUndoDock->setWidget( mUndoView );
267  mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
268  addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
269 
270  tabifyDockWidget( mUndoDock, mPropertiesDock );
271  tabifyDockWidget( mVariablesDock, mPropertiesDock );
272  mPropertiesDock->raise();
273  tabifyDockWidget( mInputsDock, mAlgorithmsDock );
274  mInputsDock->raise();
275 
276  connect( mVariablesEditor, &QgsVariableEditorWidget::scopeChanged, this, [ = ]
277  {
278  if ( mModel )
279  {
280  beginUndoCommand( tr( "Change Model Variables" ) );
281  mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
282  endUndoCommand();
283  }
284  } );
285  connect( mNameEdit, &QLineEdit::textChanged, this, [ = ]( const QString & name )
286  {
287  if ( mModel )
288  {
289  beginUndoCommand( tr( "Change Model Name" ), NameChanged );
290  mModel->setName( name );
291  endUndoCommand();
292  updateWindowTitle();
293  }
294  } );
295  connect( mGroupEdit, &QLineEdit::textChanged, this, [ = ]( const QString & group )
296  {
297  if ( mModel )
298  {
299  beginUndoCommand( tr( "Change Model Group" ), GroupChanged );
300  mModel->setGroup( group );
301  endUndoCommand();
302  }
303  } );
304 
305  fillInputsTree();
306 
307  QToolButton *toolbuttonExportToScript = new QToolButton();
308  toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
309  toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
310  toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
311  mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
312  connect( mActionExportAsScriptAlgorithm, &QAction::triggered, this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
313 
314  mActionShowComments->setChecked( settings.value( QStringLiteral( "/Processing/Modeler/ShowComments" ), true ).toBool() );
315  connect( mActionShowComments, &QAction::toggled, this, &QgsModelDesignerDialog::toggleComments );
316 
317  mPanTool = new QgsModelViewToolPan( mView );
318  mPanTool->setAction( mActionPan );
319 
320  mToolsActionGroup->addAction( mActionPan );
321  connect( mActionPan, &QAction::triggered, mPanTool, [ = ] { mView->setTool( mPanTool ); } );
322 
323  mSelectTool = new QgsModelViewToolSelect( mView );
324  mSelectTool->setAction( mActionSelectMoveItem );
325 
326  mToolsActionGroup->addAction( mActionSelectMoveItem );
327  connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
328 
329  mView->setTool( mSelectTool );
330  mView->setFocus();
331 
332  connect( mView, &QgsModelGraphicsView::macroCommandStarted, this, [ = ]( const QString & text )
333  {
334  mIgnoreUndoStackChanges++;
335  mUndoStack->beginMacro( text );
336  mIgnoreUndoStackChanges--;
337  } );
338  connect( mView, &QgsModelGraphicsView::macroCommandEnded, this, [ = ]
339  {
340  mIgnoreUndoStackChanges++;
341  mUndoStack->endMacro();
342  mIgnoreUndoStackChanges--;
343  } );
344  connect( mView, &QgsModelGraphicsView::beginCommand, this, [ = ]( const QString & text )
345  {
346  beginUndoCommand( text );
347  } );
348  connect( mView, &QgsModelGraphicsView::endCommand, this, [ = ]
349  {
350  endUndoCommand();
351  } );
352  connect( mView, &QgsModelGraphicsView::deleteSelectedItems, this, [ = ]
353  {
354  deleteSelected();
355  } );
356 
357  connect( mActionAddGroupBox, &QAction::triggered, this, [ = ]
358  {
359  const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
360  QgsProcessingModelGroupBox group;
361  group.setPosition( viewCenter );
362  group.setDescription( tr( "New Group" ) );
363 
364  beginUndoCommand( tr( "Add Group Box" ) );
365  model()->addGroupBox( group );
366  repaintModel();
367  endUndoCommand();
368  } );
369 
370  updateWindowTitle();
371 
372  // restore the toolbar and dock widgets positions using Qt settings API
373  restoreState( settings.value( QStringLiteral( "ModelDesigner/state" ), QByteArray(), QgsSettings::App ).toByteArray() );
374 }
375 
376 QgsModelDesignerDialog::~QgsModelDesignerDialog()
377 {
378  QgsSettings settings;
379  if ( !mPanelStatus.isEmpty() )
380  {
381  QStringList docksTitle;
382  QStringList docksActive;
383 
384  for ( const auto &panel : mPanelStatus.toStdMap() )
385  {
386  if ( panel.second.isVisible )
387  docksTitle << panel.first;
388  if ( panel.second.isActive )
389  docksActive << panel.first;
390  }
391  settings.setValue( QStringLiteral( "ModelDesigner/hiddenDocksTitle" ), docksTitle, QgsSettings::App );
392  settings.setValue( QStringLiteral( "ModelDesigner/hiddenDocksActive" ), docksActive, QgsSettings::App );
393  }
394  else
395  {
396  settings.remove( QStringLiteral( "ModelDesigner/hiddenDocksTitle" ), QgsSettings::App );
397  settings.remove( QStringLiteral( "ModelDesigner/hiddenDocksActive" ), QgsSettings::App );
398  }
399 
400  // store the toolbar/dock widget settings using Qt settings API
401  settings.setValue( QStringLiteral( "ModelDesigner/state" ), saveState(), QgsSettings::App );
402 
403  mIgnoreUndoStackChanges++;
404  delete mSelectTool; // delete mouse handles before everything else
405 }
406 
407 void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
408 {
409  if ( checkForUnsavedChanges() )
410  event->accept();
411  else
412  event->ignore();
413 }
414 
415 void QgsModelDesignerDialog::beginUndoCommand( const QString &text, int id )
416 {
417  if ( mBlockUndoCommands || !mUndoStack )
418  return;
419 
420  if ( mActiveCommand )
421  endUndoCommand();
422 
423  mActiveCommand = std::make_unique< QgsModelUndoCommand >( mModel.get(), text, id );
424 }
425 
426 void QgsModelDesignerDialog::endUndoCommand()
427 {
428  if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
429  return;
430 
431  mActiveCommand->saveAfterState();
432  mIgnoreUndoStackChanges++;
433  mUndoStack->push( mActiveCommand.release() );
434  mIgnoreUndoStackChanges--;
435  setDirty( true );
436 }
437 
438 QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
439 {
440  return mModel.get();
441 }
442 
443 void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
444 {
445  mModel.reset( model );
446 
447  mGroupEdit->setText( mModel->group() );
448  mNameEdit->setText( mModel->displayName() );
449  repaintModel();
450  updateVariablesGui();
451 
452  mView->centerOn( 0, 0 );
453  setDirty( false );
454 
455  mIgnoreUndoStackChanges++;
456  mUndoStack->clear();
457  mIgnoreUndoStackChanges--;
458 
459  updateWindowTitle();
460 }
461 
462 void QgsModelDesignerDialog::loadModel( const QString &path )
463 {
464  std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
465  if ( alg->fromFile( path ) )
466  {
467  alg->setProvider( QgsApplication::processingRegistry()->providerById( QStringLiteral( "model" ) ) );
468  alg->setSourceFilePath( path );
469  setModel( alg.release() );
470  }
471  else
472  {
473  QgsMessageLog::logMessage( tr( "Could not load model %1" ).arg( path ), tr( "Processing" ), Qgis::MessageLevel::Critical );
474  QMessageBox::critical( this, tr( "Open Model" ), tr( "The selected model could not be loaded.\n"
475  "See the log for more information." ) );
476  }
477 }
478 
479 void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
480 {
481  QgsModelGraphicsScene *oldScene = mScene;
482 
483  mScene = scene;
484  mScene->setParent( this );
485  mScene->setChildAlgorithmResults( mChildResults );
486  mScene->setModel( mModel.get() );
487  mScene->setMessageBar( mMessageBar );
488 
489  const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
490  mView->setModelScene( mScene );
491 
492  mSelectTool->resetCache();
493  mSelectTool->setScene( mScene );
494 
495  connect( mScene, &QgsModelGraphicsScene::rebuildRequired, this, [ = ]
496  {
497  if ( mBlockRepaints )
498  return;
499 
500  repaintModel();
501  } );
502  connect( mScene, &QgsModelGraphicsScene::componentAboutToChange, this, [ = ]( const QString & description, int id ) { beginUndoCommand( description, id ); } );
503  connect( mScene, &QgsModelGraphicsScene::componentChanged, this, [ = ] { endUndoCommand(); } );
504 
505  mView->centerOn( center );
506 
507  if ( oldScene )
508  oldScene->deleteLater();
509 }
510 
511 void QgsModelDesignerDialog::activate()
512 {
513  show();
514  raise();
515  setWindowState( windowState() & ~Qt::WindowMinimized );
516  activateWindow();
517 }
518 
519 void QgsModelDesignerDialog::updateVariablesGui()
520 {
521  mBlockUndoCommands++;
522 
523  std::unique_ptr< QgsExpressionContextScope > variablesScope = std::make_unique< QgsExpressionContextScope >( tr( "Model Variables" ) );
524  const QVariantMap modelVars = mModel->variables();
525  for ( auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
526  {
527  variablesScope->setVariable( it.key(), it.value() );
528  }
529  QgsExpressionContext variablesContext;
530  variablesContext.appendScope( variablesScope.release() );
531  mVariablesEditor->setContext( &variablesContext );
532  mVariablesEditor->setEditableScopeIndex( 0 );
533 
534  mBlockUndoCommands--;
535 }
536 
537 void QgsModelDesignerDialog::setDirty( bool dirty )
538 {
539  mHasChanged = dirty;
540  updateWindowTitle();
541 }
542 
543 bool QgsModelDesignerDialog::validateSave( SaveAction action )
544 {
545  switch ( action )
546  {
547  case QgsModelDesignerDialog::SaveAction::SaveAsFile:
548  break;
549  case QgsModelDesignerDialog::SaveAction::SaveInProject:
550  if ( mNameEdit->text().trimmed().isEmpty() )
551  {
552  mMessageBar->pushWarning( QString(), tr( "Please enter a model name before saving" ) );
553  return false;
554  }
555  break;
556  }
557 
558  return true;
559 }
560 
561 bool QgsModelDesignerDialog::checkForUnsavedChanges()
562 {
563  if ( isDirty() )
564  {
565  QMessageBox::StandardButton ret = QMessageBox::question( this, tr( "Save Model?" ),
566  tr( "There are unsaved changes in this model. Do you want to keep those?" ),
567  QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, QMessageBox::Cancel );
568  switch ( ret )
569  {
570  case QMessageBox::Save:
571  return saveModel( false );
572 
573  case QMessageBox::Discard:
574  return true;
575 
576  default:
577  return false;
578  }
579  }
580  else
581  {
582  return true;
583  }
584 }
585 
586 void QgsModelDesignerDialog::setLastRunChildAlgorithmResults( const QVariantMap &results )
587 {
588  mChildResults = results;
589  if ( mScene )
590  mScene->setChildAlgorithmResults( mChildResults );
591 }
592 
593 void QgsModelDesignerDialog::setLastRunChildAlgorithmInputs( const QVariantMap &inputs )
594 {
595  mChildInputs = inputs;
596  if ( mScene )
597  mScene->setChildAlgorithmInputs( mChildInputs );
598 }
599 
600 void QgsModelDesignerDialog::setModelName( const QString &name )
601 {
602  mNameEdit->setText( name );
603 }
604 
605 void QgsModelDesignerDialog::zoomIn()
606 {
607  mView->setTransformationAnchor( QGraphicsView::NoAnchor );
608  QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
609  QgsSettings settings;
610  const double factor = settings.value( QStringLiteral( "/qgis/zoom_favor" ), 2.0 ).toDouble();
611  mView->scale( factor, factor );
612  mView->centerOn( point );
613 }
614 
615 void QgsModelDesignerDialog::zoomOut()
616 {
617  mView->setTransformationAnchor( QGraphicsView::NoAnchor );
618  QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
619  QgsSettings settings;
620  const double factor = 1.0 / settings.value( QStringLiteral( "/qgis/zoom_favor" ), 2.0 ).toDouble();
621  mView->scale( factor, factor );
622  mView->centerOn( point );
623 }
624 
625 void QgsModelDesignerDialog::zoomActual()
626 {
627  QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
628  mView->resetTransform();
629  mView->scale( QgsApplication::desktop()->logicalDpiX() / 96, QgsApplication::desktop()->logicalDpiX() / 96 );
630  mView->centerOn( point );
631 }
632 
633 void QgsModelDesignerDialog::zoomFull()
634 {
635  QRectF totalRect = mView->scene()->itemsBoundingRect();
636  totalRect.adjust( -10, -10, 10, 10 );
637  mView->fitInView( totalRect, Qt::KeepAspectRatio );
638 }
639 
640 void QgsModelDesignerDialog::newModel()
641 {
642  if ( !checkForUnsavedChanges() )
643  return;
644 
645  std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
646  alg->setProvider( QgsApplication::processingRegistry()->providerById( QStringLiteral( "model" ) ) );
647  setModel( alg.release() );
648 }
649 
650 void QgsModelDesignerDialog::exportToImage()
651 {
652  QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Image" ), tr( "PNG files (*.png *.PNG)" ) );
653  if ( filename.isEmpty() )
654  return;
655 
656  filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "png" ) );
657 
658  repaintModel( false );
659 
660  QRectF totalRect = mView->scene()->itemsBoundingRect();
661  totalRect.adjust( -10, -10, 10, 10 );
662  const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
663 
664  QImage img( totalRect.width(), totalRect.height(),
665  QImage::Format_ARGB32_Premultiplied );
666  img.fill( Qt::white );
667  QPainter painter;
668  painter.setRenderHint( QPainter::Antialiasing );
669  painter.begin( &img );
670  mView->scene()->render( &painter, imageRect, totalRect );
671  painter.end();
672 
673  img.save( filename );
674 
675  mMessageBar->pushMessage( QString(), tr( "Successfully exported model as image to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
676  repaintModel( true );
677 }
678 
679 void QgsModelDesignerDialog::exportToPdf()
680 {
681  QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as PDF" ), tr( "PDF files (*.pdf *.PDF)" ) );
682  if ( filename.isEmpty() )
683  return;
684 
685  filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "pdf" ) );
686 
687  repaintModel( false );
688 
689  QRectF totalRect = mView->scene()->itemsBoundingRect();
690  totalRect.adjust( -10, -10, 10, 10 );
691  const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
692 
693  QPrinter printer;
694  printer.setOutputFormat( QPrinter::PdfFormat );
695  printer.setOutputFileName( filename );
696  printer.setPaperSize( QSizeF( printerRect.width(), printerRect.height() ), QPrinter::DevicePixel );
697  printer.setFullPage( true );
698 
699  QPainter painter( &printer );
700  mView->scene()->render( &painter, printerRect, totalRect );
701  painter.end();
702 
703  mMessageBar->pushMessage( QString(), tr( "Successfully exported model as PDF to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(),
704  QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
705  repaintModel( true );
706 }
707 
708 void QgsModelDesignerDialog::exportToSvg()
709 {
710  QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as SVG" ), tr( "SVG files (*.svg *.SVG)" ) );
711  if ( filename.isEmpty() )
712  return;
713 
714  filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "svg" ) );
715 
716  repaintModel( false );
717 
718  QRectF totalRect = mView->scene()->itemsBoundingRect();
719  totalRect.adjust( -10, -10, 10, 10 );
720  const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
721 
722  QSvgGenerator svg;
723  svg.setFileName( filename );
724  svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
725  svg.setViewBox( svgRect );
726  svg.setTitle( mModel->displayName() );
727 
728  QPainter painter( &svg );
729  mView->scene()->render( &painter, svgRect, totalRect );
730  painter.end();
731 
732  mMessageBar->pushMessage( QString(), tr( "Successfully exported model as SVG to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
733  repaintModel( true );
734 }
735 
736 void QgsModelDesignerDialog::exportAsPython()
737 {
738  QString filename = QFileDialog::getSaveFileName( this, tr( "Save Model as Python Script" ), tr( "Processing scripts (*.py *.PY)" ) );
739  if ( filename.isEmpty() )
740  return;
741 
742  filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << QStringLiteral( "py" ) );
743 
744  const QString text = mModel->asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, 4 ).join( '\n' );
745 
746  QFile outFile( filename );
747  if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
748  {
749  return;
750  }
751  QTextStream fout( &outFile );
752  fout << text;
753  outFile.close();
754 
755  mMessageBar->pushMessage( QString(), tr( "Successfully exported model as Python script to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
756 }
757 
758 void QgsModelDesignerDialog::toggleComments( bool show )
759 {
760  QgsSettings().setValue( QStringLiteral( "/Processing/Modeler/ShowComments" ), show );
761 
762  repaintModel( true );
763 }
764 
765 void QgsModelDesignerDialog::updateWindowTitle()
766 {
767  QString title = tr( "Model Designer" );
768  if ( !mModel->name().isEmpty() )
769  title = QStringLiteral( "%1 - %2" ).arg( title, mModel->name() );
770 
771  if ( isDirty() )
772  title.prepend( '*' );
773 
774  setWindowTitle( title );
775 }
776 
777 void QgsModelDesignerDialog::deleteSelected()
778 {
779  QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
780  if ( items.empty() )
781  return;
782 
783  if ( items.size() == 1 )
784  {
785  items.at( 0 )->deleteComponent();
786  return;
787  }
788 
789  std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem * p1, QgsModelComponentGraphicItem * p2 )
790  {
791  // try to delete the easy stuff first, so comments, then outputs, as nothing will depend on these...
792  if ( dynamic_cast< QgsModelCommentGraphicItem *>( p1 ) )
793  return true;
794  else if ( dynamic_cast< QgsModelCommentGraphicItem *>( p2 ) )
795  return false;
796  else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p1 ) )
797  return true;
798  else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p2 ) )
799  return false;
800  else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p1 ) )
801  return true;
802  else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p2 ) )
803  return false;
804  else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p1 ) )
805  return true;
806  else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p2 ) )
807  return false;
808  return false;
809  } );
810 
811 
812  beginUndoCommand( tr( "Delete Components" ) );
813 
814  QVariant prevState = mModel->toVariant();
815  mBlockUndoCommands++;
816  mBlockRepaints = true;
817  bool failed = false;
818  while ( !items.empty() )
819  {
820  QgsModelComponentGraphicItem *toDelete = nullptr;
821  for ( QgsModelComponentGraphicItem *item : items )
822  {
823  if ( item->canDeleteComponent() )
824  {
825  toDelete = item;
826  break;
827  }
828  }
829 
830  if ( !toDelete )
831  {
832  failed = true;
833  break;
834  }
835 
836  toDelete->deleteComponent();
837  items.removeAll( toDelete );
838  }
839 
840  if ( failed )
841  {
842  mModel->loadVariant( prevState );
843  QMessageBox::warning( nullptr, QObject::tr( "Could not remove components" ),
844  QObject::tr( "Components depend on the selected items.\n"
845  "Try to remove them before trying deleting these components." ) );
846  mBlockUndoCommands--;
847  mActiveCommand.reset();
848  }
849  else
850  {
851  mBlockUndoCommands--;
852  endUndoCommand();
853  }
854 
855  mBlockRepaints = false;
856  repaintModel();
857 }
858 
859 void QgsModelDesignerDialog::populateZoomToMenu()
860 {
861  mGroupMenu->clear();
862  for ( const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
863  {
864  if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
865  {
866  QAction *zoomAction = new QAction( box.description(), mGroupMenu );
867  connect( zoomAction, &QAction::triggered, this, [ = ]
868  {
869  QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
870  groupRect.adjust( -10, -10, 10, 10 );
871  mView->fitInView( groupRect, Qt::KeepAspectRatio );
872  mView->centerOn( item );
873  } );
874  mGroupMenu->addAction( zoomAction );
875  }
876  }
877 }
878 
879 void QgsModelDesignerDialog::setPanelVisibility( bool hidden )
880 {
881  const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
882  const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
883 
884  if ( hidden )
885  {
886  mPanelStatus.clear();
887  //record status of all docks
888  for ( QDockWidget *dock : docks )
889  {
890  mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(), false ) );
891  dock->setVisible( false );
892  }
893 
894  //record active dock tabs
895  for ( QTabBar *tabBar : tabBars )
896  {
897  QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
898  mPanelStatus[ currentTabTitle ].isActive = true;
899  }
900  }
901  else
902  {
903  //restore visibility of all docks
904  for ( QDockWidget *dock : docks )
905  {
906  if ( mPanelStatus.contains( dock->windowTitle() ) )
907  {
908  dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
909  }
910  }
911 
912  //restore previously active dock tabs
913  for ( QTabBar *tabBar : tabBars )
914  {
915  //loop through all tabs in tab bar
916  for ( int i = 0; i < tabBar->count(); ++i )
917  {
918  QString tabTitle = tabBar->tabText( i );
919  if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
920  {
921  tabBar->setCurrentIndex( i );
922  }
923  }
924  }
925  mPanelStatus.clear();
926  }
927 }
928 
929 void QgsModelDesignerDialog::validate()
930 {
931  QStringList issues;
932  if ( model()->validate( issues ) )
933  {
934  mMessageBar->pushSuccess( QString(), tr( "Model is valid!" ) );
935  }
936  else
937  {
938  QgsMessageBarItem *messageWidget = QgsMessageBar::createMessage( QString(), tr( "Model is invalid!" ) );
939  QPushButton *detailsButton = new QPushButton( tr( "Details" ) );
940  connect( detailsButton, &QPushButton::clicked, detailsButton, [ = ]
941  {
942  QgsMessageViewer *dialog = new QgsMessageViewer( detailsButton );
943  dialog->setTitle( tr( "Model is Invalid" ) );
944 
945  QString longMessage = tr( "<p>This model is not valid:</p>" ) + QStringLiteral( "<ul>" );
946  for ( const QString &issue : issues )
947  {
948  longMessage += QStringLiteral( "<li>%1</li>" ).arg( issue );
949  }
950  longMessage += QLatin1String( "</ul>" );
951 
952  dialog->setMessage( longMessage, QgsMessageOutput::MessageHtml );
953  dialog->showMessage();
954  } );
955  messageWidget->layout()->addWidget( detailsButton );
956  mMessageBar->clearWidgets();
957  mMessageBar->pushWidget( messageWidget, Qgis::MessageLevel::Warning, 0 );
958  }
959 }
960 
961 void QgsModelDesignerDialog::reorderInputs()
962 {
963  QgsModelInputReorderDialog dlg( this );
964  dlg.setModel( mModel.get() );
965  if ( dlg.exec() )
966  {
967  const QStringList inputOrder = dlg.inputOrder();
968  beginUndoCommand( tr( "Reorder Inputs" ) );
969  mModel->setParameterOrder( inputOrder );
970  endUndoCommand();
971  }
972 }
973 
974 bool QgsModelDesignerDialog::isDirty() const
975 {
976  return mHasChanged && mUndoStack->index() != -1;
977 }
978 
979 void QgsModelDesignerDialog::fillInputsTree()
980 {
981  const QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "mIconModelInput.svg" ) );
982  std::unique_ptr< QTreeWidgetItem > parametersItem = std::make_unique< QTreeWidgetItem >();
983  parametersItem->setText( 0, tr( "Parameters" ) );
984  QList<QgsProcessingParameterType *> available = QgsApplication::processingRegistry()->parameterTypes();
985  std::sort( available.begin(), available.end(), []( const QgsProcessingParameterType * a, const QgsProcessingParameterType * b ) -> bool
986  {
987  return QString::localeAwareCompare( a->name(), b->name() ) < 0;
988  } );
989 
990  for ( QgsProcessingParameterType *param : std::as_const( available ) )
991  {
992  if ( param->flags() & QgsProcessingParameterType::ExposeToModeler )
993  {
994  std::unique_ptr< QTreeWidgetItem > paramItem = std::make_unique< QTreeWidgetItem >();
995  paramItem->setText( 0, param->name() );
996  paramItem->setData( 0, Qt::UserRole, param->id() );
997  paramItem->setIcon( 0, icon );
998  paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
999  paramItem->setToolTip( 0, param->description() );
1000  parametersItem->addChild( paramItem.release() );
1001  }
1002  }
1003  mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1004  mInputsTreeWidget->topLevelItem( 0 )->setExpanded( true );
1005 }
1006 
1007 
1008 //
1009 // QgsModelChildDependenciesWidget
1010 //
1011 
1012 QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model, const QString &childId )
1013  : QWidget( parent )
1014  , mModel( model )
1015  , mChildId( childId )
1016 {
1017  QHBoxLayout *hl = new QHBoxLayout();
1018  hl->setContentsMargins( 0, 0, 0, 0 );
1019 
1020  mLineEdit = new QLineEdit();
1021  mLineEdit->setEnabled( false );
1022  hl->addWidget( mLineEdit, 1 );
1023 
1024  mToolButton = new QToolButton();
1025  mToolButton->setText( QString( QChar( 0x2026 ) ) );
1026  hl->addWidget( mToolButton );
1027 
1028  setLayout( hl );
1029 
1030  mLineEdit->setText( tr( "%1 dependencies selected" ).arg( 0 ) );
1031 
1032  connect( mToolButton, &QToolButton::clicked, this, &QgsModelChildDependenciesWidget::showDialog );
1033 }
1034 
1035 void QgsModelChildDependenciesWidget::setValue( const QList<QgsProcessingModelChildDependency> &value )
1036 {
1037  mValue = value;
1038 
1039  updateSummaryText();
1040 }
1041 
1042 void QgsModelChildDependenciesWidget::showDialog()
1043 {
1044  const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1045 
1046  QVariantList availableOptions;
1047  for ( const QgsProcessingModelChildDependency &dep : available )
1048  availableOptions << QVariant::fromValue( dep );
1049  QVariantList selectedOptions;
1050  for ( const QgsProcessingModelChildDependency &dep : mValue )
1051  selectedOptions << QVariant::fromValue( dep );
1052 
1054  if ( panel )
1055  {
1056  QgsProcessingMultipleSelectionPanelWidget *widget = new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1057  widget->setPanelTitle( tr( "Algorithm Dependencies" ) );
1058 
1059  widget->setValueFormatter( [ = ]( const QVariant & v ) -> QString
1060  {
1061  const QgsProcessingModelChildDependency dep = v.value< QgsProcessingModelChildDependency >();
1062 
1063  const QString description = mModel->childAlgorithm( dep.childId ).description();
1064  if ( dep.conditionalBranch.isEmpty() )
1065  return description;
1066  else
1067  return tr( "Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1068  } );
1069 
1070  connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged, this, [ = ]()
1071  {
1072  QList< QgsProcessingModelChildDependency > res;
1073  for ( const QVariant &v : widget->selectedOptions() )
1074  {
1075  res << v.value< QgsProcessingModelChildDependency >();
1076  }
1077  setValue( res );
1078  } );
1079  connect( widget, &QgsProcessingMultipleSelectionPanelWidget::acceptClicked, widget, &QgsPanelWidget::acceptPanel );
1080  panel->openPanel( widget );
1081  }
1082 }
1083 
1084 void QgsModelChildDependenciesWidget::updateSummaryText()
1085 {
1086  mLineEdit->setText( tr( "%n dependencies selected", nullptr, mValue.count() ) );
1087 }
1088 
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.
QgsDockWidget subclass with more fine-grained control over how the widget is closed or opened.
Definition: qgsdockwidget.h:32
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:174
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
Creates message bar item widget containing a message text to be displayed on the bar.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
A generic message view for displaying QGIS messages.
void setTitle(const QString &title) override
Sets title for the messages.
void setMessage(const QString &message, MessageType msgType) override
Sets message, it won't be displayed until.
void showMessage(bool blocking=true) override
display the message to the user and deletes itself
Model designer view tool for panning a model.
Model designer view tool for selecting items in the model.
Base class for any widget that can be shown as a 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.
Makes metadata of processing parameters available.
@ ExposeToModeler
Is this parameter available in the modeler. Is set to on by default.
QList< QgsProcessingParameterType * > parameterTypes() const
Returns a list with all known parameter types.
A sort/filter proxy model for providers and algorithms shown within the Processing toolbox,...
@ FilterShowKnownIssues
Show algorithms with known issues (hidden by default)
@ FilterModeler
Filters out any algorithms and content which should not be shown in the modeler.
@ PythonQgsProcessingAlgorithmSubclass
Full Python QgsProcessingAlgorithm subclass.
Definition: qgsprocessing.h:64
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
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.