17 #include "qgssettings.h"
21 #include "qgsprocessingmodelalgorithm.h"
31 #include "processing/models/qgsprocessingmodelgroupbox.h"
39 #include <QDesktopWidget>
40 #include <QKeySequence>
41 #include <QFileDialog>
43 #include <QSvgGenerator>
44 #include <QToolButton>
45 #include <QCloseEvent>
46 #include <QMessageBox>
48 #include <QPushButton>
50 #include <QTextStream>
55 QgsModelerToolboxModel::QgsModelerToolboxModel( QObject *parent )
61 Qt::ItemFlags QgsModelerToolboxModel::flags(
const QModelIndex &index )
const
63 Qt::ItemFlags f = QgsProcessingToolboxProxyModel::flags( index );
64 const QModelIndex sourceIndex = mapToSource( index );
65 if ( toolboxModel()->isAlgorithm( sourceIndex ) )
67 f = f | Qt::ItemIsDragEnabled;
72 Qt::DropActions QgsModelerToolboxModel::supportedDragActions()
const
74 return Qt::CopyAction;
79 QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
80 : QMainWindow( parent, flags )
81 , mToolsActionGroup( new QActionGroup( this ) )
85 setAttribute( Qt::WA_DeleteOnClose );
86 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
87 setWindowFlags( Qt::WindowMinimizeButtonHint |
88 Qt::WindowMaximizeButtonHint |
89 Qt::WindowCloseButtonHint );
93 mModel = std::make_unique< QgsProcessingModelAlgorithm >();
96 mUndoStack =
new QUndoStack(
this );
97 connect( mUndoStack, &QUndoStack::indexChanged,
this, [ = ]
99 if ( mIgnoreUndoStackChanges )
102 mBlockUndoCommands++;
103 updateVariablesGui();
104 mGroupEdit->setText( mModel->group() );
105 mNameEdit->setText( mModel->displayName() );
106 mBlockUndoCommands--;
110 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
111 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
112 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
113 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
115 mAlgorithmsTree->header()->setVisible(
false );
116 mAlgorithmSearchEdit->setShowSearchIcon(
true );
117 mAlgorithmSearchEdit->setPlaceholderText( tr(
"Search…" ) );
118 connect( mAlgorithmSearchEdit, &QgsFilterLineEdit::textChanged, mAlgorithmsTree, &QgsProcessingToolboxTreeView::setFilterString );
120 mInputsTreeWidget->header()->setVisible(
false );
121 mInputsTreeWidget->setAlternatingRowColors(
true );
122 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
123 mInputsTreeWidget->setDropIndicatorShown(
true );
125 mNameEdit->setPlaceholderText( tr(
"Enter model name here" ) );
126 mGroupEdit->setPlaceholderText( tr(
"Enter group name here" ) );
129 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
130 mainLayout->insertWidget( 0, mMessageBar );
132 mView->setAcceptDrops(
true );
133 QgsSettings settings;
135 connect( mActionClose, &QAction::triggered,
this, &QWidget::close );
136 connect( mActionZoomIn, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomIn );
137 connect( mActionZoomOut, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomOut );
138 connect( mActionZoomActual, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomActual );
139 connect( mActionZoomToItems, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomFull );
140 connect( mActionExportImage, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToImage );
141 connect( mActionExportPdf, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToPdf );
142 connect( mActionExportSvg, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToSvg );
143 connect( mActionExportPython, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsPython );
144 connect( mActionSave, &QAction::triggered,
this, [ = ] { saveModel(
false ); } );
145 connect( mActionSaveAs, &QAction::triggered,
this, [ = ] { saveModel(
true ); } );
146 connect( mActionDeleteComponents, &QAction::triggered,
this, &QgsModelDesignerDialog::deleteSelected );
147 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
148 connect( mActionValidate, &QAction::triggered,
this, &QgsModelDesignerDialog::validate );
149 connect( mActionReorderInputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderInputs );
150 connect( mReorderInputsButton, &QPushButton::clicked,
this, &QgsModelDesignerDialog::reorderInputs );
152 mActionSnappingEnabled->setChecked( settings.value( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ),
false ).toBool() );
153 connect( mActionSnappingEnabled, &QAction::toggled,
this, [ = ](
bool enabled )
155 mView->snapper()->setSnapToGrid( enabled );
156 QgsSettings().setValue( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ), enabled );
158 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
160 connect( mActionSelectAll, &QAction::triggered,
this, [ = ]
165 QStringList docksTitle = settings.value( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), QStringList(), QgsSettings::App ).toStringList();
166 QStringList docksActive = settings.value( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), QStringList(), QgsSettings::App ).toStringList();
167 if ( !docksTitle.isEmpty() )
169 for (
const auto &title : docksTitle )
171 mPanelStatus.insert( title, PanelStatus(
true, docksActive.contains( title ) ) );
174 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
175 connect( mActionHidePanels, &QAction::toggled,
this, &QgsModelDesignerDialog::setPanelVisibility );
177 mUndoAction = mUndoStack->createUndoAction(
this );
179 mUndoAction->setShortcuts( QKeySequence::Undo );
180 mRedoAction = mUndoStack->createRedoAction(
this );
182 mRedoAction->setShortcuts( QKeySequence::Redo );
184 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
185 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
186 mMenuEdit->insertSeparator( mActionDeleteComponents );
187 mToolbar->insertAction( mActionZoomIn, mUndoAction );
188 mToolbar->insertAction( mActionZoomIn, mRedoAction );
189 mToolbar->insertSeparator( mActionZoomIn );
191 mGroupMenu =
new QMenu( tr(
"Zoom To" ),
this );
192 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
193 connect( mGroupMenu, &QMenu::aboutToShow,
this, &QgsModelDesignerDialog::populateZoomToMenu );
197 mActionCut =
new QAction( tr(
"Cu&t" ),
this );
198 mActionCut->setShortcuts( QKeySequence::Cut );
199 mActionCut->setStatusTip( tr(
"Cut" ) );
201 connect( mActionCut, &QAction::triggered,
this, [ = ]
203 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut );
206 mActionCopy =
new QAction( tr(
"&Copy" ),
this );
207 mActionCopy->setShortcuts( QKeySequence::Copy );
208 mActionCopy->setStatusTip( tr(
"Copy" ) );
210 connect( mActionCopy, &QAction::triggered,
this, [ = ]
212 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy );
215 mActionPaste =
new QAction( tr(
"&Paste" ),
this );
216 mActionPaste->setShortcuts( QKeySequence::Paste );
217 mActionPaste->setStatusTip( tr(
"Paste" ) );
219 connect( mActionPaste, &QAction::triggered,
this, [ = ]
221 mView->pasteItems( QgsModelGraphicsView::PasteModeCursor );
223 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
224 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
225 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
226 mMenuEdit->insertSeparator( mActionDeleteComponents );
229 if ( settings.value( QStringLiteral(
"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES" ),
false ).toBool() )
233 mAlgorithmsTree->setFilters( filters );
234 mAlgorithmsTree->setDragDropMode( QTreeWidget::DragOnly );
235 mAlgorithmsTree->setDropIndicatorShown(
true );
237 mAlgorithmsModel =
new QgsModelerToolboxModel(
this );
238 mAlgorithmsTree->setToolboxProxyModel( mAlgorithmsModel );
240 connect( mView, &QgsModelGraphicsView::algorithmDropped,
this, [ = ](
const QString & algorithmId,
const QPointF & pos )
242 addAlgorithm( algorithmId, pos );
244 connect( mAlgorithmsTree, &QgsProcessingToolboxTreeView::doubleClicked,
this, [ = ]()
246 if ( mAlgorithmsTree->selectedAlgorithm() )
247 addAlgorithm( mAlgorithmsTree->selectedAlgorithm()->id(), QPointF() );
249 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked,
this, [ = ](
const QModelIndex & )
251 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
252 addInput( parameterType, QPointF() );
255 connect( mView, &QgsModelGraphicsView::inputDropped,
this, &QgsModelDesignerDialog::addInput );
258 QShortcut *ctrlEquals =
new QShortcut( QKeySequence( QStringLiteral(
"Ctrl+=" ) ),
this );
259 connect( ctrlEquals, &QShortcut::activated,
this, &QgsModelDesignerDialog::zoomIn );
262 mUndoDock->setObjectName( QStringLiteral(
"UndoDock" ) );
263 mUndoView =
new QUndoView( mUndoStack,
this );
264 mUndoDock->setWidget( mUndoView );
265 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
266 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
268 tabifyDockWidget( mUndoDock, mPropertiesDock );
269 tabifyDockWidget( mVariablesDock, mPropertiesDock );
270 mPropertiesDock->raise();
271 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
272 mInputsDock->raise();
278 beginUndoCommand( tr(
"Change Model Variables" ) );
279 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
283 connect( mNameEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & name )
287 beginUndoCommand( tr(
"Change Model Name" ), NameChanged );
288 mModel->setName( name );
293 connect( mGroupEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & group )
297 beginUndoCommand( tr(
"Change Model Group" ), GroupChanged );
298 mModel->setGroup( group );
305 QToolButton *toolbuttonExportToScript =
new QToolButton();
306 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
307 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
308 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
309 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
310 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
312 mActionShowComments->setChecked( settings.value( QStringLiteral(
"/Processing/Modeler/ShowComments" ),
true ).toBool() );
313 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
316 mPanTool->setAction( mActionPan );
318 mToolsActionGroup->addAction( mActionPan );
319 connect( mActionPan, &QAction::triggered, mPanTool, [ = ] { mView->setTool( mPanTool ); } );
322 mSelectTool->setAction( mActionSelectMoveItem );
324 mToolsActionGroup->addAction( mActionSelectMoveItem );
325 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
327 mView->setTool( mSelectTool );
330 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [ = ](
const QString & text )
332 mIgnoreUndoStackChanges++;
333 mUndoStack->beginMacro( text );
334 mIgnoreUndoStackChanges--;
336 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [ = ]
338 mIgnoreUndoStackChanges++;
339 mUndoStack->endMacro();
340 mIgnoreUndoStackChanges--;
342 connect( mView, &QgsModelGraphicsView::beginCommand,
this, [ = ](
const QString & text )
344 beginUndoCommand( text );
346 connect( mView, &QgsModelGraphicsView::endCommand,
this, [ = ]
350 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [ = ]
355 connect( mActionAddGroupBox, &QAction::triggered,
this, [ = ]
357 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
358 QgsProcessingModelGroupBox group;
359 group.setPosition( viewCenter );
360 group.setDescription( tr(
"New Group" ) );
362 beginUndoCommand( tr(
"Add Group Box" ) );
363 model()->addGroupBox( group );
371 restoreState( settings.value( QStringLiteral(
"ModelDesigner/state" ), QByteArray(), QgsSettings::App ).toByteArray() );
374 QgsModelDesignerDialog::~QgsModelDesignerDialog()
376 QgsSettings settings;
377 if ( !mPanelStatus.isEmpty() )
379 QStringList docksTitle;
380 QStringList docksActive;
382 for (
const auto &panel : mPanelStatus.toStdMap() )
384 if ( panel.second.isVisible )
385 docksTitle << panel.first;
386 if ( panel.second.isActive )
387 docksActive << panel.first;
389 settings.setValue( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), docksTitle, QgsSettings::App );
390 settings.setValue( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), docksActive, QgsSettings::App );
394 settings.remove( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), QgsSettings::App );
395 settings.remove( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), QgsSettings::App );
399 settings.setValue( QStringLiteral(
"ModelDesigner/state" ), saveState(), QgsSettings::App );
401 mIgnoreUndoStackChanges++;
405 void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
407 if ( checkForUnsavedChanges() )
413 void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
int id )
415 if ( mBlockUndoCommands || !mUndoStack )
418 if ( mActiveCommand )
421 mActiveCommand = std::make_unique< QgsModelUndoCommand >( mModel.get(), text,
id );
424 void QgsModelDesignerDialog::endUndoCommand()
426 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
429 mActiveCommand->saveAfterState();
430 mIgnoreUndoStackChanges++;
431 mUndoStack->push( mActiveCommand.release() );
432 mIgnoreUndoStackChanges--;
436 QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
441 void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
443 mModel.reset( model );
445 mGroupEdit->setText( mModel->group() );
446 mNameEdit->setText( mModel->displayName() );
448 updateVariablesGui();
450 mView->centerOn( 0, 0 );
453 mIgnoreUndoStackChanges++;
455 mIgnoreUndoStackChanges--;
460 void QgsModelDesignerDialog::loadModel(
const QString &path )
462 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
463 if ( alg->fromFile( path ) )
466 setModel( alg.release() );
470 QgsMessageLog::logMessage( tr(
"Could not load model %1" ).arg( path ), tr(
"Processing" ), Qgis::MessageLevel::Critical );
471 QMessageBox::critical(
this, tr(
"Open Model" ), tr(
"The selected model could not be loaded.\n"
472 "See the log for more information." ) );
476 void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
478 QgsModelGraphicsScene *oldScene = mScene;
481 mScene->setParent(
this );
482 mScene->setChildAlgorithmResults( mChildResults );
483 mScene->setModel( mModel.get() );
484 mScene->setMessageBar( mMessageBar );
486 const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
487 mView->setModelScene( mScene );
489 mSelectTool->resetCache();
490 mSelectTool->setScene( mScene );
492 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [ = ]
494 if ( mBlockRepaints )
499 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [ = ](
const QString & description,
int id ) { beginUndoCommand( description,
id ); } );
500 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [ = ] { endUndoCommand(); } );
502 mView->centerOn( center );
505 oldScene->deleteLater();
508 void QgsModelDesignerDialog::updateVariablesGui()
510 mBlockUndoCommands++;
512 std::unique_ptr< QgsExpressionContextScope > variablesScope = std::make_unique< QgsExpressionContextScope >( tr(
"Model Variables" ) );
513 const QVariantMap modelVars = mModel->variables();
514 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
516 variablesScope->setVariable( it.key(), it.value() );
519 variablesContext.
appendScope( variablesScope.release() );
520 mVariablesEditor->setContext( &variablesContext );
521 mVariablesEditor->setEditableScopeIndex( 0 );
523 mBlockUndoCommands--;
526 void QgsModelDesignerDialog::setDirty(
bool dirty )
532 bool QgsModelDesignerDialog::validateSave()
534 if ( mNameEdit->text().trimmed().isEmpty() )
536 mMessageBar->pushWarning( QString(), tr(
"Please a enter model name before saving" ) );
543 bool QgsModelDesignerDialog::checkForUnsavedChanges()
547 QMessageBox::StandardButton ret = QMessageBox::question(
this, tr(
"Save Model?" ),
548 tr(
"There are unsaved changes in this model. Do you want to keep those?" ),
549 QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, QMessageBox::Cancel );
552 case QMessageBox::Save:
556 case QMessageBox::Discard:
569 void QgsModelDesignerDialog::setLastRunChildAlgorithmResults(
const QVariantMap &results )
571 mChildResults = results;
573 mScene->setChildAlgorithmResults( mChildResults );
576 void QgsModelDesignerDialog::setLastRunChildAlgorithmInputs(
const QVariantMap &inputs )
578 mChildInputs = inputs;
580 mScene->setChildAlgorithmInputs( mChildInputs );
583 void QgsModelDesignerDialog::zoomIn()
585 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
586 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
587 QgsSettings settings;
588 const double factor = settings.value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
589 mView->scale( factor, factor );
590 mView->centerOn( point );
593 void QgsModelDesignerDialog::zoomOut()
595 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
596 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
597 QgsSettings settings;
598 const double factor = 1.0 / settings.value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
599 mView->scale( factor, factor );
600 mView->centerOn( point );
603 void QgsModelDesignerDialog::zoomActual()
605 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
606 mView->resetTransform();
607 mView->scale( QgsApplication::desktop()->logicalDpiX() / 96, QgsApplication::desktop()->logicalDpiX() / 96 );
608 mView->centerOn( point );
611 void QgsModelDesignerDialog::zoomFull()
613 QRectF totalRect = mView->scene()->itemsBoundingRect();
614 totalRect.adjust( -10, -10, 10, 10 );
615 mView->fitInView( totalRect, Qt::KeepAspectRatio );
618 void QgsModelDesignerDialog::exportToImage()
620 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ), tr(
"PNG files (*.png *.PNG)" ) );
621 if ( filename.isEmpty() )
626 repaintModel(
false );
628 QRectF totalRect = mView->scene()->itemsBoundingRect();
629 totalRect.adjust( -10, -10, 10, 10 );
630 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
632 QImage img( totalRect.width(), totalRect.height(),
633 QImage::Format_ARGB32_Premultiplied );
634 img.fill( Qt::white );
636 painter.setRenderHint( QPainter::Antialiasing );
637 painter.begin( &img );
638 mView->scene()->render( &painter, imageRect, totalRect );
641 img.save( filename );
643 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as image to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
644 repaintModel(
true );
647 void QgsModelDesignerDialog::exportToPdf()
649 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ), tr(
"PDF files (*.pdf *.PDF)" ) );
650 if ( filename.isEmpty() )
655 repaintModel(
false );
657 QRectF totalRect = mView->scene()->itemsBoundingRect();
658 totalRect.adjust( -10, -10, 10, 10 );
659 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
662 printer.setOutputFormat( QPrinter::PdfFormat );
663 printer.setOutputFileName( filename );
664 printer.setPaperSize( QSizeF( printerRect.width(), printerRect.height() ), QPrinter::DevicePixel );
665 printer.setFullPage(
true );
667 QPainter painter( &printer );
668 mView->scene()->render( &painter, printerRect, totalRect );
671 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as PDF to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
672 repaintModel(
true );
675 void QgsModelDesignerDialog::exportToSvg()
677 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ), tr(
"SVG files (*.svg *.SVG)" ) );
678 if ( filename.isEmpty() )
683 repaintModel(
false );
685 QRectF totalRect = mView->scene()->itemsBoundingRect();
686 totalRect.adjust( -10, -10, 10, 10 );
687 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
690 svg.setFileName( filename );
691 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
692 svg.setViewBox( svgRect );
693 svg.setTitle( mModel->displayName() );
695 QPainter painter( &svg );
696 mView->scene()->render( &painter, svgRect, totalRect );
699 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as SVG to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
700 repaintModel(
true );
703 void QgsModelDesignerDialog::exportAsPython()
705 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ), tr(
"Processing scripts (*.py *.PY)" ) );
706 if ( filename.isEmpty() )
713 QFile outFile( filename );
714 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
718 QTextStream fout( &outFile );
722 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as Python script to <a href=\"{}\">{}</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
725 void QgsModelDesignerDialog::toggleComments(
bool show )
727 QgsSettings().setValue( QStringLiteral(
"/Processing/Modeler/ShowComments" ), show );
729 repaintModel(
true );
732 void QgsModelDesignerDialog::updateWindowTitle()
734 QString title = tr(
"Model Designer" );
735 if ( !mModel->name().isEmpty() )
736 title = QStringLiteral(
"%1 - %2" ).arg( title, mModel->name() );
739 title.prepend(
'*' );
741 setWindowTitle( title );
744 void QgsModelDesignerDialog::deleteSelected()
746 QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
750 if ( items.size() == 1 )
752 items.at( 0 )->deleteComponent();
756 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem * p1, QgsModelComponentGraphicItem * p2 )
759 if ( dynamic_cast< QgsModelCommentGraphicItem *>( p1 ) )
761 else if ( dynamic_cast< QgsModelCommentGraphicItem *>( p2 ) )
763 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p1 ) )
765 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p2 ) )
767 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p1 ) )
769 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p2 ) )
771 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p1 ) )
773 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p2 ) )
779 beginUndoCommand( tr(
"Delete Components" ) );
781 QVariant prevState = mModel->toVariant();
782 mBlockUndoCommands++;
783 mBlockRepaints =
true;
785 while ( !items.empty() )
787 QgsModelComponentGraphicItem *toDelete =
nullptr;
788 for ( QgsModelComponentGraphicItem *item : items )
790 if ( item->canDeleteComponent() )
803 toDelete->deleteComponent();
804 items.removeAll( toDelete );
809 mModel->loadVariant( prevState );
810 QMessageBox::warning(
nullptr, QObject::tr(
"Could not remove components" ),
811 QObject::tr(
"Components depend on the selected items.\n"
812 "Try to remove them before trying deleting these components." ) );
813 mBlockUndoCommands--;
814 mActiveCommand.reset();
818 mBlockUndoCommands--;
822 mBlockRepaints =
false;
826 void QgsModelDesignerDialog::populateZoomToMenu()
829 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
831 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
833 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
834 connect( zoomAction, &QAction::triggered,
this, [ = ]
836 mView->centerOn( item );
838 mGroupMenu->addAction( zoomAction );
843 void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
845 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
846 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
850 mPanelStatus.clear();
852 for ( QDockWidget *dock : docks )
854 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
855 dock->setVisible(
false );
859 for ( QTabBar *tabBar : tabBars )
861 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
862 mPanelStatus[ currentTabTitle ].isActive =
true;
868 for ( QDockWidget *dock : docks )
870 if ( mPanelStatus.contains( dock->windowTitle() ) )
872 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
877 for ( QTabBar *tabBar : tabBars )
880 for (
int i = 0; i < tabBar->count(); ++i )
882 QString tabTitle = tabBar->tabText( i );
883 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
885 tabBar->setCurrentIndex( i );
889 mPanelStatus.clear();
893 void QgsModelDesignerDialog::validate()
896 if ( model()->validate( issues ) )
898 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
902 QgsMessageBarItem *messageWidget = mMessageBar->createMessage( QString(), tr(
"Model is invalid!" ) );
903 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
904 connect( detailsButton, &QPushButton::clicked, detailsButton, [ = ]
907 dialog->
setTitle( tr(
"Model is Invalid" ) );
909 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + QStringLiteral(
"<ul>" );
910 for (
const QString &issue : issues )
912 longMessage += QStringLiteral(
"<li>%1</li>" ).arg( issue );
914 longMessage += QLatin1String(
"</ul>" );
919 messageWidget->layout()->addWidget( detailsButton );
920 mMessageBar->clearWidgets();
921 mMessageBar->pushWidget( messageWidget, Qgis::MessageLevel::Warning, 0 );
925 void QgsModelDesignerDialog::reorderInputs()
927 QgsModelInputReorderDialog dlg(
this );
928 dlg.setModel( mModel.get() );
931 const QStringList inputOrder = dlg.inputOrder();
932 beginUndoCommand( tr(
"Reorder Inputs" ) );
933 mModel->setParameterOrder( inputOrder );
938 bool QgsModelDesignerDialog::isDirty()
const
940 return mHasChanged && mUndoStack->index() != -1;
943 void QgsModelDesignerDialog::fillInputsTree()
946 std::unique_ptr< QTreeWidgetItem > parametersItem = std::make_unique< QTreeWidgetItem >();
947 parametersItem->setText( 0, tr(
"Parameters" ) );
951 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
958 std::unique_ptr< QTreeWidgetItem > paramItem = std::make_unique< QTreeWidgetItem >();
959 paramItem->setText( 0, param->name() );
960 paramItem->setData( 0, Qt::UserRole, param->id() );
961 paramItem->setIcon( 0, icon );
962 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
963 paramItem->setToolTip( 0, param->description() );
964 parametersItem->addChild( paramItem.release() );
967 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
968 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
976 QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
979 , mChildId( childId )
981 QHBoxLayout *hl =
new QHBoxLayout();
982 hl->setContentsMargins( 0, 0, 0, 0 );
984 mLineEdit =
new QLineEdit();
985 mLineEdit->setEnabled(
false );
986 hl->addWidget( mLineEdit, 1 );
988 mToolButton =
new QToolButton();
989 mToolButton->setText( QString( QChar( 0x2026 ) ) );
990 hl->addWidget( mToolButton );
994 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
996 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
999 void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1003 updateSummaryText();
1006 void QgsModelChildDependenciesWidget::showDialog()
1008 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1010 QVariantList availableOptions;
1011 for (
const QgsProcessingModelChildDependency &dep : available )
1012 availableOptions << QVariant::fromValue( dep );
1013 QVariantList selectedOptions;
1014 for (
const QgsProcessingModelChildDependency &dep : mValue )
1015 selectedOptions << QVariant::fromValue( dep );
1020 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1021 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1023 widget->setValueFormatter( [ = ](
const QVariant & v ) -> QString
1025 const QgsProcessingModelChildDependency dep = v.value< QgsProcessingModelChildDependency >();
1027 const QString description = mModel->childAlgorithm( dep.childId ).description();
1028 if ( dep.conditionalBranch.isEmpty() )
1031 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1034 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [ = ]()
1036 QList< QgsProcessingModelChildDependency > res;
1037 for (
const QVariant &v : widget->selectedOptions() )
1039 res << v.value< QgsProcessingModelChildDependency >();
1048 void QgsModelChildDependenciesWidget::updateSummaryText()
1050 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( mValue.count() ) );
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.
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...
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
A generic message view for displaying QGIS messages.
void setTitle(const QString &title) override
Sets title for the messages.
void setMessage(const QString &message, MessageType msgType) override
Sets message, it won't be displayed until.
void showMessage(bool blocking=true) override
display the message to the user and deletes itself
Makes metadata of processing parameters available.
@ ExposeToModeler
Is this parameter available in the modeler. Is set to on by default.
QList< QgsProcessingParameterType * > parameterTypes() const
Returns a list with all known parameter types.
@ PythonQgsProcessingAlgorithmSubclass
Full Python QgsProcessingAlgorithm subclass.