40 #include <QDesktopWidget>
41 #include <QKeySequence>
42 #include <QFileDialog>
44 #include <QSvgGenerator>
45 #include <QToolButton>
46 #include <QCloseEvent>
47 #include <QMessageBox>
49 #include <QPushButton>
51 #include <QTextStream>
52 #include <QActionGroup>
57 QgsModelerToolboxModel::QgsModelerToolboxModel( QObject *parent )
63 Qt::ItemFlags QgsModelerToolboxModel::flags(
const QModelIndex &index )
const
65 Qt::ItemFlags f = QgsProcessingToolboxProxyModel::flags( index );
66 const QModelIndex sourceIndex = mapToSource( index );
67 if ( toolboxModel()->isAlgorithm( sourceIndex ) )
69 f = f | Qt::ItemIsDragEnabled;
74 Qt::DropActions QgsModelerToolboxModel::supportedDragActions()
const
76 return Qt::CopyAction;
81 QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
82 : QMainWindow( parent, flags )
83 , mToolsActionGroup( new QActionGroup( this ) )
87 setAttribute( Qt::WA_DeleteOnClose );
88 setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
89 setWindowFlags( Qt::WindowMinimizeButtonHint |
90 Qt::WindowMaximizeButtonHint |
91 Qt::WindowCloseButtonHint );
95 mModel = std::make_unique< QgsProcessingModelAlgorithm >();
98 mUndoStack =
new QUndoStack(
this );
99 connect( mUndoStack, &QUndoStack::indexChanged,
this, [ = ]
101 if ( mIgnoreUndoStackChanges )
104 mBlockUndoCommands++;
105 updateVariablesGui();
106 mGroupEdit->setText( mModel->group() );
107 mNameEdit->setText( mModel->displayName() );
108 mBlockUndoCommands--;
112 mPropertiesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
113 mInputsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
114 mAlgorithmsDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable );
115 mVariablesDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
117 mAlgorithmsTree->header()->setVisible(
false );
118 mAlgorithmSearchEdit->setShowSearchIcon(
true );
119 mAlgorithmSearchEdit->setPlaceholderText( tr(
"Search…" ) );
120 connect( mAlgorithmSearchEdit, &QgsFilterLineEdit::textChanged, mAlgorithmsTree, &QgsProcessingToolboxTreeView::setFilterString );
122 mInputsTreeWidget->header()->setVisible(
false );
123 mInputsTreeWidget->setAlternatingRowColors(
true );
124 mInputsTreeWidget->setDragDropMode( QTreeWidget::DragOnly );
125 mInputsTreeWidget->setDropIndicatorShown(
true );
127 mNameEdit->setPlaceholderText( tr(
"Enter model name here" ) );
128 mGroupEdit->setPlaceholderText( tr(
"Enter group name here" ) );
131 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
132 mainLayout->insertWidget( 0, mMessageBar );
134 mView->setAcceptDrops(
true );
137 connect( mActionClose, &QAction::triggered,
this, &QWidget::close );
138 connect( mActionNew, &QAction::triggered,
this, &QgsModelDesignerDialog::newModel );
139 connect( mActionZoomIn, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomIn );
140 connect( mActionZoomOut, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomOut );
141 connect( mActionZoomActual, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomActual );
142 connect( mActionZoomToItems, &QAction::triggered,
this, &QgsModelDesignerDialog::zoomFull );
143 connect( mActionExportImage, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToImage );
144 connect( mActionExportPdf, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToPdf );
145 connect( mActionExportSvg, &QAction::triggered,
this, &QgsModelDesignerDialog::exportToSvg );
146 connect( mActionExportPython, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsPython );
147 connect( mActionSave, &QAction::triggered,
this, [ = ] { saveModel(
false ); } );
148 connect( mActionSaveAs, &QAction::triggered,
this, [ = ] { saveModel(
true ); } );
149 connect( mActionDeleteComponents, &QAction::triggered,
this, &QgsModelDesignerDialog::deleteSelected );
150 connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
151 connect( mActionValidate, &QAction::triggered,
this, &QgsModelDesignerDialog::validate );
152 connect( mActionReorderInputs, &QAction::triggered,
this, &QgsModelDesignerDialog::reorderInputs );
153 connect( mActionEditHelp, &QAction::triggered,
this, &QgsModelDesignerDialog::editHelp );
154 connect( mReorderInputsButton, &QPushButton::clicked,
this, &QgsModelDesignerDialog::reorderInputs );
156 mActionSnappingEnabled->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ),
false ).toBool() );
157 connect( mActionSnappingEnabled, &QAction::toggled,
this, [ = ](
bool enabled )
159 mView->snapper()->setSnapToGrid( enabled );
160 QgsSettings().
setValue( QStringLiteral(
"/Processing/Modeler/enableSnapToGrid" ), enabled );
162 mView->snapper()->setSnapToGrid( mActionSnappingEnabled->isChecked() );
164 connect( mActionSelectAll, &QAction::triggered,
this, [ = ]
169 QStringList docksTitle = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksTitle" ), QStringList(),
QgsSettings::App ).toStringList();
170 QStringList docksActive = settings.
value( QStringLiteral(
"ModelDesigner/hiddenDocksActive" ), QStringList(),
QgsSettings::App ).toStringList();
171 if ( !docksTitle.isEmpty() )
173 for (
const auto &title : docksTitle )
175 mPanelStatus.insert( title, PanelStatus(
true, docksActive.contains( title ) ) );
178 mActionHidePanels->setChecked( !docksTitle.isEmpty() );
179 connect( mActionHidePanels, &QAction::toggled,
this, &QgsModelDesignerDialog::setPanelVisibility );
181 mUndoAction = mUndoStack->createUndoAction(
this );
183 mUndoAction->setShortcuts( QKeySequence::Undo );
184 mRedoAction = mUndoStack->createRedoAction(
this );
186 mRedoAction->setShortcuts( QKeySequence::Redo );
188 mMenuEdit->insertAction( mActionDeleteComponents, mRedoAction );
189 mMenuEdit->insertAction( mActionDeleteComponents, mUndoAction );
190 mMenuEdit->insertSeparator( mActionDeleteComponents );
191 mToolbar->insertAction( mActionZoomIn, mUndoAction );
192 mToolbar->insertAction( mActionZoomIn, mRedoAction );
193 mToolbar->insertSeparator( mActionZoomIn );
195 mGroupMenu =
new QMenu( tr(
"Zoom To" ),
this );
196 mMenuView->insertMenu( mActionZoomIn, mGroupMenu );
197 connect( mGroupMenu, &QMenu::aboutToShow,
this, &QgsModelDesignerDialog::populateZoomToMenu );
201 mActionCut =
new QAction( tr(
"Cu&t" ),
this );
202 mActionCut->setShortcuts( QKeySequence::Cut );
203 mActionCut->setStatusTip( tr(
"Cut" ) );
205 connect( mActionCut, &QAction::triggered,
this, [ = ]
207 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCut );
210 mActionCopy =
new QAction( tr(
"&Copy" ),
this );
211 mActionCopy->setShortcuts( QKeySequence::Copy );
212 mActionCopy->setStatusTip( tr(
"Copy" ) );
214 connect( mActionCopy, &QAction::triggered,
this, [ = ]
216 mView->copySelectedItems( QgsModelGraphicsView::ClipboardCopy );
219 mActionPaste =
new QAction( tr(
"&Paste" ),
this );
220 mActionPaste->setShortcuts( QKeySequence::Paste );
221 mActionPaste->setStatusTip( tr(
"Paste" ) );
223 connect( mActionPaste, &QAction::triggered,
this, [ = ]
225 mView->pasteItems( QgsModelGraphicsView::PasteModeCursor );
227 mMenuEdit->insertAction( mActionDeleteComponents, mActionCut );
228 mMenuEdit->insertAction( mActionDeleteComponents, mActionCopy );
229 mMenuEdit->insertAction( mActionDeleteComponents, mActionPaste );
230 mMenuEdit->insertSeparator( mActionDeleteComponents );
232 mAlgorithmsModel =
new QgsModelerToolboxModel(
this );
233 mAlgorithmsTree->setToolboxProxyModel( mAlgorithmsModel );
236 if ( settings.
value( QStringLiteral(
"Processing/Configuration/SHOW_ALGORITHMS_KNOWN_ISSUES" ),
false ).toBool() )
240 mAlgorithmsTree->setFilters( filters );
241 mAlgorithmsTree->setDragDropMode( QTreeWidget::DragOnly );
242 mAlgorithmsTree->setDropIndicatorShown(
true );
244 connect( mView, &QgsModelGraphicsView::algorithmDropped,
this, [ = ](
const QString & algorithmId,
const QPointF & pos )
246 addAlgorithm( algorithmId, pos );
248 connect( mAlgorithmsTree, &QgsProcessingToolboxTreeView::doubleClicked,
this, [ = ]()
250 if ( mAlgorithmsTree->selectedAlgorithm() )
251 addAlgorithm( mAlgorithmsTree->selectedAlgorithm()->id(), QPointF() );
253 connect( mInputsTreeWidget, &QgsModelDesignerInputsTreeWidget::doubleClicked,
this, [ = ](
const QModelIndex & )
255 const QString parameterType = mInputsTreeWidget->currentItem()->data( 0, Qt::UserRole ).toString();
256 addInput( parameterType, QPointF() );
259 connect( mView, &QgsModelGraphicsView::inputDropped,
this, &QgsModelDesignerDialog::addInput );
262 QShortcut *ctrlEquals =
new QShortcut( QKeySequence( QStringLiteral(
"Ctrl+=" ) ),
this );
263 connect( ctrlEquals, &QShortcut::activated,
this, &QgsModelDesignerDialog::zoomIn );
266 mUndoDock->setObjectName( QStringLiteral(
"UndoDock" ) );
267 mUndoView =
new QUndoView( mUndoStack,
this );
268 mUndoDock->setWidget( mUndoView );
269 mUndoDock->setFeatures( QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
270 addDockWidget( Qt::DockWidgetArea::LeftDockWidgetArea, mUndoDock );
272 tabifyDockWidget( mUndoDock, mPropertiesDock );
273 tabifyDockWidget( mVariablesDock, mPropertiesDock );
274 mPropertiesDock->raise();
275 tabifyDockWidget( mInputsDock, mAlgorithmsDock );
276 mInputsDock->raise();
282 beginUndoCommand( tr(
"Change Model Variables" ) );
283 mModel->setVariables( mVariablesEditor->variablesInActiveScope() );
287 connect( mNameEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & name )
291 beginUndoCommand( tr(
"Change Model Name" ), NameChanged );
292 mModel->setName( name );
297 connect( mGroupEdit, &QLineEdit::textChanged,
this, [ = ](
const QString & group )
301 beginUndoCommand( tr(
"Change Model Group" ), GroupChanged );
302 mModel->setGroup( group );
309 QToolButton *toolbuttonExportToScript =
new QToolButton();
310 toolbuttonExportToScript->setPopupMode( QToolButton::InstantPopup );
311 toolbuttonExportToScript->addAction( mActionExportAsScriptAlgorithm );
312 toolbuttonExportToScript->setDefaultAction( mActionExportAsScriptAlgorithm );
313 mToolbar->insertWidget( mActionExportImage, toolbuttonExportToScript );
314 connect( mActionExportAsScriptAlgorithm, &QAction::triggered,
this, &QgsModelDesignerDialog::exportAsScriptAlgorithm );
316 mActionShowComments->setChecked( settings.
value( QStringLiteral(
"/Processing/Modeler/ShowComments" ),
true ).toBool() );
317 connect( mActionShowComments, &QAction::toggled,
this, &QgsModelDesignerDialog::toggleComments );
320 mPanTool->setAction( mActionPan );
322 mToolsActionGroup->addAction( mActionPan );
323 connect( mActionPan, &QAction::triggered, mPanTool, [ = ] { mView->setTool( mPanTool ); } );
326 mSelectTool->setAction( mActionSelectMoveItem );
328 mToolsActionGroup->addAction( mActionSelectMoveItem );
329 connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
331 mView->setTool( mSelectTool );
334 connect( mView, &QgsModelGraphicsView::macroCommandStarted,
this, [ = ](
const QString & text )
336 mIgnoreUndoStackChanges++;
337 mUndoStack->beginMacro( text );
338 mIgnoreUndoStackChanges--;
340 connect( mView, &QgsModelGraphicsView::macroCommandEnded,
this, [ = ]
342 mIgnoreUndoStackChanges++;
343 mUndoStack->endMacro();
344 mIgnoreUndoStackChanges--;
346 connect( mView, &QgsModelGraphicsView::beginCommand,
this, [ = ](
const QString & text )
348 beginUndoCommand( text );
350 connect( mView, &QgsModelGraphicsView::endCommand,
this, [ = ]
354 connect( mView, &QgsModelGraphicsView::deleteSelectedItems,
this, [ = ]
359 connect( mActionAddGroupBox, &QAction::triggered,
this, [ = ]
361 const QPointF viewCenter = mView->mapToScene( mView->viewport()->rect().center() );
362 QgsProcessingModelGroupBox group;
363 group.setPosition( viewCenter );
364 group.setDescription( tr(
"New Group" ) );
366 beginUndoCommand( tr(
"Add Group Box" ) );
367 model()->addGroupBox( group );
375 restoreState( settings.
value( QStringLiteral(
"ModelDesigner/state" ), QByteArray(),
QgsSettings::App ).toByteArray() );
378 QgsModelDesignerDialog::~QgsModelDesignerDialog()
381 if ( !mPanelStatus.isEmpty() )
383 QStringList docksTitle;
384 QStringList docksActive;
386 for (
const auto &panel : mPanelStatus.toStdMap() )
388 if ( panel.second.isVisible )
389 docksTitle << panel.first;
390 if ( panel.second.isActive )
391 docksActive << panel.first;
405 mIgnoreUndoStackChanges++;
409 void QgsModelDesignerDialog::closeEvent( QCloseEvent *event )
411 if ( checkForUnsavedChanges() )
417 void QgsModelDesignerDialog::beginUndoCommand(
const QString &text,
int id )
419 if ( mBlockUndoCommands || !mUndoStack )
422 if ( mActiveCommand )
425 mActiveCommand = std::make_unique< QgsModelUndoCommand >( mModel.get(), text,
id );
428 void QgsModelDesignerDialog::endUndoCommand()
430 if ( mBlockUndoCommands || !mActiveCommand || !mUndoStack )
433 mActiveCommand->saveAfterState();
434 mIgnoreUndoStackChanges++;
435 mUndoStack->push( mActiveCommand.release() );
436 mIgnoreUndoStackChanges--;
440 QgsProcessingModelAlgorithm *QgsModelDesignerDialog::model()
445 void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
447 mModel.reset( model );
449 mGroupEdit->setText( mModel->group() );
450 mNameEdit->setText( mModel->displayName() );
452 updateVariablesGui();
454 mView->centerOn( 0, 0 );
457 mIgnoreUndoStackChanges++;
459 mIgnoreUndoStackChanges--;
464 void QgsModelDesignerDialog::loadModel(
const QString &path )
466 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
467 if ( alg->fromFile( path ) )
470 alg->setSourceFilePath( path );
471 setModel( alg.release() );
475 QgsMessageLog::logMessage( tr(
"Could not load model %1" ).arg( path ), tr(
"Processing" ), Qgis::MessageLevel::Critical );
476 QMessageBox::critical(
this, tr(
"Open Model" ), tr(
"The selected model could not be loaded.\n"
477 "See the log for more information." ) );
481 void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
483 QgsModelGraphicsScene *oldScene = mScene;
486 mScene->setParent(
this );
487 mScene->setChildAlgorithmResults( mChildResults );
488 mScene->setModel( mModel.get() );
489 mScene->setMessageBar( mMessageBar );
491 const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
492 mView->setModelScene( mScene );
494 mSelectTool->resetCache();
495 mSelectTool->setScene( mScene );
497 connect( mScene, &QgsModelGraphicsScene::rebuildRequired,
this, [ = ]
499 if ( mBlockRepaints )
504 connect( mScene, &QgsModelGraphicsScene::componentAboutToChange,
this, [ = ](
const QString & description,
int id ) { beginUndoCommand( description,
id ); } );
505 connect( mScene, &QgsModelGraphicsScene::componentChanged,
this, [ = ] { endUndoCommand(); } );
507 mView->centerOn( center );
510 oldScene->deleteLater();
513 void QgsModelDesignerDialog::activate()
517 setWindowState( windowState() & ~Qt::WindowMinimized );
521 void QgsModelDesignerDialog::updateVariablesGui()
523 mBlockUndoCommands++;
525 std::unique_ptr< QgsExpressionContextScope > variablesScope = std::make_unique< QgsExpressionContextScope >( tr(
"Model Variables" ) );
526 const QVariantMap modelVars = mModel->variables();
527 for (
auto it = modelVars.constBegin(); it != modelVars.constEnd(); ++it )
529 variablesScope->setVariable( it.key(), it.value() );
532 variablesContext.
appendScope( variablesScope.release() );
533 mVariablesEditor->setContext( &variablesContext );
534 mVariablesEditor->setEditableScopeIndex( 0 );
536 mBlockUndoCommands--;
539 void QgsModelDesignerDialog::setDirty(
bool dirty )
545 bool QgsModelDesignerDialog::validateSave( SaveAction action )
549 case QgsModelDesignerDialog::SaveAction::SaveAsFile:
551 case QgsModelDesignerDialog::SaveAction::SaveInProject:
552 if ( mNameEdit->text().trimmed().isEmpty() )
554 mMessageBar->pushWarning( QString(), tr(
"Please enter a model name before saving" ) );
563 bool QgsModelDesignerDialog::checkForUnsavedChanges()
567 QMessageBox::StandardButton ret = QMessageBox::question(
this, tr(
"Save Model?" ),
568 tr(
"There are unsaved changes in this model. Do you want to keep those?" ),
569 QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, QMessageBox::Cancel );
572 case QMessageBox::Save:
573 return saveModel(
false );
575 case QMessageBox::Discard:
588 void QgsModelDesignerDialog::setLastRunChildAlgorithmResults(
const QVariantMap &results )
590 mChildResults = results;
592 mScene->setChildAlgorithmResults( mChildResults );
595 void QgsModelDesignerDialog::setLastRunChildAlgorithmInputs(
const QVariantMap &inputs )
597 mChildInputs = inputs;
599 mScene->setChildAlgorithmInputs( mChildInputs );
602 void QgsModelDesignerDialog::setModelName(
const QString &name )
604 mNameEdit->setText( name );
607 void QgsModelDesignerDialog::zoomIn()
609 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
610 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
612 const double factor = settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
613 mView->scale( factor, factor );
614 mView->centerOn( point );
617 void QgsModelDesignerDialog::zoomOut()
619 mView->setTransformationAnchor( QGraphicsView::NoAnchor );
620 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
622 const double factor = 1.0 / settings.
value( QStringLiteral(
"/qgis/zoom_favor" ), 2.0 ).toDouble();
623 mView->scale( factor, factor );
624 mView->centerOn( point );
627 void QgsModelDesignerDialog::zoomActual()
629 QPointF point = mView->mapToScene( QPoint( mView->viewport()->width() / 2.0, mView->viewport()->height() / 2 ) );
630 mView->resetTransform();
631 mView->scale( QgsApplication::desktop()->logicalDpiX() / 96, QgsApplication::desktop()->logicalDpiX() / 96 );
632 mView->centerOn( point );
635 void QgsModelDesignerDialog::zoomFull()
637 QRectF totalRect = mView->scene()->itemsBoundingRect();
638 totalRect.adjust( -10, -10, 10, 10 );
639 mView->fitInView( totalRect, Qt::KeepAspectRatio );
642 void QgsModelDesignerDialog::newModel()
644 if ( !checkForUnsavedChanges() )
647 std::unique_ptr< QgsProcessingModelAlgorithm > alg = std::make_unique< QgsProcessingModelAlgorithm >();
649 setModel( alg.release() );
652 void QgsModelDesignerDialog::exportToImage()
655 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
657 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Image" ),
659 tr(
"PNG files (*.png *.PNG)" ) );
660 if ( filename.isEmpty() )
665 const QFileInfo saveFileInfo( filename );
668 repaintModel(
false );
670 QRectF totalRect = mView->scene()->itemsBoundingRect();
671 totalRect.adjust( -10, -10, 10, 10 );
672 const QRectF imageRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
674 QImage img( totalRect.width(), totalRect.height(),
675 QImage::Format_ARGB32_Premultiplied );
676 img.fill( Qt::white );
678 painter.setRenderHint( QPainter::Antialiasing );
679 painter.begin( &img );
680 mView->scene()->render( &painter, imageRect, totalRect );
683 img.save( filename );
685 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as image to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
686 repaintModel(
true );
689 void QgsModelDesignerDialog::exportToPdf()
692 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
694 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as PDF" ),
696 tr(
"PDF files (*.pdf *.PDF)" ) );
697 if ( filename.isEmpty() )
702 const QFileInfo saveFileInfo( filename );
705 repaintModel(
false );
707 QRectF totalRect = mView->scene()->itemsBoundingRect();
708 totalRect.adjust( -10, -10, 10, 10 );
709 const QRectF printerRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
712 printer.setOutputFormat( QPrinter::PdfFormat );
713 printer.setOutputFileName( filename );
714 printer.setPaperSize( QSizeF( printerRect.width(), printerRect.height() ), QPrinter::DevicePixel );
715 printer.setFullPage(
true );
717 QPainter painter( &printer );
718 mView->scene()->render( &painter, printerRect, totalRect );
721 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as PDF to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(),
722 QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
723 repaintModel(
true );
726 void QgsModelDesignerDialog::exportToSvg()
729 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
731 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as SVG" ),
733 tr(
"SVG files (*.svg *.SVG)" ) );
734 if ( filename.isEmpty() )
739 const QFileInfo saveFileInfo( filename );
742 repaintModel(
false );
744 QRectF totalRect = mView->scene()->itemsBoundingRect();
745 totalRect.adjust( -10, -10, 10, 10 );
746 const QRectF svgRect = QRectF( 0, 0, totalRect.width(), totalRect.height() );
749 svg.setFileName( filename );
750 svg.setSize( QSize( totalRect.width(), totalRect.height() ) );
751 svg.setViewBox( svgRect );
752 svg.setTitle( mModel->displayName() );
754 QPainter painter( &svg );
755 mView->scene()->render( &painter, svgRect, totalRect );
758 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as SVG to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
759 repaintModel(
true );
762 void QgsModelDesignerDialog::exportAsPython()
765 QString lastExportDir = settings.
value( QStringLiteral(
"lastModelDesignerExportDir" ), QDir::homePath(),
QgsSettings::App ).toString();
767 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save Model as Python Script" ),
769 tr(
"Processing scripts (*.py *.PY)" ) );
770 if ( filename.isEmpty() )
775 const QFileInfo saveFileInfo( filename );
780 QFile outFile( filename );
781 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
785 QTextStream fout( &outFile );
789 mMessageBar->pushMessage( QString(), tr(
"Successfully exported model as Python script to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filename ).toString(), QDir::toNativeSeparators( filename ) ), Qgis::MessageLevel::Success, 0 );
792 void QgsModelDesignerDialog::toggleComments(
bool show )
796 repaintModel(
true );
799 void QgsModelDesignerDialog::updateWindowTitle()
801 QString title = tr(
"Model Designer" );
802 if ( !mModel->name().isEmpty() )
803 title = QStringLiteral(
"%1 - %2" ).arg( title, mModel->name() );
806 title.prepend(
'*' );
808 setWindowTitle( title );
811 void QgsModelDesignerDialog::deleteSelected()
813 QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems();
817 if ( items.size() == 1 )
819 items.at( 0 )->deleteComponent();
823 std::sort( items.begin(), items.end(), []( QgsModelComponentGraphicItem * p1, QgsModelComponentGraphicItem * p2 )
826 if ( dynamic_cast< QgsModelCommentGraphicItem *>( p1 ) )
828 else if ( dynamic_cast< QgsModelCommentGraphicItem *>( p2 ) )
830 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p1 ) )
832 else if ( dynamic_cast< QgsModelGroupBoxGraphicItem *>( p2 ) )
834 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p1 ) )
836 else if ( dynamic_cast< QgsModelOutputGraphicItem *>( p2 ) )
838 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p1 ) )
840 else if ( dynamic_cast< QgsModelChildAlgorithmGraphicItem *>( p2 ) )
846 beginUndoCommand( tr(
"Delete Components" ) );
848 QVariant prevState = mModel->toVariant();
849 mBlockUndoCommands++;
850 mBlockRepaints =
true;
852 while ( !items.empty() )
854 QgsModelComponentGraphicItem *toDelete =
nullptr;
855 for ( QgsModelComponentGraphicItem *item : items )
857 if ( item->canDeleteComponent() )
870 toDelete->deleteComponent();
871 items.removeAll( toDelete );
876 mModel->loadVariant( prevState );
877 QMessageBox::warning(
nullptr, QObject::tr(
"Could not remove components" ),
878 QObject::tr(
"Components depend on the selected items.\n"
879 "Try to remove them before trying deleting these components." ) );
880 mBlockUndoCommands--;
881 mActiveCommand.reset();
885 mBlockUndoCommands--;
889 mBlockRepaints =
false;
893 void QgsModelDesignerDialog::populateZoomToMenu()
896 for (
const QgsProcessingModelGroupBox &box : model()->groupBoxes() )
898 if ( QgsModelComponentGraphicItem *item = mScene->groupBoxItem( box.uuid() ) )
900 QAction *zoomAction =
new QAction( box.description(), mGroupMenu );
901 connect( zoomAction, &QAction::triggered,
this, [ = ]
903 QRectF groupRect = item->mapToScene( item->boundingRect() ).boundingRect();
904 groupRect.adjust( -10, -10, 10, 10 );
905 mView->fitInView( groupRect, Qt::KeepAspectRatio );
906 mView->centerOn( item );
908 mGroupMenu->addAction( zoomAction );
913 void QgsModelDesignerDialog::setPanelVisibility(
bool hidden )
915 const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
916 const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
920 mPanelStatus.clear();
922 for ( QDockWidget *dock : docks )
924 mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(),
false ) );
925 dock->setVisible(
false );
929 for ( QTabBar *tabBar : tabBars )
931 QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
932 mPanelStatus[ currentTabTitle ].isActive =
true;
938 for ( QDockWidget *dock : docks )
940 if ( mPanelStatus.contains( dock->windowTitle() ) )
942 dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
947 for ( QTabBar *tabBar : tabBars )
950 for (
int i = 0; i < tabBar->count(); ++i )
952 QString tabTitle = tabBar->tabText( i );
953 if ( mPanelStatus.contains( tabTitle ) && mPanelStatus.value( tabTitle ).isActive )
955 tabBar->setCurrentIndex( i );
959 mPanelStatus.clear();
963 void QgsModelDesignerDialog::editHelp()
965 QgsProcessingHelpEditorDialog dialog(
this );
966 dialog.setWindowTitle( tr(
"Edit Model Help" ) );
967 dialog.setAlgorithm( mModel.get() );
970 beginUndoCommand( tr(
"Edit Model Help" ) );
971 mModel->setHelpContent( dialog.helpContent() );
976 void QgsModelDesignerDialog::validate()
979 if ( model()->validate( issues ) )
981 mMessageBar->pushSuccess( QString(), tr(
"Model is valid!" ) );
986 QPushButton *detailsButton =
new QPushButton( tr(
"Details" ) );
987 connect( detailsButton, &QPushButton::clicked, detailsButton, [ = ]
989 QgsMessageViewer *dialog =
new QgsMessageViewer( detailsButton );
990 dialog->setTitle( tr(
"Model is Invalid" ) );
992 QString longMessage = tr(
"<p>This model is not valid:</p>" ) + QStringLiteral(
"<ul>" );
993 for (
const QString &issue : issues )
995 longMessage += QStringLiteral(
"<li>%1</li>" ).arg( issue );
997 longMessage += QLatin1String(
"</ul>" );
1000 dialog->showMessage();
1002 messageWidget->layout()->addWidget( detailsButton );
1003 mMessageBar->clearWidgets();
1004 mMessageBar->pushWidget( messageWidget, Qgis::MessageLevel::Warning, 0 );
1008 void QgsModelDesignerDialog::reorderInputs()
1010 QgsModelInputReorderDialog dlg(
this );
1011 dlg.setModel( mModel.get() );
1014 const QStringList inputOrder = dlg.inputOrder();
1015 beginUndoCommand( tr(
"Reorder Inputs" ) );
1016 mModel->setParameterOrder( inputOrder );
1021 bool QgsModelDesignerDialog::isDirty()
const
1023 return mHasChanged && mUndoStack->index() != -1;
1026 void QgsModelDesignerDialog::fillInputsTree()
1029 std::unique_ptr< QTreeWidgetItem > parametersItem = std::make_unique< QTreeWidgetItem >();
1030 parametersItem->setText( 0, tr(
"Parameters" ) );
1034 return QString::localeAwareCompare( a->name(), b->name() ) < 0;
1041 std::unique_ptr< QTreeWidgetItem > paramItem = std::make_unique< QTreeWidgetItem >();
1042 paramItem->setText( 0, param->name() );
1043 paramItem->setData( 0, Qt::UserRole, param->id() );
1044 paramItem->setIcon( 0, icon );
1045 paramItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
1046 paramItem->setToolTip( 0, param->description() );
1047 parametersItem->addChild( paramItem.release() );
1050 mInputsTreeWidget->addTopLevelItem( parametersItem.release() );
1051 mInputsTreeWidget->topLevelItem( 0 )->setExpanded(
true );
1059 QgsModelChildDependenciesWidget::QgsModelChildDependenciesWidget( QWidget *parent, QgsProcessingModelAlgorithm *model,
const QString &childId )
1062 , mChildId( childId )
1064 QHBoxLayout *hl =
new QHBoxLayout();
1065 hl->setContentsMargins( 0, 0, 0, 0 );
1067 mLineEdit =
new QLineEdit();
1068 mLineEdit->setEnabled(
false );
1069 hl->addWidget( mLineEdit, 1 );
1071 mToolButton =
new QToolButton();
1072 mToolButton->setText( QString( QChar( 0x2026 ) ) );
1073 hl->addWidget( mToolButton );
1077 mLineEdit->setText( tr(
"%1 dependencies selected" ).arg( 0 ) );
1079 connect( mToolButton, &QToolButton::clicked,
this, &QgsModelChildDependenciesWidget::showDialog );
1082 void QgsModelChildDependenciesWidget::setValue(
const QList<QgsProcessingModelChildDependency> &value )
1086 updateSummaryText();
1089 void QgsModelChildDependenciesWidget::showDialog()
1091 const QList<QgsProcessingModelChildDependency> available = mModel->availableDependenciesForChildAlgorithm( mChildId );
1093 QVariantList availableOptions;
1094 for (
const QgsProcessingModelChildDependency &dep : available )
1095 availableOptions << QVariant::fromValue( dep );
1096 QVariantList selectedOptions;
1097 for (
const QgsProcessingModelChildDependency &dep : mValue )
1098 selectedOptions << QVariant::fromValue( dep );
1103 QgsProcessingMultipleSelectionPanelWidget *widget =
new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
1104 widget->setPanelTitle( tr(
"Algorithm Dependencies" ) );
1106 widget->setValueFormatter( [ = ](
const QVariant & v ) -> QString
1108 const QgsProcessingModelChildDependency dep = v.value< QgsProcessingModelChildDependency >();
1110 const QString description = mModel->childAlgorithm( dep.childId ).description();
1111 if ( dep.conditionalBranch.isEmpty() )
1114 return tr(
"Condition “%1” from algorithm “%2”" ).arg( dep.conditionalBranch, description );
1117 connect( widget, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged,
this, [ = ]()
1119 QList< QgsProcessingModelChildDependency > res;
1120 for (
const QVariant &v : widget->selectedOptions() )
1122 res << v.value< QgsProcessingModelChildDependency >();
1131 void QgsModelChildDependenciesWidget::updateSummaryText()
1133 mLineEdit->setText( tr(
"%n dependencies selected",
nullptr, mValue.count() ) );