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();
499 mIgnoreUndoStackChanges++;
501 mIgnoreUndoStackChanges--;
507 QTimer::singleShot( 100,
this, [
this] { zoomFull(); } );
510void QgsModelDesignerDialog::loadModel(
const QString &path )
512 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
513 if ( alg->fromFile( path ) )
516 alg->setSourceFilePath( path );
517 setModel( alg.release() );
522 QMessageBox::critical(
526 "The selected model could not be loaded.\n"
527 "See the log for more information."
533void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
535 QgsModelGraphicsScene *oldScene = mScene;
538 mScene->setParent(
this );
539 mScene->setLastRunResult( mLastResult, mLayerStore );
540 mScene->setModel( mModel.get() );
541 mScene->setMessageBar( mMessageBar );
542 mScene->registerWidgetContextGenerator(
this );
545 const bool showFeatureCount = settings.
value( u
"/Processing/Modeler/ShowFeatureCount"_s,
true ).toBool();
546 if ( !showFeatureCount )
547 mScene->setFlag( QgsModelGraphicsScene::FlagHideFeatureCount );
549 mView->setModelScene( mScene );
551 mSelectTool->resetCache();
552 mSelectTool->setScene( mScene );
554 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [
this] {
555 if ( mBlockRepaints )
559 mScene->flagChildrenAsOutdated( mOutdatedChildResults );
561 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [
this](
const QString &description,
const QString &
id ) { beginUndoCommand( description,
id ); } );
562 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [
this] { endUndoCommand(); } );
563 connect( mScene, &QgsModelGraphicsScene::runFromChild,
this, &QgsModelDesignerDialog::runFromChild );
564 connect( mScene, &QgsModelGraphicsScene::runSelected,
this, &QgsModelDesignerDialog::runSelectedSteps );
565 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs,
this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
566 connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog,
this, &QgsModelDesignerDialog::showChildAlgorithmLog );
569 oldScene->deleteLater();
572QgsModelGraphicsScene *QgsModelDesignerDialog::modelScene()
579 auto result = std::make_unique< QgsProcessingModelFeedback >();
580 mScene->setupFeedbackConnections( result.get() );
582 mOutdatedChildResults.remove( childId );
585 return result.release();
597void QgsModelDesignerDialog::activate()
601 setWindowState( windowState() & ~Qt::WindowMinimized );
607 return mContext.get();
610void QgsModelDesignerDialog::updateVariablesGui()
612 mBlockUndoCommands++;
614 auto variablesScope = std::make_unique<QgsExpressionContextScope>( tr(
"Model Variables" ) );
615 const QVariantMap modelVars = mModel->variables();
616 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
618 variablesScope->setVariable( it.key(), it.value() );
621 variablesContext.
appendScope( variablesScope.release() );
622 mVariablesEditor->setContext( &variablesContext );
623 mVariablesEditor->setEditableScopeIndex( 0 );
625 mBlockUndoCommands--;
628void QgsModelDesignerDialog::setDirty(
bool dirty )
632 if ( mAlgorithmWidget )
634 if (
QgsMessageBar *messageBar = mAlgorithmWidget->messageBar() )
636 QgsMessageBarItem *messageBarItem = messageBar->createMessage( QString(), tr(
"The model has changed, this panel should be reloaded." ) );
637 auto reloadButton =
new QPushButton( tr(
"Reload Now" ) );
638 connect( reloadButton, &QPushButton::clicked, reloadButton, [
this] {
639 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
641 QMessageBox messageBox;
642 messageBox.setIcon( QMessageBox::Icon::Warning );
643 messageBox.setWindowTitle( tr(
"Run Model" ) );
644 messageBox.setText( tr(
"This model is currently running." ) );
645 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults );
647 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
648 buttonReRun->setText( tr(
"Terminate and Reload" ) );
650 int r = messageBox.exec();
654 case QMessageBox::StandardButton::Cancel:
656 case QMessageBox::StandardButton::RestoreDefaults:
662 cancelRunningModel();
665 messageBarItem->layout()->addWidget( reloadButton );
671bool QgsModelDesignerDialog::validateSave( SaveAction action )
675 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
677 case QgsModelDesignerDialog::SaveAction::SaveInProject:
678 if ( mNameEdit->text().trimmed().isEmpty() )
680 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
689bool QgsModelDesignerDialog::checkForUnsavedChanges()
693 QMessageBox::StandardButton ret = QMessageBox::
694 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 );
697 case QMessageBox::Save:
698 return saveModel(
false );
700 case QMessageBox::Discard:
715 mLastResult.mergeWith( result );
717 mScene->setLastRunResult( mLastResult, mLayerStore );
720void QgsModelDesignerDialog::setModelName(
const QString &name )
722 mNameEdit->setText( name );
725void QgsModelDesignerDialog::saveInProject()
727 if ( !validateSave( SaveAction::SaveInProject ) )
730 mModel->setSourceFilePath( QString() );
733 if ( !projectProvider )
736 projectProvider->addModel( *mModel );
746bool QgsModelDesignerDialog::saveModel(
bool saveAs )
748 if ( !validateSave( SaveAction::SaveAsFile ) )
751 const bool modelNameMatchedFileName = mModel->modelNameMatchesFilePath();
753 if ( !mModel->sourceFilePath().isEmpty() && !saveAs )
755 fileName = mModel->sourceFilePath();
760 if ( !mModel->sourceFilePath().isEmpty() )
762 initialPath = mModel->sourceFilePath();
764 else if ( !mModel->name().isEmpty() )
773 fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Model" ), initialPath, tr(
"Processing models (*.model3 *.MODEL3)" ) );
774 if ( fileName.isEmpty() )
780 mModel->setSourceFilePath( fileName );
782 if ( mModel->name().isEmpty() || mModel->name() == tr(
"model" ) )
784 setModelName( QFileInfo( fileName ).baseName() );
786 else if ( saveAs && modelNameMatchedFileName )
790 setModelName( QFileInfo( fileName ).baseName() );
794 if ( !mModel->toFile( fileName ) )
798 QMessageBox::warning(
this, tr(
"Save Model" ), tr(
"Unable to save edits (probably you do not have permission to write to this location)." ) );
802 QMessageBox::warning(
806 "This model can't be saved in its original location (probably you do not "
807 "have permission to do it). Please, use the 'Save as…' option."
817 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 );
824QPointF QgsModelDesignerDialog::getPositionForParameterItem()
const
826 constexpr double MARGIN = 20;
827 constexpr double BOX_WIDTH = 200;
828 constexpr double BOX_HEIGHT = 80;
831 const QMap<QString, QgsProcessingModelParameter> components = mModel->parameterComponents();
832 if ( !components.isEmpty() )
835 for (
auto it = components.constBegin(); it != components.constEnd(); ++it )
837 maxX = std::max( maxX, it->position().x() );
839 newX = MARGIN + BOX_WIDTH + maxX;
843 newX = MARGIN + BOX_WIDTH / 2.0;
845 return QPointF( newX, MARGIN + BOX_HEIGHT / 2.0 );
848QPointF QgsModelDesignerDialog::getPositionForAlgorithmItem()
const
850 constexpr double MARGIN = 20;
851 constexpr double BOX_WIDTH = 200;
852 constexpr double BOX_HEIGHT = 80;
854 const QMap<QString, QgsProcessingModelChildAlgorithm> algorithms = mModel->childAlgorithms();
858 if ( !algorithms.isEmpty() )
862 for (
auto it = algorithms.constBegin(); it != algorithms.constEnd(); ++it )
864 maxX = std::max( maxX, it->position().x() );
865 maxY = std::max( maxY, it->position().y() );
867 newX = MARGIN + BOX_WIDTH + maxX;
868 newY = MARGIN + BOX_HEIGHT + maxY;
872 newX = MARGIN + BOX_WIDTH / 2.0;
873 newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2.0;
875 return QPointF( newX, newY );
880 const QString safeName = QgsProcessingModelAlgorithm::safeName( parameter->
description() );
881 QString name = safeName.toLower();
883 while ( mModel->parameterDefinition( name ) )
885 name = safeName.toLower() + QString::number( i );
891void QgsModelDesignerDialog::addAlgorithm(
const QString &algorithmId,
const QPointF &pos )
897 QgsProcessingModelChildAlgorithm childAlg = QgsProcessingModelChildAlgorithm( algorithmId );
898 childAlg.setDescription( alg->displayName() );
902 childAlg.setPosition( getPositionForAlgorithmItem() );
906 childAlg.setPosition( pos );
909 childAlg.comment()->setPosition( childAlg.position() + QPointF( childAlg.size().width(), -1.5 * childAlg.size().height() ) );
911 const double outputOffsetX = childAlg.size().width();
912 double outputOffsetY = 1.5 * childAlg.size().height();
914 const QMap<QString, QgsProcessingModelOutput> childOutputs = childAlg.modelOutputs();
915 for (
auto out = childOutputs.constBegin(); out != childOutputs.constEnd(); ++out )
917 childAlg.modelOutput( out.key() ).setPosition( childAlg.position() + QPointF( outputOffsetX, outputOffsetY ) );
918 outputOffsetY += 1.5 * childAlg.modelOutput( out.key() ).size().height();
921 beginUndoCommand( tr(
"Add Algorithm" ) );
922 mModel->addChildAlgorithm( childAlg );
927void QgsModelDesignerDialog::addInput(
const QString ¶meterType,
const QPointF &position )
929 QPointF pos = position;
933 std::unique_ptr< QgsProcessingParameterDefinition > newParam;
938 dlg.registerProcessingContextGenerator(
this );
942 newParam.reset( dlg.createParameter() );
946 autoGenerateParameterName( newParam.get() );
948 comment = dlg.comments();
952 pos = getPositionForParameterItem();
955 QgsProcessingModelParameter component = QgsProcessingModelParameter( newParam->name() );
956 component.setDescription( newParam->name() );
957 component.setPosition( pos );
959 component.comment()->setDescription( comment );
960 component.comment()->setPosition( component.position() + QPointF( component.size().width(), -1.5 * component.size().height() ) );
962 beginUndoCommand( tr(
"Add Model Input" ) );
963 mModel->addModelParameter( newParam.release(), component );
968QgsProcessingAlgorithmWidget *QgsModelDesignerDialog::createExecutionWidget()
971 if ( !dialogFactory )
978 widget->registerProcessingFeedbackGenerator(
this );
982void QgsModelDesignerDialog::exportAsScriptAlgorithm()
985 if ( !dialogFactory )
994 dialog->codeEditor()->setText( codeLines.join(
'\n' ) );
999void QgsModelDesignerDialog::zoomIn()
1001 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
1002 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
1004 const double factor = settings.
value( u
"/qgis/zoom_favor"_s, 2.0 ).toDouble();
1005 mView->scale( factor, factor );
1006 mView->centerOn( point );
1009void QgsModelDesignerDialog::zoomOut()
1011 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
1012 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
1014 const double factor = 1.0 / settings.
value( u
"/qgis/zoom_favor"_s, 2.0 ).toDouble();
1015 mView->scale( factor, factor );
1016 mView->centerOn( point );
1019void QgsModelDesignerDialog::zoomActual()
1021 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
1022 mView->resetTransform();
1023 mView->scale( mScreenHelper->screenDpi() / 96, mScreenHelper->screenDpi() / 96 );
1024 mView->centerOn( point );
1027void QgsModelDesignerDialog::zoomFull()
1029 QRectF totalRect = mView->scene()->itemsBoundingRect();
1030 totalRect.adjust( -10, -10, 10, 10 );
1031 mView->fitInView( totalRect, Qt::KeepAspectRatio );
1034void QgsModelDesignerDialog::newModel()
1036 if ( !checkForUnsavedChanges() )
1039 auto alg = std::make_unique<QgsProcessingModelAlgorithm>();
1041 setModel( alg.release() );
1044void QgsModelDesignerDialog::openModel()
1046 if ( !checkForUnsavedChanges() )
1050 const QString lastModelDir = settings.
value( u
"Processing/lastModelsDir"_s, QDir::homePath() ).toString();
1051 const QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Open Model" ), lastModelDir, tr(
"Processing models (*.model3 *.MODEL3)" ) );
1052 if ( !fileName.isEmpty() )
1054 settings.
setValue( u
"Processing/lastModelsDir"_s, QFileInfo( fileName ).absoluteDir().absolutePath() );
1055 loadModel( fileName );
1059void QgsModelDesignerDialog::exportToImage()
1062 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1064 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ), lastExportDir, tr(
"PNG files (*.png *.PNG)" ) );
1068 if ( filename.isEmpty() )
1073 const QFileInfo saveFileInfo( filename );
1076 repaintModel(
false );
1078 QRectF totalRect = mView->scene()->itemsBoundingRect();
1079 totalRect.adjust( -10, -10, 10, 10 );
1080 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
1082 QImage img( totalRect.width(), totalRect.height(), QImage::Format_ARGB32_Premultiplied );
1083 img.fill( Qt::white );
1085 painter.setRenderHint( QPainter::Antialiasing );
1086 painter.begin( &img );
1087 mView->scene()->render( &painter, imageRect, totalRect );
1090 img.save( filename );
1093 ->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 );
1094 repaintModel(
true );
1097void QgsModelDesignerDialog::exportToPdf()
1100 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1102 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ), lastExportDir, tr(
"PDF files (*.pdf *.PDF)" ) );
1106 if ( filename.isEmpty() )
1111 const QFileInfo saveFileInfo( filename );
1114 repaintModel(
false );
1116 QRectF totalRect = mView->scene()->itemsBoundingRect();
1117 totalRect.adjust( -10, -10, 10, 10 );
1118 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
1120 QPdfWriter pdfWriter( filename );
1122 const double scaleFactor = 96 / 25.4;
1124 QPageLayout pageLayout( QPageSize( totalRect.size() / scaleFactor, QPageSize::Millimeter ), QPageLayout::Portrait, QMarginsF( 0, 0, 0, 0 ) );
1125 pageLayout.setMode( QPageLayout::FullPageMode );
1126 pdfWriter.setPageLayout( pageLayout );
1128 QPainter painter( &pdfWriter );
1129 mView->scene()->render( &painter, printerRect, totalRect );
1133 ->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 );
1134 repaintModel(
true );
1137void QgsModelDesignerDialog::exportToSvg()
1140 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1142 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ), lastExportDir, tr(
"SVG files (*.svg *.SVG)" ) );
1146 if ( filename.isEmpty() )
1151 const QFileInfo saveFileInfo( filename );
1154 repaintModel(
false );
1156 QRectF totalRect = mView->scene()->itemsBoundingRect();
1157 totalRect.adjust( -10, -10, 10, 10 );
1158 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
1161 svg.setFileName( filename );
1162 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
1163 svg.setViewBox( svgRect );
1164 svg.setTitle( mModel->displayName() );
1166 QPainter painter( &svg );
1167 mView->scene()->render( &painter, svgRect, totalRect );
1171 ->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 );
1172 repaintModel(
true );
1175void QgsModelDesignerDialog::exportAsPython()
1178 QString lastExportDir = settings.
value( u
"lastModelDesignerExportDir"_s, QDir::homePath(),
QgsSettings::App ).toString();
1180 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ), lastExportDir, tr(
"Processing scripts (*.py *.PY)" ) );
1184 if ( filename.isEmpty() )
1189 const QFileInfo saveFileInfo( filename );
1194 QFile outFile( filename );
1195 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1199 QTextStream fout( &outFile );
1204 ->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 );
1207void QgsModelDesignerDialog::repaintModel(
bool showControls )
1209 auto scene =
new QgsModelGraphicsScene(
this );
1210 if ( !showControls )
1212 scene->setFlag( QgsModelGraphicsScene::Flag::FlagHideControls );
1215 const bool showComments =
QgsSettings().
value(
"/Processing/Modeler/ShowComments",
true ).toBool();
1216 if ( !showComments )
1218 scene->setFlag( QgsModelGraphicsScene::Flag::FlagHideComments );
1221 setModelScene( scene );
1222 scene->createItems( mModel.get(), *mContext );
1223 scene->updateBounds();
1226void QgsModelDesignerDialog::toggleComments(
bool show )
1230 repaintModel(
true );
1233void QgsModelDesignerDialog::toggleFeatureCount(
bool show )
1237 repaintModel(
true );
1240void QgsModelDesignerDialog::updateWindowTitle()
1242 QString title = tr(
"Model Designer" );
1243 if ( !mModel->name().isEmpty() )
1244 title = mModel->group().isEmpty() ? u
"%1: %2"_s.arg( title, mModel->name() ) : u
"%1: %2 - %3"_s.arg( title, mModel->group(), mModel->name() );
1247 title.prepend(
'*' );
1249 setWindowTitle( title );
1252void QgsModelDesignerDialog::deleteSelected()
1254 QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
1255 if ( items.empty() )
1258 if ( items.size() == 1 )
1260 items.at( 0 )->deleteComponent();
1264 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem *p1, QgsModelComponentGraphicItem *p2 ) {
1269 if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) && dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
1271 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p1 ) )
1273 else if ( dynamic_cast<QgsModelCommentGraphicItem *>( p2 ) )
1276 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) && dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
1278 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p1 ) )
1280 else if ( dynamic_cast<QgsModelGroupBoxGraphicItem *>( p2 ) )
1283 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) && dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
1285 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p1 ) )
1287 else if ( dynamic_cast<QgsModelOutputGraphicItem *>( p2 ) )
1290 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) && dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
1292 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p1 ) )
1294 else if ( dynamic_cast<QgsModelChildAlgorithmGraphicItem *>( p2 ) )
1301 beginUndoCommand( tr(
"Delete Components" ) );
1303 QVariant prevState = mModel->toVariant();
1304 mBlockUndoCommands++;
1305 mBlockRepaints =
true;
1306 bool failed =
false;
1307 while ( !items.empty() )
1309 QgsModelComponentGraphicItem *toDelete =
nullptr;
1310 for ( QgsModelComponentGraphicItem *item : items )
1312 if ( item->canDeleteComponent() )
1325 toDelete->deleteComponent();
1326 items.removeAll( toDelete );
1331 mModel->loadVariant( prevState );
1332 QMessageBox::warning(
1334 QObject::tr(
"Could not remove components" ),
1336 "Components depend on the selected items.\n"
1337 "Try to remove them before trying deleting these components."
1340 mBlockUndoCommands--;
1341 mActiveCommand.reset();
1345 mBlockUndoCommands--;
1349 mBlockRepaints =
false;
1353void QgsModelDesignerDialog::populateZoomToMenu()
1355 mGroupMenu->clear();
1356 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
1358 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
1360 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
1361 connect( zoomAction, &QAction::triggered,
this, [
this, item] {
1362 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
1363 groupRect.adjust( -10, -10, 10, 10 );
1364 mView->fitInView( groupRect, Qt::KeepAspectRatio );
1365 mView->centerOn( item );
1367 mGroupMenu->addAction( zoomAction );
1372void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
1374 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
1375 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
1379 mPanelStatus.clear();
1381 for ( QDockWidget *dock : docks )
1383 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
1384 dock->setVisible(
false );
1388 for ( QTabBar *tabBar : tabBars )
1390 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
1391 mPanelStatus[currentTabTitle].isActive =
true;
1397 for ( QDockWidget *dock : docks )
1399 if ( mPanelStatus.contains( dock->windowTitle() ) )
1401 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
1406 for ( QTabBar *tabBar : tabBars )
1409 for (
int i = 0; i < tabBar->count(); ++i )
1411 QString tabTitle = tabBar->tabText( i );
1412 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
1414 tabBar->setCurrentIndex( i );
1418 mPanelStatus.clear();
1422void QgsModelDesignerDialog::editHelp()
1424 QgsProcessingHelpEditorDialog dialog(
this );
1425 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
1426 dialog.setAlgorithm( mModel.get() );
1427 if ( dialog.exec() )
1429 beginUndoCommand( tr(
"Edit Model Help" ) );
1430 mModel->setHelpContent( dialog.helpContent() );
1435void QgsModelDesignerDialog::runSelectedSteps()
1437 QSet<QString> children;
1438 const QList<QgsModelComponentGraphicItem *> items = mScene->selectedComponentItems();
1439 for ( QgsModelComponentGraphicItem *item : items )
1441 if ( QgsProcessingModelChildAlgorithm *childAlgorithm =
dynamic_cast<QgsProcessingModelChildAlgorithm *
>( item->component() ) )
1443 children.insert( childAlgorithm->childId() );
1447 if ( children.isEmpty() )
1449 mMessageBar->pushWarning( QString(), tr(
"No steps are selected" ) );
1456void QgsModelDesignerDialog::runFromChild(
const QString &
id )
1458 QSet<QString> children = mModel->dependentChildAlgorithms(
id );
1459 children.insert(
id );
1463void QgsModelDesignerDialog::cancelRunningModel()
1465 if ( !mAlgorithmWidget )
1471 mAlgorithmWidget->cancel();
1473 mAlgorithmWidget->forceClose();
1476 if ( mAlgorithmWidget )
1482 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1484 mAlgorithmWidget.clear();
1487void QgsModelDesignerDialog::run(
const QSet<QString> &childAlgorithmSubset )
1490 const bool isValid = model()->validate( errors );
1493 QMessageBox messageBox;
1494 messageBox.setWindowTitle( tr(
"Model is Invalid" ) );
1495 messageBox.setIcon( QMessageBox::Icon::Warning );
1496 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?" ) );
1497 messageBox.setStandardButtons( QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel );
1498 messageBox.setDefaultButton( QMessageBox::StandardButton::Cancel );
1500 QString errorString;
1501 for (
const QString &error : std::as_const( errors ) )
1503 QString cleanedError = error;
1504 const thread_local QRegularExpression re( u
"<[^>]*>"_s );
1505 cleanedError.replace( re, QString() );
1506 errorString += u
"• %1\n"_s.arg( cleanedError );
1509 messageBox.setDetailedText( errorString );
1510 if ( messageBox.exec() == QMessageBox::StandardButton::Cancel )
1514 if ( !childAlgorithmSubset.isEmpty() )
1516 for (
const QString &child : childAlgorithmSubset )
1519 const QSet<QString> requirements = mModel->dependsOnChildAlgorithms( child );
1520 for (
const QString &requirement : requirements )
1522 if ( !mLastResult.executedChildIds().contains( requirement ) )
1524 QMessageBox messageBox;
1525 messageBox.setWindowTitle( tr(
"Run Model" ) );
1526 messageBox.setIcon( QMessageBox::Icon::Warning );
1527 messageBox.setText( tr(
"Prerequisite parts of this model have not yet been run (try running the full model first)." ) );
1528 messageBox.setStandardButtons( QMessageBox::StandardButton::Ok );
1536 if ( mAlgorithmWidget && mAlgorithmWidget->isRunning() )
1538 QMessageBox messageBox;
1539 messageBox.setIcon( QMessageBox::Icon::Warning );
1540 messageBox.setWindowTitle( tr(
"Run Model" ) );
1541 messageBox.setText( tr(
"This model is already running." ) );
1542 messageBox.setStandardButtons( QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::RestoreDefaults | QMessageBox::StandardButton::Ok );
1544 QAbstractButton *buttonShowRunningAlg = messageBox.button( QMessageBox::StandardButton::Ok );
1545 buttonShowRunningAlg->setText( tr(
"Show Progress" ) );
1547 QAbstractButton *buttonReRun = messageBox.button( QMessageBox::StandardButton::RestoreDefaults );
1548 buttonReRun->setText( tr(
"Cancel and Restart Model" ) );
1550 int r = messageBox.exec();
1554 case QMessageBox::StandardButton::Cancel:
1556 case QMessageBox::StandardButton::RestoreDefaults:
1557 cancelRunningModel();
1559 case QMessageBox::StandardButton::Ok:
1560 mAlgorithmWidget->showWidget();
1566 else if ( mAlgorithmWidget )
1569 mAlgorithmWidget->close();
1570 if ( mAlgorithmWidget )
1576 mAlgorithmWidgetsToCleanUp << mAlgorithmWidget;
1579 mAlgorithmWidget.clear();
1582 if ( !mAlgorithmWidget )
1584 mAlgorithmWidget = createExecutionWidget();
1585 if ( !mAlgorithmWidget )
1588 mAlgorithmWidget->hideShortHelp();
1589 mAlgorithmWidget->setTitle( tr(
"Run Model" ) );
1592 mAlgorithmWidget->setParameters( mModel->designerParameterValues() );
1594 if ( !childAlgorithmSubset.isEmpty() )
1596 mAlgorithmWidget->runButton()->setText( tr(
"Run Subset" ) );
1597 mAlgorithmWidget->runButton()->setToolTip( tr(
"Runs a subset of the child algorithms from this model" ) );
1600 connect( mAlgorithmWidget.get(), &QgsProcessingAlgorithmWidgetBase::algorithmAboutToRun,
this, [
this, childAlgorithmSubset](
QgsProcessingContext *context ) {
1601 if ( !childAlgorithmSubset.empty() )
1604 auto modelConfig = std::make_unique<QgsProcessingModelInitialRunConfig>();
1605 modelConfig->setChildAlgorithmSubset( childAlgorithmSubset );
1606 modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() );
1607 modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() );
1608 modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() );
1612 const QMap<QString, QgsMapLayer *> previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers();
1613 auto previousResultStore = std::make_unique<QgsMapLayerStore>();
1614 for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it )
1616 std::unique_ptr<QgsMapLayer> clone( it.value()->clone() );
1617 clone->setId( it.value()->id() );
1618 previousResultStore->addMapLayer( clone.release() );
1620 previousResultStore->moveToThread( nullptr );
1621 modelConfig->setPreviousLayerStore( std::move( previousResultStore ) );
1622 context->setModelInitialRunConfig( std::move( modelConfig ) );
1624 mScene->resetChildAlgorithmItems( childAlgorithmSubset );
1627 for ( const QString &child : childAlgorithmSubset )
1629 const QSet< QString > outdated = mModel->dependentChildAlgorithms( child );
1630 mScene->flagChildrenAsOutdated( outdated );
1631 mOutdatedChildResults.unite( outdated );
1637 mScene->resetChildAlgorithmItems();
1641 connect( mAlgorithmWidget, &QgsProcessingAlgorithmWidgetBase::algorithmFinished,
this, [
this](
bool,
const QVariantMap & ) {
1644 mLayerStore.temporaryLayerStore()->removeAllMapLayers();
1645 mLayerStore.takeResultsFrom( *context );
1653void QgsModelDesignerDialog::showChildAlgorithmOutputs(
const QString &childId )
1655 const bool isOutdated = mOutdatedChildResults.contains( childId );
1656 const QString childDescription = mModel->childAlgorithm( childId ).description();
1659 const QVariantMap childAlgorithmOutputs = result.
outputs();
1660 if ( childAlgorithmOutputs.isEmpty() )
1662 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1669 mMessageBar->pushCritical( QString(), tr(
"Results cannot be shown for an invalid model component" ) );
1673 const QList<const QgsProcessingParameterDefinition *> outputParams =
algorithm->destinationParameterDefinitions();
1674 if ( outputParams.isEmpty() )
1677 QgsDebugError(
"Cannot show results for algorithms with no outputs" );
1681 bool foundResults =
false;
1684 const QVariant output = childAlgorithmOutputs.value( outputParam->name() );
1685 if ( !output.isValid() )
1688 if ( output.type() == QVariant::String )
1692 QgsDebugMsgLevel( u
"Loading previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ), 2 );
1694 std::unique_ptr<QgsMapLayer> layer( resultLayer->clone() );
1697 if ( outputParams.size() > 1 )
1698 baseName = tr(
"%1 — %2" ).arg( childDescription, outputParam->name() );
1700 baseName = childDescription;
1704 QString name = baseName;
1709 name = tr(
"%1 (%2)" ).arg( baseName ).arg( counter );
1712 layer->setName( name );
1715 foundResults =
true;
1720 QgsDebugError( u
"Could not load previous result for %1: %2"_s.arg( outputParam->name(), output.toString() ) );
1725 if ( !foundResults )
1727 mMessageBar->pushWarning( QString(), tr(
"No results are available for %1" ).arg( childDescription ) );
1730 else if ( isOutdated )
1732 mMessageBar->pushWarning( QString(), tr(
"These results are outdated, and may not reflect the most recent model execution" ) );
1737void QgsModelDesignerDialog::showChildAlgorithmLog(
const QString &childId )
1739 const QString childDescription = mModel->childAlgorithm( childId ).description();
1744 if ( QgsModelChildAlgorithmGraphicItem *item = mScene->childAlgorithmItem( childId ) )
1746 result = item->results();
1748 if ( result.
htmlLog().isEmpty() )
1750 result = mLastResult.childResults().value( childId );
1753 if ( result.
htmlLog().isEmpty() )
1755 mMessageBar->pushWarning( QString(), tr(
"No log is available for %1" ).arg( childDescription ) );
1760 m.setWindowTitle( childDescription );
1761 m.setCheckBoxVisible(
false );
1762 m.setMessageAsHtml( result.
htmlLog() );
1766void QgsModelDesignerDialog::onItemFocused( QgsModelComponentGraphicItem *item )
1772 if ( !item || !item->component() )
1774 mConfigWidget->showComponentConfig(
nullptr, *mContext, widgetContext );
1778 mConfigWidget->showComponentConfig( item->component(), *mContext, widgetContext );
1780 if (
auto childAlgorithmItem = qobject_cast< QgsModelChildAlgorithmGraphicItem * >( item ) )
1782 connect( childAlgorithmItem, &QgsModelChildAlgorithmGraphicItem::rebuildConfigurationDockWidget, childAlgorithmItem, [
this] {
1786 mConfigWidget->showComponentConfig(
nullptr, *mContext, widgetContext );
1792void QgsModelDesignerDialog::validate()
1795 if ( model()->validate( issues ) )
1797 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
1802 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
1803 connect( detailsButton, &QPushButton::clicked, detailsButton, [detailsButton, issues] {
1805 dialog->
setTitle( tr(
"Model is Invalid" ) );
1807 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + u
"<ul>"_s;
1808 for (
const QString &issue : issues )
1810 longMessage += u
"<li>%1</li>"_s.arg( issue );
1812 longMessage +=
"</ul>"_L1;
1817 messageWidget->layout()->addWidget( detailsButton );
1818 mMessageBar->clearWidgets();
1823void QgsModelDesignerDialog::reorderInputs()
1825 QgsModelInputReorderDialog dlg(
this );
1826 dlg.setModel( mModel.get() );
1829 const QStringList inputOrder = dlg.inputOrder();
1830 beginUndoCommand( tr(
"Reorder Inputs" ) );
1831 mModel->setParameterOrder( inputOrder );
1836void QgsModelDesignerDialog::reorderOutputs()
1838 QgsModelOutputReorderDialog dlg(
this );
1839 dlg.setModel( mModel.get() );
1842 const QStringList outputOrder = dlg.outputOrder();
1843 beginUndoCommand( tr(
"Reorder Outputs" ) );
1844 mModel->setOutputOrder( outputOrder );
1845 mModel->setOutputGroup( dlg.outputGroup() );
1850bool QgsModelDesignerDialog::isDirty()
const
1852 return mHasChanged && mUndoStack->index() != -1;
1855void QgsModelDesignerDialog::fillInputsTree()
1858 auto parametersItem = std::make_unique<QTreeWidgetItem>();
1859 parametersItem->setText( 0, tr(
"Parameters" ) );
1862 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1869 auto paramItem = std::make_unique<QTreeWidgetItem>();
1870 paramItem->setText( 0, param->name() );
1871 paramItem->setData( 0, Qt::UserRole, param->id() );
1872 paramItem->setIcon( 0, icon );
1873 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1874 paramItem->setToolTip( 0, param->description() );
1875 parametersItem->addChild( paramItem.release() );
1878 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1879 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1887QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1890 , mChildId( childId )
1892 QHBoxLayout *hl =
new QHBoxLayout();
1893 hl->setContentsMargins( 0, 0, 0, 0 );
1895 mLineEdit =
new QLineEdit();
1896 mLineEdit->setEnabled(
false );
1897 hl->addWidget( mLineEdit, 1 );
1899 mToolButton =
new QToolButton();
1900 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1901 hl->addWidget( mToolButton );
1905 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1907 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1910void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1912 const bool hasChanged = value != mValue;
1915 updateSummaryText();
1922void QgsModelChildDependenciesWidget::showDialog()
1924 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1926 QVariantList availableOptions;
1927 for (
const QgsProcessingModelChildDependency &dep : available )
1928 availableOptions << QVariant::fromValue( dep );
1929 QVariantList selectedOptions;
1930 for (
const QgsProcessingModelChildDependency &dep : mValue )
1931 selectedOptions << QVariant::fromValue( dep );
1936 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1937 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1939 widget->setValueFormatter( [
this](
const QVariant &v ) -> QString {
1940 const QgsProcessingModelChildDependency dep = v.value<QgsProcessingModelChildDependency>();
1942 const QString description = mModel->childAlgorithm( dep.childId ).description();
1943 if ( dep.conditionalBranch.isEmpty() )
1946 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1949 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [
this, widget]() {
1950 QList<QgsProcessingModelChildDependency> res;
1951 for (
const QVariant &v : widget->selectedOptions() )
1953 res << v.value<QgsProcessingModelChildDependency>();
1962void QgsModelChildDependenciesWidget::updateSummaryText()
1964 mLineEdit->setText( tr(
"%n dependencies selected",
nullptr, mValue.count() ) );
@ 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.
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.