54#include <QActionGroup>
57#include <QKeySequence>
63#include <QSvgGenerator>
70#include "moc_qgsmodeldesignerdialog.cpp"
72using namespace Qt::StringLiterals;
77QgsModelerToolboxModel::QgsModelerToolboxModel( QObject *parent )
81Qt::ItemFlags QgsModelerToolboxModel::flags(
const QModelIndex &index )
const
83 Qt::ItemFlags f = QgsProcessingToolboxProxyModel::flags( index );
84 const QModelIndex sourceIndex = mapToSource( index );
85 if ( toolboxModel()->isAlgorithm( sourceIndex ) || toolboxModel()->isParameter( sourceIndex ) )
87 f = f | Qt::ItemIsDragEnabled;
92Qt::DropActions QgsModelerToolboxModel::supportedDragActions()
const
94 return Qt::CopyAction;
97QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
98 : QMainWindow( parent, flags )
99 , mContext(
QgsGui::processingGuiRegistry()->contextFactory() ?
QgsGui::processingGuiRegistry()->contextFactory()->createContext() : new
QgsProcessingContext() )
100 , mToolsActionGroup( new QActionGroup( this ) )
112 setAttribute( Qt::WA_DeleteOnClose );
113 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
114 setWindowFlags( Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint );
118 mModel = std::make_unique<QgsProcessingModelAlgorithm>();
121 mUndoStack =
new QUndoStack(
this );
122 connect( mUndoStack, &QUndoStack::indexChanged,
this, [
this] {
123 if ( mIgnoreUndoStackChanges )
126 mBlockUndoCommands++;
127 updateVariablesGui();
128 mGroupEdit->setText( mModel->group() );
129 mNameEdit->setText( mModel->displayName() );
130 mBlockUndoCommands--;
135 mConfigWidgetDock->setWindowTitle( tr(
"Configuration" ) );
136 mConfigWidgetDock->setObjectName( u
"ModelConfigDock"_s );
139 mConfigWidgetDock->setWidget( mConfigWidget );
140 mConfigWidgetDock->setFeatures( QDockWidget::NoDockWidgetFeatures );
143 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
144 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
145 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
146 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
148 mToolboxTree->header()->setVisible(
false );
149 mToolboxSearchEdit->setShowSearchIcon(
true );
150 mToolboxSearchEdit->setPlaceholderText( tr(
"Search…" ) );
151 connect( mToolboxSearchEdit, &QgsFilterLineEdit::textChanged, mToolboxTree, &QgsProcessingToolboxTreeView::setFilterString );
153 mInputsTreeWidget->header()->setVisible(
false );
154 mInputsTreeWidget->setAlternatingRowColors(
true );
155 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
156 mInputsTreeWidget->setDropIndicatorShown(
true );
158 mNameEdit->setPlaceholderText( tr(
"Enter model name here" ) );
159 mGroupEdit->setPlaceholderText( tr(
"Enter group name here" ) );
162 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
163 mainLayout->insertWidget( 0, mMessageBar );
165 mView->setAcceptDrops(
true );
168 connect( mActionClose, &QAction::triggered,
this, &QWidget::close );
169 connect( mActionNew, &QAction::triggered,
this, &QgsModelDesignerDialog::newModel );
170 connect( mActionZoomIn, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomIn );
171 connect( mActionZoomOut, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomOut );
172 connect( mActionZoomActual, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomActual );
173 connect( mActionZoomToItems, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomFull );
174 connect( mActionExportImage, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToImage );
175 connect( mActionExportPdf, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToPdf );
176 connect( mActionExportSvg, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToSvg );
177 connect( mActionExportPython, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsPython );
178 connect( mActionOpen, &QAction::triggered,
this, &QgsModelDesignerDialog::openModel );
179 connect( mActionSave, &QAction::triggered,
this, [
this] { saveModel(
false ); } );
180 connect( mActionSaveAs, &QAction::triggered,
this, [
this] { saveModel(
true ); } );
181 connect( mActionSaveInProject, &QAction::triggered,
this, &QgsModelDesignerDialog::saveInProject );
182 connect( mActionDeleteComponents, &QAction::triggered,
this, &QgsModelDesignerDialog::deleteSelected );
183 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
184 connect( mActionValidate, &QAction::triggered,
this, &QgsModelDesignerDialog::validate );
185 connect( mActionReorderInputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderInputs );
186 connect( mActionReorderOutputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderOutputs );
187 connect( mActionEditHelp, &QAction::triggered,
this, &QgsModelDesignerDialog::editHelp );
188 connect( mReorderInputsButton, &QPushButton::clicked,
this, &QgsModelDesignerDialog::reorderInputs );
189 connect( mActionRun, &QAction::triggered,
this, [
this] { run(); } );
190 connect( mActionRunSelectedSteps, &QAction::triggered,
this, &QgsModelDesignerDialog::runSelectedSteps );
192 mActionSnappingEnabled->setChecked( settings.
value( u
"/Processing/Modeler/enableSnapToGrid"_s,
false ).toBool() );
193 connect( mActionSnappingEnabled, &QAction::toggled,
this, [
this](
bool enabled ) {
194 mView->snapper()->setSnapToGrid( enabled );
197 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
199 connect( mView, &QgsModelGraphicsView::itemFocused,
this, &QgsModelDesignerDialog::onItemFocused );
201 connect( mActionSelectAll, &QAction::triggered,
this, [
this] { mScene->selectAll(); } );
203 QStringList docksTitle = settings.
value( u
"ModelDesigner/hiddenDocksTitle"_s, QStringList(),
QgsSettings::App ).toStringList();
204 QStringList docksActive = settings.
value( u
"ModelDesigner/hiddenDocksActive"_s, QStringList(),
QgsSettings::App ).toStringList();
205 if ( !docksTitle.isEmpty() )
207 for (
const auto &title : docksTitle )
209 mPanelStatus.insert( title, PanelStatus(
true, docksActive.contains( title ) ) );
212 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
213 connect( mActionHidePanels, &QAction::toggled,
this, &QgsModelDesignerDialog::setPanelVisibility );
215 mUndoAction = mUndoStack->createUndoAction(
this );
217 mUndoAction->setShortcuts( QKeySequence::Undo );
218 mRedoAction = mUndoStack->createRedoAction(
this );
220 mRedoAction->setShortcuts( QKeySequence::Redo );
222 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
223 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
224 mMenuEdit->insertSeparator( mActionDeleteComponents );
225 mToolbar->insertAction( mActionZoomIn, mUndoAction );
226 mToolbar->insertAction( mActionZoomIn, mRedoAction );
227 mToolbar->insertSeparator( mActionZoomIn );
229 mGroupMenu =
new QMenu( tr(
"Zoom To" ),
this );
230 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
231 connect( mGroupMenu, &QMenu::aboutToShow,
this, &QgsModelDesignerDialog::populateZoomToMenu );
235 mActionCut =
new QAction( tr(
"Cu&t" ),
this );
236 mActionCut->setShortcuts( QKeySequence::Cut );
237 mActionCut->setStatusTip( tr(
"Cut" ) );
239 connect( mActionCut, &QAction::triggered,
this, [
this] { mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut ); } );
241 mActionCopy =
new QAction( tr(
"&Copy" ),
this );
242 mActionCopy->setShortcuts( QKeySequence::Copy );
243 mActionCopy->setStatusTip( tr(
"Copy" ) );
245 connect( mActionCopy, &QAction::triggered,
this, [
this] { mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy ); } );
247 mActionPaste =
new QAction( tr(
"&Paste" ),
this );
248 mActionPaste->setShortcuts( QKeySequence::Paste );
249 mActionPaste->setStatusTip( tr(
"Paste" ) );
251 connect( mActionPaste, &QAction::triggered,
this, [
this] { mView->pasteItems( QgsModelGraphicsView::PasteModeCursor ); } );
252 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
253 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
254 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
255 mMenuEdit->insertSeparator( mActionDeleteComponents );
257 mAlgorithmsModel =
new QgsModelerToolboxModel(
this );
258 mToolboxTree->setToolboxProxyModel( mAlgorithmsModel );
261 if ( settings.
value( u
"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES"_s,
false ).toBool() )
265 mToolboxTree->setFilters( filters );
266 mToolboxTree->setDragDropMode( QTreeWidget::DragOnly );
267 mToolboxTree->setDropIndicatorShown(
true );
269 connect( mView, &QgsModelGraphicsView::algorithmDropped,
this, [
this](
const QString &algorithmId,
const QPointF &pos ) { addAlgorithm( algorithmId, pos ); } );
270 connect( mView, &QgsModelGraphicsView::inputDropped,
this, &QgsModelDesignerDialog::addInput );
272 connect( mToolboxTree, &QgsProcessingToolboxTreeView::doubleClicked,
this, [
this](
const QModelIndex & ) {
273 if ( mToolboxTree->selectedAlgorithm() )
274 addAlgorithm( mToolboxTree->selectedAlgorithm()->id(), QPointF() );
275 if ( mToolboxTree->selectedParameterType() )
276 addInput( mToolboxTree->selectedParameterType()->id(), QPointF() );
279 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked,
this, [
this](
const QModelIndex & ) {
280 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
281 addInput( parameterType, QPointF() );
285 QShortcut *ctrlEquals =
new QShortcut( QKeySequence( u
"Ctrl+="_s ),
this );
286 connect( ctrlEquals, &QShortcut::activated,
this, &QgsModelDesignerDialog::zoomIn );
289 mUndoDock->setObjectName( u
"UndoDock"_s );
290 mUndoView =
new QUndoView( mUndoStack,
this );
291 mUndoDock->setWidget( mUndoView );
292 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
293 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
295 tabifyDockWidget( mUndoDock, mPropertiesDock );
296 tabifyDockWidget( mVariablesDock, mPropertiesDock );
297 mPropertiesDock->raise();
298 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
299 mInputsDock->raise();
304 beginUndoCommand( tr(
"Change Model Variables" ) );
305 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
309 connect( mNameEdit, &QLineEdit::textChanged,
this, [
this](
const QString &name ) {
312 beginUndoCommand( tr(
"Change Model Name" ), QString(), QgsModelUndoCommand::CommandOperation::NameChanged );
313 mModel->setName( name );
318 connect( mGroupEdit, &QLineEdit::textChanged,
this, [
this](
const QString &group ) {
321 beginUndoCommand( tr(
"Change Model Group" ), QString(), QgsModelUndoCommand::CommandOperation::GroupChanged );
322 mModel->setGroup( group );
330 QToolButton *toolbuttonExportToScript =
new QToolButton();
331 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
332 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
333 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
334 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
335 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
337 mActionShowComments->setChecked( settings.
value( u
"/Processing/Modeler/ShowComments"_s,
true ).toBool() );
338 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
340 mActionShowFeatureCount->setChecked( settings.
value( u
"/Processing/Modeler/ShowFeatureCount"_s,
true ).toBool() );
341 connect( mActionShowFeatureCount, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleFeatureCount );
344 mPanTool->setAction( mActionPan );
346 mToolsActionGroup->addAction( mActionPan );
347 connect( mActionPan, &QAction::triggered, mPanTool, [
this] { mView->setTool( mPanTool ); } );
352 mSelectTool->setAction( mActionSelectMoveItem );
354 mToolsActionGroup->addAction( mActionSelectMoveItem );
355 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [
this] { mView->setTool( mSelectTool ); } );
357 mView->setTool( mSelectTool );
360 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [
this](
const QString &text ) {
361 mIgnoreUndoStackChanges++;
362 mUndoStack->beginMacro( text );
363 mIgnoreUndoStackChanges--;
365 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [
this] {
366 mIgnoreUndoStackChanges++;
367 mUndoStack->endMacro();
368 mIgnoreUndoStackChanges--;
370 connect( mView, &QgsModelGraphicsView::commandBegun,
this, [
this](
const QString &text ) { beginUndoCommand( text ); } );
371 connect( mView, &QgsModelGraphicsView::commandEnded,
this, [
this] { endUndoCommand(); } );
372 connect( mView, &QgsModelGraphicsView::commandAborted,
this, [
this] { abortUndoCommand(); } );
373 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [
this] { deleteSelected(); } );
375 connect( mActionAddGroupBox, &QAction::triggered,
this, [
this] {
376 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
377 QgsProcessingModelGroupBox group;
378 group.setPosition( viewCenter );
379 group.setDescription( tr(
"New Group" ) );
381 beginUndoCommand( tr(
"Add Group Box" ) );
382 model()->addGroupBox( group );
390 restoreState( settings.
value( u
"ModelDesigner/state"_s, QByteArray(),
QgsSettings::App ).toByteArray() );
393QgsModelDesignerDialog::~QgsModelDesignerDialog()
395 if ( mAlgorithmWidget )
397 delete mAlgorithmWidget;
399 for (
const QPointer<QgsProcessingAlgorithmWidget> &widget : std::as_const( mAlgorithmWidgetsToCleanUp ) )
412 if ( !mPanelStatus.isEmpty() )
414 QStringList docksTitle;
415 QStringList docksActive;
417 for (
const auto &panel : mPanelStatus.toStdMap() )
419 if ( panel.second.isVisible )
420 docksTitle << panel.first;
421 if ( panel.second.isActive )
422 docksActive << panel.first;
436 mIgnoreUndoStackChanges++;
439void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
441 if ( checkForUnsavedChanges() )
447void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
const QString &
id, QgsModelUndoCommand::CommandOperation operation )
449 if ( mBlockUndoCommands || !mUndoStack )
452 if ( mActiveCommand )
457 mActiveCommand = std::make_unique<QgsModelUndoCommand>( mModel.get(), text,
id );
461 mActiveCommand = std::make_unique<QgsModelUndoCommand>( mModel.get(), text, operation );
465void QgsModelDesignerDialog::endUndoCommand()
467 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
470 mActiveCommand->saveAfterState();
471 mIgnoreUndoStackChanges++;
472 mUndoStack->push( mActiveCommand.release() );
473 mIgnoreUndoStackChanges--;
477void QgsModelDesignerDialog::abortUndoCommand()
479 if ( mActiveCommand )
480 mActiveCommand->setObsolete(
true );
483QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
488void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
490 mModel.reset( model );
492 mGroupEdit->setText( mModel->group() );
493 mNameEdit->setText( mModel->displayName() );
494 repaintModel(
true );
495 updateVariablesGui();
497 const QString modelQgisVersion = mModel->modelQgisVersion();
498 handleVersionMismatch( modelQgisVersion );
502 mIgnoreUndoStackChanges++;
504 mIgnoreUndoStackChanges--;
510 QTimer::singleShot( 100,
this, [
this] { zoomFull(); } );
513void QgsModelDesignerDialog::loadModel(
const QString &path )
515 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
516 if ( alg->fromFile( path ) )
519 alg->setSourceFilePath( path );
520 setModel( alg.release() );
525 QMessageBox::critical(
529 "The selected model could not be loaded.\n"
530 "See the log for more information."
536void QgsModelDesignerDialog::handleVersionMismatch(
const QString &modelVersion )
const
545 const QString message = tr(
546 "This model file was created in an older QGIS version."
547 " When saving this model file, QGIS will update it to version %1, "
548 "possibly rendering it unusable with older versions of QGIS."
550 .arg( thisVersion.text() );
551 mMessageBar->pushInfo( QString(), message );
553 else if ( thisVersion > fileVersion )
555 const QString message = tr(
556 "This model file was created in QGIS version %1."
557 " When saving this model file, QGIS will update it to version %2, "
558 "possibly rendering it unusable with older versions of QGIS."
560 .arg( fileVersion.
text(), thisVersion.text() );
561 mMessageBar->pushInfo( QString(), message );
563 else if ( thisVersion < fileVersion )
565 const QString message = tr(
566 "This model was created by a newer version of QGIS (%1)."
567 "The integrity of this model file may not be guaranteed."
569 .arg( fileVersion.
text() );
571 mMessageBar->pushWarning( QString(), message );
579void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
581 QgsModelGraphicsScene *oldScene = mScene;
584 mScene->setParent(
this );
585 mScene->setLastRunResult( mLastResult, mLayerStore );
586 mScene->setModel( mModel.get() );
587 mScene->setMessageBar( mMessageBar );
588 mScene->registerWidgetContextGenerator(
this );
591 const bool showFeatureCount = settings.
value( u
"/Processing/Modeler/ShowFeatureCount"_s,
true ).toBool();
592 if ( !showFeatureCount )
593 mScene->setFlag( QgsModelGraphicsScene::FlagHideFeatureCount );
595 mView->setModelScene( mScene );
597 mSelectTool->resetCache();
598 mSelectTool->setScene( mScene );
600 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [
this] {
601 if ( mBlockRepaints )
605 mScene->flagChildrenAsOutdated( mOutdatedChildResults );
607 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [
this](
const QString &description,
const QString &
id ) { beginUndoCommand( description,
id ); } );
608 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [
this] { endUndoCommand(); } );
609 connect( mScene, &QgsModelGraphicsScene::runFromChild,
this, &QgsModelDesignerDialog::runFromChild );
610 connect( mScene, &QgsModelGraphicsScene::runSelected,
this, &QgsModelDesignerDialog::runSelectedSteps );
611 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs,
this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
612 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog,
this, &QgsModelDesignerDialog::showChildAlgorithmLog );
615 oldScene->deleteLater();
618QgsModelGraphicsScene *QgsModelDesignerDialog::modelScene()
625 auto result = std::make_unique< QgsProcessingModelFeedback >();
626 mScene->setupFeedbackConnections( result.get() );
628 mOutdatedChildResults.remove( childId );
631 return result.release();
643void QgsModelDesignerDialog::activate()
647 setWindowState( windowState() & ~Qt::WindowMinimized );
653 return mContext.get();
656void QgsModelDesignerDialog::updateVariablesGui()
658 mBlockUndoCommands++;
660 auto variablesScope = std::make_unique<QgsExpressionContextScope>( tr(
"Model Variables" ) );
661 const QVariantMap modelVars = mModel->variables();
662 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
664 variablesScope->setVariable( it.key(), it.value() );
667 variablesContext.
appendScope( variablesScope.release() );
668 mVariablesEditor->setContext( &variablesContext );
669 mVariablesEditor->setEditableScopeIndex( 0 );
671 mBlockUndoCommands--;
674void QgsModelDesignerDialog::setDirty(
bool dirty )
678 if ( mAlgorithmWidget )
680 if (
QgsMessageBar *messageBar = mAlgorithmWidget->messageBar() )
682 QgsMessageBarItem *messageBarItem = messageBar->createMessage( QString(), tr(
"The model has changed, this panel should be reloaded." ) );
683 auto reloadButton =
new QPushButton( tr(
"Reload Now" ) );
684 connect( reloadButton, &QPushButton::clicked, reloadButton, [
this] {
685 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
687 QMessageBox messageBox;
688 messageBox.setIcon( QMessageBox::Icon::Warning );
689 messageBox.setWindowTitle( tr(
"Run Model" ) );
690 messageBox.setText( tr(
"This model is currently running." ) );
691 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults );
693 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
694 buttonReRun->setText( tr(
"Terminate and Reload" ) );
696 int r = messageBox.exec();
700 case QMessageBox::StandardButton::Cancel:
702 case QMessageBox::StandardButton::RestoreDefaults:
708 cancelRunningModel();
711 messageBarItem->layout()->addWidget( reloadButton );
717bool QgsModelDesignerDialog::validateSave( SaveAction action )
721 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
723 case QgsModelDesignerDialog::SaveAction::SaveInProject:
724 if ( mNameEdit->text().trimmed().isEmpty() )
726 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
735bool QgsModelDesignerDialog::checkForUnsavedChanges()
739 QMessageBox::StandardButton ret = QMessageBox::
740 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 );
743 case QMessageBox::Save:
744 return saveModel(
false );
746 case QMessageBox::Discard:
761 mLastResult.mergeWith( result );
763 mScene->setLastRunResult( mLastResult, mLayerStore );
766void QgsModelDesignerDialog::setModelName(
const QString &name )
768 mNameEdit->setText( name );
771void QgsModelDesignerDialog::saveInProject()
773 if ( !validateSave( SaveAction::SaveInProject ) )
776 mModel->setSourceFilePath( QString() );
779 if ( !projectProvider )
782 projectProvider->addModel( *mModel );
792bool QgsModelDesignerDialog::saveModel(
bool saveAs )
794 if ( !validateSave( SaveAction::SaveAsFile ) )
797 const bool modelNameMatchedFileName = mModel->modelNameMatchesFilePath();
799 if ( !mModel->sourceFilePath().isEmpty() && !saveAs )
801 fileName = mModel->sourceFilePath();
806 if ( !mModel->sourceFilePath().isEmpty() )
808 initialPath = mModel->sourceFilePath();
810 else if ( !mModel->name().isEmpty() )
819 fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Model" ), initialPath, tr(
"Processing models (*.model3 *.MODEL3)" ) );
820 if ( fileName.isEmpty() )
826 mModel->setSourceFilePath( fileName );
828 if ( mModel->name().isEmpty() || mModel->name() == tr(
"model" ) )
830 setModelName( QFileInfo( fileName ).baseName() );
832 else if ( saveAs && modelNameMatchedFileName )
836 setModelName( QFileInfo( fileName ).baseName() );
840 if ( !mModel->toFile( fileName ) )
844 QMessageBox::warning(
this, tr(
"Save Model" ), tr(
"Unable to save edits (probably you do not have permission to write to this location)." ) );
848 QMessageBox::warning(
852 "This model can't be saved in its original location (probably you do not "
853 "have permission to do it). Please, use the 'Save as…' option."
863 mMessageBar->pushMessage( QString(), tr(
"Model was saved to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( fileName ).toString(), QDir::toNativeSeparators( fileName ) ),
Qgis::MessageLevel::Success, 5 );
870QPointF QgsModelDesignerDialog::getPositionForParameterItem()
const
872 constexpr double MARGIN = 20;
873 constexpr double BOX_WIDTH = 200;
874 constexpr double BOX_HEIGHT = 80;
877 const QMap<QString, QgsProcessingModelParameter> components = mModel->parameterComponents();
878 if ( !components.isEmpty() )
881 for (
auto it = components.constBegin(); it != components.constEnd(); ++it )
883 maxX = std::max( maxX, it->position().x() );
885 newX = MARGIN + BOX_WIDTH + maxX;
889 newX = MARGIN + BOX_WIDTH / 2.0;
891 return QPointF( newX, MARGIN + BOX_HEIGHT / 2.0 );
894QPointF QgsModelDesignerDialog::getPositionForAlgorithmItem()
const
896 constexpr double MARGIN = 20;
897 constexpr double BOX_WIDTH = 200;
898 constexpr double BOX_HEIGHT = 80;
900 const QMap<QString, QgsProcessingModelChildAlgorithm> algorithms = mModel->childAlgorithms();
904 if ( !algorithms.isEmpty() )
908 for (
auto it = algorithms.constBegin(); it != algorithms.constEnd(); ++it )
910 maxX = std::max( maxX, it->position().x() );
911 maxY = std::max( maxY, it->position().y() );
913 newX = MARGIN + BOX_WIDTH + maxX;
914 newY = MARGIN + BOX_HEIGHT + maxY;
918 newX = MARGIN + BOX_WIDTH / 2.0;
919 newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2.0;
921 return QPointF( newX, newY );
926 const QString safeName = QgsProcessingModelAlgorithm::safeName( parameter->
description() );
927 QString name = safeName.toLower();
929 while ( mModel->parameterDefinition( name ) )
931 name = safeName.toLower() + QString::number( i );
937void QgsModelDesignerDialog::addAlgorithm(
const QString &algorithmId,
const QPointF &pos )
943 QgsProcessingModelChildAlgorithm childAlg = QgsProcessingModelChildAlgorithm( algorithmId );
944 childAlg.setDescription( alg->displayName() );
948 childAlg.setPosition( getPositionForAlgorithmItem() );
952 childAlg.setPosition( pos );
955 childAlg.comment()->setPosition( childAlg.position() + QPointF( childAlg.size().width(), -1.5 * childAlg.size().height() ) );
957 const double outputOffsetX = childAlg.size().width();
958 double outputOffsetY = 1.5 * childAlg.size().height();
960 const QMap<QString, QgsProcessingModelOutput> childOutputs = childAlg.modelOutputs();
961 for (
auto out = childOutputs.constBegin(); out != childOutputs.constEnd(); ++out )
963 childAlg.modelOutput( out.key() ).setPosition( childAlg.position() + QPointF( outputOffsetX, outputOffsetY ) );
964 outputOffsetY += 1.5 * childAlg.modelOutput( out.key() ).size().height();
967 beginUndoCommand( tr(
"Add Algorithm" ) );
968 mModel->addChildAlgorithm( childAlg );
973void QgsModelDesignerDialog::addInput(
const QString ¶meterType,
const QPointF &position )
975 QPointF pos = position;
979 std::unique_ptr< QgsProcessingParameterDefinition > newParam;
984 dlg.registerProcessingContextGenerator(
this );
988 newParam.reset( dlg.createParameter() );
992 autoGenerateParameterName( newParam.get() );
994 comment = dlg.comments();
998 pos = getPositionForParameterItem();
1001 QgsProcessingModelParameter component = QgsProcessingModelParameter( newParam->name() );
1002 component.setDescription( newParam->name() );
1003 component.setPosition( pos );
1005 component.comment()->setDescription( comment );
1006 component.comment()->setPosition( component.position() + QPointF( component.size().width(), -1.5 * component.size().height() ) );
1008 beginUndoCommand( tr(
"Add Model Input" ) );
1009 mModel->addModelParameter( newParam.release(), component );
1014QgsProcessingAlgorithmWidget *QgsModelDesignerDialog::createExecutionWidget()
1017 if ( !dialogFactory )
1024 widget->registerProcessingFeedbackGenerator(
this );
1028void QgsModelDesignerDialog::exportAsScriptAlgorithm()
1031 if ( !dialogFactory )
1040 dialog->codeEditor()->setText( codeLines.join(
'\n' ) );
1045void QgsModelDesignerDialog::zoomIn()
1047 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
1048 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
1050 const double factor = settings.
value( u
"/qgis/zoom_favor"_s, 2.0 ).toDouble();
1051 mView->scale( factor, factor );
1052 mView->centerOn( point );
1055void QgsModelDesignerDialog::zoomOut()
1057 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
1058 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
1060 const double factor = 1.0 / settings.
value( u
"/qgis/zoom_favor"_s, 2.0 ).toDouble();
1061 mView->scale( factor, factor );
1062 mView->centerOn( point );
1065void QgsModelDesignerDialog::zoomActual()
1067 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
1068 mView->resetTransform();
1069 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
1070 mView->centerOn( point );
1073void QgsModelDesignerDialog::zoomFull()
1075 QRectF totalRect = mView->scene()->itemsBoundingRect();
1076 totalRect.adjust( -10, -10, 10, 10 );
1077 mView->fitInView( totalRect, Qt::KeepAspectRatio );
1080void QgsModelDesignerDialog::newModel()
1082 if ( !checkForUnsavedChanges() )
1085 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
1087 setModel( alg.release() );
1090void QgsModelDesignerDialog::openModel()
1092 if ( !checkForUnsavedChanges() )
1096 const QString lastModelDir = settings.
value( u
"Processing/lastModelsDir"_s, QDir::homePath() ).toString();
1097 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Model" ), lastModelDir, tr(
"Processing models (*.model3 *.MODEL3)" ) );
1098 if ( !fileName.isEmpty() )
1100 settings.
setValue( u
"Processing/lastModelsDir"_s, QFileInfo( fileName ).absoluteDir().absolutePath() );
1101 loadModel( fileName );
1105void QgsModelDesignerDialog::exportToImage()
1108 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1110 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ), lastExportDir, tr(
"PNG files (*.png *.PNG)" ) );
1114 if ( filename.isEmpty() )
1119 const QFileInfo saveFileInfo( filename );
1122 repaintModel(
false );
1124 QRectF totalRect = mView->scene()->itemsBoundingRect();
1125 totalRect.adjust( -10, -10, 10, 10 );
1126 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
1128 QImage img( totalRect.width(), totalRect.height(), QImage::Format_ARGB32_Premultiplied );
1129 img.fill( Qt::white );
1131 painter.setRenderHint( QPainter::Antialiasing );
1132 painter.begin( &img );
1133 mView->scene()->render( &painter, imageRect, totalRect );
1136 img.save( filename );
1139 ->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 );
1140 repaintModel(
true );
1143void QgsModelDesignerDialog::exportToPdf()
1146 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1148 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ), lastExportDir, tr(
"PDF files (*.pdf *.PDF)" ) );
1152 if ( filename.isEmpty() )
1157 const QFileInfo saveFileInfo( filename );
1160 repaintModel(
false );
1162 QRectF totalRect = mView->scene()->itemsBoundingRect();
1163 totalRect.adjust( -10, -10, 10, 10 );
1164 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
1166 QPdfWriter pdfWriter( filename );
1168 const double scaleFactor = 96 / 25.4;
1170 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ), QPageLayout::Portrait, QMarginsF( 0, 0, 0, 0 ) );
1171 pageLayout.setMode( QPageLayout::FullPageMode );
1172 pdfWriter.setPageLayout( pageLayout );
1174 QPainter painter( &pdfWriter );
1175 mView->scene()->render( &painter, printerRect, totalRect );
1179 ->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 );
1180 repaintModel(
true );
1183void QgsModelDesignerDialog::exportToSvg()
1186 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1188 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ), lastExportDir, tr(
"SVG files (*.svg *.SVG)" ) );
1192 if ( filename.isEmpty() )
1197 const QFileInfo saveFileInfo( filename );
1200 repaintModel(
false );
1202 QRectF totalRect = mView->scene()->itemsBoundingRect();
1203 totalRect.adjust( -10, -10, 10, 10 );
1204 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
1207 svg.setFileName( filename );
1208 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
1209 svg.setViewBox( svgRect );
1210 svg.setTitle( mModel->displayName() );
1212 QPainter painter( &svg );
1213 mView->scene()->render( &painter, svgRect, totalRect );
1217 ->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 );
1218 repaintModel(
true );
1221void QgsModelDesignerDialog::exportAsPython()
1224 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1226 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ), lastExportDir, tr(
"Processing scripts (*.py *.PY)" ) );
1230 if ( filename.isEmpty() )
1235 const QFileInfo saveFileInfo( filename );
1240 QFile outFile( filename );
1241 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1245 QTextStream fout( &outFile );
1250 ->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 );
1253void QgsModelDesignerDialog::repaintModel(
bool showControls )
1255 auto scene =
new QgsModelGraphicsScene(
this );
1256 if ( !showControls )
1258 scene->setFlag( QgsModelGraphicsScene::Flag::FlagHideControls );
1261 const bool showComments =
QgsSettings().
value(
"/Processing/Modeler/ShowComments",
true ).toBool();
1262 if ( !showComments )
1264 scene->setFlag( QgsModelGraphicsScene::Flag::FlagHideComments );
1267 setModelScene( scene );
1268 scene->createItems( mModel.get(), *mContext );
1269 scene->updateBounds();
1272void QgsModelDesignerDialog::toggleComments(
bool show )
1276 repaintModel(
true );
1279void QgsModelDesignerDialog::toggleFeatureCount(
bool show )
1283 repaintModel(
true );
1286void QgsModelDesignerDialog::updateWindowTitle()
1288 QString title = tr(
"Model Designer" );
1289 if ( !mModel->name().isEmpty() )
1290 title = mModel->group().isEmpty() ? u
"%1: %2"_s.arg( title, mModel->name() ) : u
"%1: %2 - %3"_s.arg( title, mModel->group(), mModel->name() );
1293 title.prepend(
'*' );
1295 setWindowTitle( title );
1298void QgsModelDesignerDialog::deleteSelected()
1300 QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
1301 if ( items.empty() )
1304 if ( items.size() == 1 )
1306 items.at( 0 )->deleteComponent();
1310 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem *p1, QgsModelComponentGraphicItem *p2 ) {
1315 if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) && dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
1317 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) )
1319 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
1322 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) && dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
1324 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) )
1326 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
1329 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) && dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
1331 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) )
1333 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
1336 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) && dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
1338 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) )
1340 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
1347 beginUndoCommand( tr(
"Delete Components" ) );
1349 QVariant prevState = mModel->toVariant();
1350 mBlockUndoCommands++;
1351 mBlockRepaints =
true;
1352 bool failed =
false;
1353 while ( !items.empty() )
1355 QgsModelComponentGraphicItem *toDelete =
nullptr;
1356 for ( QgsModelComponentGraphicItem *item : items )
1358 if ( item->canDeleteComponent() )
1371 toDelete->deleteComponent();
1372 items.removeAll( toDelete );
1377 mModel->loadVariant( prevState );
1378 QMessageBox::warning(
1380 QObject::tr(
"Could not remove components" ),
1382 "Components depend on the selected items.\n"
1383 "Try to remove them before trying deleting these components."
1386 mBlockUndoCommands--;
1387 mActiveCommand.reset();
1391 mBlockUndoCommands--;
1395 mBlockRepaints =
false;
1399void QgsModelDesignerDialog::populateZoomToMenu()
1401 mGroupMenu->clear();
1402 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
1404 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
1406 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
1407 connect( zoomAction, &QAction::triggered,
this, [
this, item] {
1408 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
1409 groupRect.adjust( -10, -10, 10, 10 );
1410 mView->fitInView( groupRect, Qt::KeepAspectRatio );
1411 mView->centerOn( item );
1413 mGroupMenu->addAction( zoomAction );
1418void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
1420 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
1421 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
1425 mPanelStatus.clear();
1427 for ( QDockWidget *dock : docks )
1429 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
1430 dock->setVisible(
false );
1434 for ( QTabBar *tabBar : tabBars )
1436 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
1437 mPanelStatus[currentTabTitle].isActive =
true;
1443 for ( QDockWidget *dock : docks )
1445 if ( mPanelStatus.contains( dock->windowTitle() ) )
1447 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
1452 for ( QTabBar *tabBar : tabBars )
1455 for (
int i = 0; i < tabBar->count(); ++i )
1457 QString tabTitle = tabBar->tabText( i );
1458 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
1460 tabBar->setCurrentIndex( i );
1464 mPanelStatus.clear();
1468void QgsModelDesignerDialog::editHelp()
1470 QgsProcessingHelpEditorDialog dialog(
this );
1471 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
1472 dialog.setAlgorithm( mModel.get() );
1473 if ( dialog.exec() )
1475 beginUndoCommand( tr(
"Edit Model Help" ) );
1476 mModel->setHelpContent( dialog.helpContent() );
1481void QgsModelDesignerDialog::runSelectedSteps()
1483 QSet<QString> children;
1484 const QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
1485 for ( QgsModelComponentGraphicItem *item : items )
1487 if ( QgsProcessingModelChildAlgorithm *childAlgorithm =
dynamic_cast<QgsProcessingModelChildAlgorithm *
>( item->component() ) )
1489 children.insert( childAlgorithm->childId() );
1493 if ( children.isEmpty() )
1495 mMessageBar->pushWarning( QString(), tr(
"No steps are selected" ) );
1502void QgsModelDesignerDialog::runFromChild(
const QString &
id )
1504 QSet<QString> children = mModel->dependentChildAlgorithms(
id );
1505 children.insert(
id );
1509void QgsModelDesignerDialog::cancelRunningModel()
1511 if ( !mAlgorithmWidget )
1517 mAlgorithmWidget->cancel();
1519 mAlgorithmWidget->forceClose();
1522 if ( mAlgorithmWidget )
1528 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1530 mAlgorithmWidget.clear();
1533void QgsModelDesignerDialog::run(
const QSet<QString> &childAlgorithmSubset )
1536 const bool isValid = model()->validate( errors );
1539 QMessageBox messageBox;
1540 messageBox.setWindowTitle( tr(
"Model is Invalid" ) );
1541 messageBox.setIcon( QMessageBox::Icon::Warning );
1542 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?" ) );
1543 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1544 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1546 QString errorString;
1547 for (
const QString &error : std::as_const( errors ) )
1549 QString cleanedError = error;
1550 const thread_local QRegularExpression re( u
"<[^>]*>"_s );
1551 cleanedError.replace( re, QString() );
1552 errorString += u
"• %1\n"_s.arg( cleanedError );
1555 messageBox.setDetailedText( errorString );
1556 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1560 if ( !childAlgorithmSubset.isEmpty() )
1562 for (
const QString &child : childAlgorithmSubset )
1565 const QSet<QString> requirements = mModel->dependsOnChildAlgorithms( child );
1566 for (
const QString &requirement : requirements )
1568 if ( !mLastResult.executedChildIds().contains( requirement ) )
1570 QMessageBox messageBox;
1571 messageBox.setWindowTitle( tr(
"Run Model" ) );
1572 messageBox.setIcon( QMessageBox::Icon::Warning );
1573 messageBox.setText( tr(
"Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1574 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1582 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
1584 QMessageBox messageBox;
1585 messageBox.setIcon( QMessageBox::Icon::Warning );
1586 messageBox.setWindowTitle( tr(
"Run Model" ) );
1587 messageBox.setText( tr(
"This model is already running." ) );
1588 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults | QMessageBox::StandardButton::Ok );
1590 QAbstractButton *buttonShowRunningAlg = messageBox.button( QMessageBox::StandardButton::Ok );
1591 buttonShowRunningAlg->setText( tr(
"Show Progress" ) );
1593 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
1594 buttonReRun->setText( tr(
"Cancel and Restart Model" ) );
1596 int r = messageBox.exec();
1600 case QMessageBox::StandardButton::Cancel:
1602 case QMessageBox::StandardButton::RestoreDefaults:
1603 cancelRunningModel();
1605 case QMessageBox::StandardButton::Ok:
1606 mAlgorithmWidget->showWidget();
1612 else if ( mAlgorithmWidget )
1615 mAlgorithmWidget->close();
1616 if ( mAlgorithmWidget )
1622 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1625 mAlgorithmWidget.clear();
1628 if ( !mAlgorithmWidget )
1630 mAlgorithmWidget = createExecutionWidget();
1631 if ( !mAlgorithmWidget )
1634 mAlgorithmWidget->hideShortHelp();
1635 mAlgorithmWidget->setTitle( tr(
"Run Model" ) );
1638 mAlgorithmWidget->setParameters( mModel->designerParameterValues() );
1640 if ( !childAlgorithmSubset.isEmpty() )
1642 mAlgorithmWidget->runButton()->setText( tr(
"Run Subset" ) );
1643 mAlgorithmWidget->runButton()->setToolTip( tr(
"Runs a subset of the child algorithms from this model" ) );
1646 connect( mAlgorithmWidget.get(), &QgsProcessingAlgorithmWidgetBase::algorithmAboutToRun,
this, [
this, childAlgorithmSubset](
QgsProcessingContext *context ) {
1647 if ( !childAlgorithmSubset.empty() )
1650 auto modelConfig = std::make_unique<QgsProcessingModelInitialRunConfig>();
1651 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1652 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1653 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1654 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1658 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1659 auto previousResultStore = std::make_unique<QgsMapLayerStore>();
1660 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1662 std::unique_ptr<QgsMapLayer> clone( it.value()->clone() );
1663 clone->setId( it.value()->id() );
1664 previousResultStore->addMapLayer( clone.release() );
1666 previousResultStore->moveToThread( nullptr );
1667 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1668 context->setModelInitialRunConfig( std::move( modelConfig ) );
1670 mScene->resetChildAlgorithmItems( childAlgorithmSubset );
1673 for ( const QString &child : childAlgorithmSubset )
1675 const QSet< QString > outdated = mModel->dependentChildAlgorithms( child );
1676 mScene->flagChildrenAsOutdated( outdated );
1677 mOutdatedChildResults.unite( outdated );
1683 mScene->resetChildAlgorithmItems();
1687 connect( mAlgorithmWidget, &QgsProcessingAlgorithmWidgetBase::algorithmFinished,
this, [
this](
bool,
const QVariantMap &,
QgsProcessingContext *context ) {
1689 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1690 mLayerStore.takeResultsFrom( *context );
1698void QgsModelDesignerDialog::showChildAlgorithmOutputs(
const QString &childId )
1700 const bool isOutdated = mOutdatedChildResults.contains( childId );
1701 const QString childDescription = mModel->childAlgorithm( childId ).description();
1704 const QVariantMap childAlgorithmOutputs = result.
outputs();
1705 if ( childAlgorithmOutputs.isEmpty() )
1707 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1714 mMessageBar->pushCritical( QString(), tr(
"Results cannot be shown for an invalid model component" ) );
1718 const QList<const QgsProcessingParameterDefinition *> outputParams =
algorithm->destinationParameterDefinitions();
1719 if ( outputParams.isEmpty() )
1722 QgsDebugError(
"Cannot show results for algorithms with no outputs" );
1726 bool foundResults =
false;
1729 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1730 if ( !output.isValid() )
1733 if ( output.type() == QVariant::String )
1737 QgsDebugMsgLevel( u
"Loading previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ), 2 );
1739 std::unique_ptr<QgsMapLayer> layer( resultLayer->clone() );
1742 if ( outputParams.size() > 1 )
1743 baseName = tr(
"%1 — %2" ).arg( childDescription, outputParam->name() );
1745 baseName = childDescription;
1749 QString name = baseName;
1754 name = tr(
"%1 (%2)" ).arg( baseName ).arg( counter );
1757 layer->setName( name );
1760 foundResults =
true;
1765 QgsDebugError( u
"Could not load previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ) );
1770 if ( !foundResults )
1772 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1775 else if ( isOutdated )
1777 mMessageBar->pushWarning( QString(), tr(
"These results are outdated, and may not reflect the most recent model execution" ) );
1782void QgsModelDesignerDialog::showChildAlgorithmLog(
const QString &childId )
1784 const QString childDescription = mModel->childAlgorithm( childId ).description();
1789 if ( QgsModelChildAlgorithmGraphicItem *item = mScene->childAlgorithmItem( childId ) )
1791 result = item->results();
1793 if ( result.
htmlLog().isEmpty() )
1795 result = mLastResult.childResults().value( childId );
1798 if ( result.
htmlLog().isEmpty() )
1800 mMessageBar->pushWarning( QString(), tr(
"No log is available for %1" ).arg( childDescription ) );
1805 m.setWindowTitle( childDescription );
1806 m.setCheckBoxVisible(
false );
1807 m.setMessageAsHtml( result.
htmlLog() );
1811void QgsModelDesignerDialog::onItemFocused( QgsModelComponentGraphicItem *item )
1817 if ( !item || !item->component() )
1819 mConfigWidget->showComponentConfig(
nullptr, *mContext, widgetContext );
1823 mConfigWidget->showComponentConfig( item->component(), *mContext, widgetContext );
1825 if (
auto childAlgorithmItem = qobject_cast< QgsModelChildAlgorithmGraphicItem * >( item ) )
1827 connect( childAlgorithmItem, &QgsModelChildAlgorithmGraphicItem::rebuildConfigurationDockWidget, childAlgorithmItem, [
this] {
1831 mConfigWidget->showComponentConfig(
nullptr, *mContext, widgetContext );
1837void QgsModelDesignerDialog::validate()
1840 if ( model()->validate( issues ) )
1842 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
1847 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
1848 connect( detailsButton, &QPushButton::clicked, detailsButton, [detailsButton, issues] {
1850 dialog->
setTitle( tr(
"Model is Invalid" ) );
1852 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + u
"<ul>"_s;
1853 for (
const QString &issue : issues )
1855 longMessage += u
"<li>%1</li>"_s.arg( issue );
1857 longMessage +=
"</ul>"_L1;
1862 messageWidget->layout()->addWidget( detailsButton );
1863 mMessageBar->clearWidgets();
1868void QgsModelDesignerDialog::reorderInputs()
1870 QgsModelInputReorderDialog dlg(
this );
1871 dlg.setModel( mModel.get() );
1874 const QStringList inputOrder = dlg.inputOrder();
1875 beginUndoCommand( tr(
"Reorder Inputs" ) );
1876 mModel->setParameterOrder( inputOrder );
1881void QgsModelDesignerDialog::reorderOutputs()
1883 QgsModelOutputReorderDialog dlg(
this );
1884 dlg.setModel( mModel.get() );
1887 const QStringList outputOrder = dlg.outputOrder();
1888 beginUndoCommand( tr(
"Reorder Outputs" ) );
1889 mModel->setOutputOrder( outputOrder );
1890 mModel->setOutputGroup( dlg.outputGroup() );
1895bool QgsModelDesignerDialog::isDirty()
const
1897 return mHasChanged && mUndoStack->index() != -1;
1900void QgsModelDesignerDialog::fillInputsTree()
1903 auto parametersItem = std::make_unique<QTreeWidgetItem>();
1904 parametersItem->setText( 0, tr(
"Parameters" ) );
1907 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1914 auto paramItem = std::make_unique<QTreeWidgetItem>();
1915 paramItem->setText( 0, param->name() );
1916 paramItem->setData( 0, Qt::UserRole, param->id() );
1917 paramItem->setIcon( 0, icon );
1918 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1919 paramItem->setToolTip( 0, param->description() );
1920 parametersItem->addChild( paramItem.release() );
1923 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1924 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1932QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1935 , mChildId( childId )
1937 QHBoxLayout *hl =
new QHBoxLayout();
1938 hl->setContentsMargins( 0, 0, 0, 0 );
1940 mLineEdit =
new QLineEdit();
1941 mLineEdit->setEnabled(
false );
1942 hl->addWidget( mLineEdit, 1 );
1944 mToolButton =
new QToolButton();
1945 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1946 hl->addWidget( mToolButton );
1950 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1952 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1955void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1957 const bool hasChanged = value != mValue;
1960 updateSummaryText();
1967void QgsModelChildDependenciesWidget::showDialog()
1969 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1971 QVariantList availableOptions;
1972 for (
const QgsProcessingModelChildDependency &dep : available )
1973 availableOptions << QVariant::fromValue( dep );
1974 QVariantList selectedOptions;
1975 for (
const QgsProcessingModelChildDependency &dep : mValue )
1976 selectedOptions << QVariant::fromValue( dep );
1981 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1982 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1984 widget->setValueFormatter( [
this](
const QVariant &v ) -> QString {
1985 const QgsProcessingModelChildDependency dep = v.value<QgsProcessingModelChildDependency>();
1987 const QString description = mModel->childAlgorithm( dep.childId ).description();
1988 if ( dep.conditionalBranch.isEmpty() )
1991 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1994 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [
this, widget]() {
1995 QList<QgsProcessingModelChildDependency> res;
1996 for (
const QVariant &v : widget->selectedOptions() )
1998 res << v.value<QgsProcessingModelChildDependency>();
2007void QgsModelChildDependenciesWidget::updateSummaryText()
2009 mLineEdit->setText( tr(
"%n dependencies selected",
nullptr, mValue.count() ) );
static QString version()
Version string.
@ ExposeToModeler
Is this parameter available in the modeler. Is set to on by default.
@ Warning
Warning message.
@ Critical
Critical/error message.
@ Success
Used for reporting a successful operation.
@ ForceDocked
Force the widget to be docked.
@ ModelDebug
Model debug level logging. Includes verbose logging and other outputs useful for debugging models.
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.
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.
A singleton class containing various registry and other global members related to GUI classes.
static QSize iconSize(Qgis::UserInterfaceIconType iconType=Qgis::UserInterfaceIconType::MainWindowToolbar)
Returns the desired size for user interface icons.
static QgsProcessingGuiRegistry * processingGuiRegistry()
Returns the global processing gui registry, used for registering the GUI behavior of processing algor...
void applicationStyleSheetChanged(const QString &styleSheet)
Emitted whenever the application style sheet is changed.
static QString applicationStyleSheet()
Returns the application style sheet.
static QgsGui * instance()
Returns a pointer to the singleton instance.
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...
Base class for all map layer types.
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
Abstract base class for processing algorithms.
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.
A factory for creating widgets and dialogs for Processing components.
virtual QgsProcessingScriptEditorDialog * createScriptEditorDialog(const QString &filePath=QString(), QWidget *parent=nullptr)=0
Creates a new script editor dialog.
virtual QgsProcessingAlgorithmWidget * createWidget(QgsProcessingAlgorithm *algorithm, bool inPlace=false, QMainWindow *parent=nullptr, QgsProcessingAlgorithmWidgetBase::WidgetFlags flags=QgsProcessingAlgorithmWidgetBase::WidgetFlags(), Qgis::DockableWidgetInitialState initialState=Qgis::DockableWidgetInitialState::RestorePreviousState)=0
Creates a new widget for executing the specified algorithm.
Base class for providing feedback from a processing algorithm.
QgsProcessingDialogFactory * dialogFactory()
Returns the application's processing dialog factory, if set.
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.
A dialog which allows users to specify the properties of a Processing parameter.
Base class for the definition of processing parameters.
QString description() const
Returns the description for the parameter.
void setName(const QString &name)
Sets the name of the parameter.
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.
static QStringList modelFolders()
Returns the list of locations for reading Processing model files.
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.
static const QString MODEL_PROVIDER_ID
Constant used for the model provider's ID.
@ PythonQgsProcessingAlgorithmSubclass
Full Python QgsProcessingAlgorithm subclass.
static const QString PROJECT_PROVIDER_ID
Constant used for the project model provider's ID.
Describes the version of a project.
QString text() const
Returns a string representation of the version.
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.
void setDirty(bool b=true)
Flag the project as dirty (modified).
A utility class for dynamic handling of changes to screen properties.
Stores settings for use within QGIS.
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 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)
#define QgsDebugError(str)
constexpr QObjectUniquePtr< Tp > make_qobject_unique(Args &&...args)
Create an object owned by a QObjectUniquePtr.