Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] Allow configuration of order of outputs created by a model
This adds a new "Reorder Model Outputs" action to the model designer
menu (to accompany the existing "Reorder Model Inputs" action).
Selecting this option allows model creators to set a specific order
which the outputs from their model must use when loading the results
into a project. This gives the model creator a means of ensuring
that layers are logically ordered, eg placing a vector layer over
a raster layer and a point layer over a polygon layer.

Optionally, the model creator can also set a "Group name" for the
outputs. If this is specified then all outputs from the model will
be placed into a (newly created if necessary) layer tree group
with that name.

Sponsored by the QGIS Germany User Group
  • Loading branch information
nyalldawson committed May 10, 2023
1 parent f1c0923 commit f27195c
Show file tree
Hide file tree
Showing 11 changed files with 684 additions and 4 deletions.
Expand Up @@ -340,6 +340,45 @@ model :py:func:`~QgsProcessingModelAlgorithm.parameterComponents`.
.. seealso:: :py:func:`orderedParameters`

.. versionadded:: 3.14
%End

QList< QgsProcessingModelOutput > orderedOutputs() const;
%Docstring
Returns an ordered list of outputs for the model.

.. seealso:: :py:func:`setOutputOrder`

.. versionadded:: 3.32
%End

void setOutputOrder( const QStringList &order );
%Docstring
Sets the ``order`` for sorting outputs for the model.

The ``order`` list should consist of "output child algorithm id:output name" formatted strings corresponding to existing
model outputs.

.. seealso:: :py:func:`orderedOutputs`

.. versionadded:: 3.32
%End

QString outputGroup() const;
%Docstring
Returns the destination layer tree group name for outputs created by the model.

.. seealso:: :py:func:`setOutputGroup`

.. versionadded:: 3.32
%End

void setOutputGroup( const QString &group );
%Docstring
Sets the destination layer tree ``group`` name for outputs created by the model.

.. seealso:: :py:func:`outputGroup`

.. versionadded:: 3.32
%End

void updateDestinationParameters();
Expand Down
74 changes: 71 additions & 3 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -478,10 +478,10 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa

// look through child alg's outputs to determine whether any of these should be copied
// to the final model outputs
QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
for ( ; outputIt != outputs.constEnd(); ++outputIt )
const QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
{
const int outputSortKey = mOutputOrder.indexOf( QStringLiteral( "%1:%2" ).arg( childId, outputIt->childOutputName() ) );
switch ( mInternalVersion )
{
case QgsProcessingModelAlgorithm::InternalVersion::Version1:
Expand All @@ -494,6 +494,14 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
}
break;
}

if ( !results.value( outputIt->childOutputName() ).toString().isEmpty() )
{
QgsProcessingContext::LayerDetails &details = context.layerToLoadOnCompletionDetails( results.value( outputIt->childOutputName() ).toString() );
details.groupName = mOutputGroup;
if ( outputSortKey > 0 )
details.layerSortKey = outputSortKey;
}
}

executed.insert( childId );
Expand Down Expand Up @@ -1373,6 +1381,62 @@ void QgsProcessingModelAlgorithm::setParameterOrder( const QStringList &order )
mParameterOrder = order;
}

QList<QgsProcessingModelOutput> QgsProcessingModelAlgorithm::orderedOutputs() const
{
QList< QgsProcessingModelOutput > res;
QSet< QString > found;

for ( const QString &output : mOutputOrder )
{
bool foundOutput = false;
for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
{
const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
{
if ( output == QStringLiteral( "%1:%2" ).arg( outputIt->childId(), outputIt->childOutputName() ) )
{
res << outputIt.value();
foundOutput = true;
found.insert( QStringLiteral( "%1:%2" ).arg( outputIt->childId(), outputIt->childOutputName() ) );
}
}
if ( foundOutput )
break;
}
}

// add any missing ones to end of list
for ( auto it = mChildAlgorithms.constBegin(); it != mChildAlgorithms.constEnd(); ++it )
{
const QMap<QString, QgsProcessingModelOutput> outputs = it.value().modelOutputs();
for ( auto outputIt = outputs.constBegin(); outputIt != outputs.constEnd(); ++outputIt )
{
if ( !found.contains( QStringLiteral( "%1:%2" ).arg( outputIt->childId(), outputIt->childOutputName() ) ) )
{
res << outputIt.value();
}
}
}

return res;
}

void QgsProcessingModelAlgorithm::setOutputOrder( const QStringList &order )
{
mOutputOrder = order;
}

QString QgsProcessingModelAlgorithm::outputGroup() const
{
return mOutputGroup;
}

void QgsProcessingModelAlgorithm::setOutputGroup( const QString &group )
{
mOutputGroup = group;
}

void QgsProcessingModelAlgorithm::updateDestinationParameters()
{
//delete existing destination parameters
Expand Down Expand Up @@ -1517,6 +1581,8 @@ QVariant QgsProcessingModelAlgorithm::toVariant() const
map.insert( QStringLiteral( "designerParameterValues" ), mDesignerParameterValues );

map.insert( QStringLiteral( "parameterOrder" ), mParameterOrder );
map.insert( QStringLiteral( "outputOrder" ), mOutputOrder );
map.insert( QStringLiteral( "outputGroup" ), mOutputGroup );

return map;
}
Expand All @@ -1536,6 +1602,8 @@ bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
mDesignerParameterValues = map.value( QStringLiteral( "designerParameterValues" ) ).toMap();

mParameterOrder = map.value( QStringLiteral( "parameterOrder" ) ).toStringList();
mOutputOrder = map.value( QStringLiteral( "outputOrder" ) ).toStringList();
mOutputGroup = map.value( QStringLiteral( "outputGroup" ) ).toString();

mChildAlgorithms.clear();
QVariantMap childMap = map.value( QStringLiteral( "children" ) ).toMap();
Expand Down
37 changes: 37 additions & 0 deletions src/core/processing/models/qgsprocessingmodelalgorithm.h
Expand Up @@ -297,6 +297,41 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
*/
void setParameterOrder( const QStringList &order );

/**
* Returns an ordered list of outputs for the model.
*
* \see setOutputOrder()
* \since QGIS 3.32
*/
QList< QgsProcessingModelOutput > orderedOutputs() const;

/**
* Sets the \a order for sorting outputs for the model.
*
* The \a order list should consist of "output child algorithm id:output name" formatted strings corresponding to existing
* model outputs.
*
* \see orderedOutputs()
* \since QGIS 3.32
*/
void setOutputOrder( const QStringList &order );

/**
* Returns the destination layer tree group name for outputs created by the model.
*
* \see setOutputGroup()
* \since QGIS 3.32
*/
QString outputGroup() const;

/**
* Sets the destination layer tree \a group name for outputs created by the model.
*
* \see outputGroup()
* \since QGIS 3.32
*/
void setOutputGroup( const QString &group );

/**
* Updates the model's parameter definitions to include all relevant destination
* parameters as required by child algorithm ModelOutputs.
Expand Down Expand Up @@ -585,6 +620,8 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
QMap< QString, QgsProcessingModelGroupBox > mGroupBoxes;

QStringList mParameterOrder;
QStringList mOutputOrder;
QString mOutputGroup;

void dependsOnChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends ) const;
void dependentChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends, const QString &branch ) const;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -402,6 +402,7 @@ set(QGIS_GUI_SRCS
processing/models/qgsmodelgraphicsview.cpp
processing/models/qgsmodelgroupboxdefinitionwidget.cpp
processing/models/qgsmodelinputreorderwidget.cpp
processing/models/qgsmodeloutputreorderwidget.cpp
processing/models/qgsmodelsnapper.cpp
processing/models/qgsmodelundocommand.cpp
processing/models/qgsmodelviewmouseevent.cpp
Expand Down Expand Up @@ -1325,6 +1326,7 @@ set(QGIS_GUI_HDRS
processing/models/qgsmodelgraphicsview.h
processing/models/qgsmodelgroupboxdefinitionwidget.h
processing/models/qgsmodelinputreorderwidget.h
processing/models/qgsmodeloutputreorderwidget.h
processing/models/qgsmodelsnapper.h
processing/models/qgsmodelundocommand.h
processing/models/qgsmodelviewmouseevent.h
Expand Down
16 changes: 16 additions & 0 deletions src/gui/processing/models/qgsmodeldesignerdialog.cpp
Expand Up @@ -30,6 +30,7 @@
#include "qgsmodelcomponentgraphicitem.h"
#include "processing/models/qgsprocessingmodelgroupbox.h"
#include "processing/models/qgsmodelinputreorderwidget.h"
#include "processing/models/qgsmodeloutputreorderwidget.h"
#include "qgsmessageviewer.h"
#include "qgsmessagebaritem.h"
#include "qgspanelwidget.h"
Expand Down Expand Up @@ -152,6 +153,7 @@ QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags
connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
connect( mActionValidate, &QAction::triggered, this, &QgsModelDesignerDialog::validate );
connect( mActionReorderInputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderInputs );
connect( mActionReorderOutputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderOutputs );
connect( mActionEditHelp, &QAction::triggered, this, &QgsModelDesignerDialog::editHelp );
connect( mReorderInputsButton, &QPushButton::clicked, this, &QgsModelDesignerDialog::reorderInputs );

Expand Down Expand Up @@ -1028,6 +1030,20 @@ void QgsModelDesignerDialog::reorderInputs()
}
}

void QgsModelDesignerDialog::reorderOutputs()
{
QgsModelOutputReorderDialog dlg( this );
dlg.setModel( mModel.get() );
if ( dlg.exec() )
{
const QStringList outputOrder = dlg.outputOrder();
beginUndoCommand( tr( "Reorder Outputs" ) );
mModel->setOutputOrder( outputOrder );
mModel->setOutputGroup( dlg.outputGroup() );
endUndoCommand();
}
}

bool QgsModelDesignerDialog::isDirty() const
{
return mHasChanged && mUndoStack->index() != -1;
Expand Down
1 change: 1 addition & 0 deletions src/gui/processing/models/qgsmodeldesignerdialog.h
Expand Up @@ -183,6 +183,7 @@ class GUI_EXPORT QgsModelDesignerDialog : public QMainWindow, public Ui::QgsMode
void populateZoomToMenu();
void validate();
void reorderInputs();
void reorderOutputs();
void setPanelVisibility( bool hidden );
void editHelp();

Expand Down
123 changes: 123 additions & 0 deletions src/gui/processing/models/qgsmodeloutputreorderwidget.cpp
@@ -0,0 +1,123 @@
/***************************************************************************
qgsmodeloutputreorderwidget.cpp
------------------------------------
Date : April 2023
Copyright : (C) 2023 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsmodeloutputreorderwidget.h"
#include "qgsgui.h"
#include "qgsprocessingmodelalgorithm.h"
#include <QDialogButtonBox>
#include <QStandardItemModel>
///@cond NOT_STABLE

QgsModelOutputReorderWidget::QgsModelOutputReorderWidget( QWidget *parent )
: QWidget( parent )
{
setupUi( this );

mItemModel = new QStandardItemModel( 0, 1, this );
mOutputsList->setModel( mItemModel );

mOutputsList->setDropIndicatorShown( true );
mOutputsList->setDragDropOverwriteMode( false );
mOutputsList->setDragEnabled( true );
mOutputsList->setDragDropMode( QAbstractItemView::InternalMove );

connect( mButtonUp, &QPushButton::clicked, this, [ = ]
{
int currentRow = mOutputsList->currentIndex().row();
if ( currentRow == 0 )
return;

mItemModel->insertRow( currentRow - 1, mItemModel->takeRow( currentRow ) );
mOutputsList->setCurrentIndex( mItemModel->index( currentRow - 1, 0 ) );
} );

connect( mButtonDown, &QPushButton::clicked, this, [ = ]
{
int currentRow = mOutputsList->currentIndex().row();
if ( currentRow == mItemModel->rowCount() - 1 )
return;

mItemModel->insertRow( currentRow + 1, mItemModel->takeRow( currentRow ) );
mOutputsList->setCurrentIndex( mItemModel->index( currentRow + 1, 0 ) );
} );

}

void QgsModelOutputReorderWidget::setModel( QgsProcessingModelAlgorithm *model )
{
mModel = model;
mOutputs = mModel->orderedOutputs();
mItemModel->clear();
for ( const QgsProcessingModelOutput &output : std::as_const( mOutputs ) )
{
QStandardItem *item = new QStandardItem( output.name() );
item->setData( QStringLiteral( "%1:%2" ).arg( output.childId(), output.childOutputName() ), Qt::UserRole + 1 );
item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
// we show the outputs list reversed in the gui, because we want the "higher" outputs to be at the top of the list
mItemModel->insertRow( 0, item );
}

mPlaceInGroupCheck->setChecked( !model->outputGroup().isEmpty() );
mGroupNameEdit->setText( model->outputGroup() );
}

QStringList QgsModelOutputReorderWidget::outputOrder() const
{
QStringList order;
order.reserve( mItemModel->rowCount( ) );
// we show the outputs list reversed in the gui, because we want the "higher" outputs to be at the top of the list
for ( int row = mItemModel->rowCount() - 1; row >= 0; --row )
{
order << mItemModel->data( mItemModel->index( row, 0 ), Qt::UserRole + 1 ).toString();
}
return order;
}

QString QgsModelOutputReorderWidget::outputGroup() const
{
return mPlaceInGroupCheck->isChecked() ? mGroupNameEdit->text() : QString();
}


QgsModelOutputReorderDialog::QgsModelOutputReorderDialog( QWidget *parent )
: QDialog( parent )
{
setWindowTitle( tr( "Reorder Model Outputs" ) );
mWidget = new QgsModelOutputReorderWidget();
QVBoxLayout *vl = new QVBoxLayout();
vl->addWidget( mWidget, 1 );
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
vl->addWidget( buttonBox );
setLayout( vl );
}

void QgsModelOutputReorderDialog::setModel( QgsProcessingModelAlgorithm *model )
{
mWidget->setModel( model );
}

QStringList QgsModelOutputReorderDialog::outputOrder() const
{
return mWidget->outputOrder();
}

QString QgsModelOutputReorderDialog::outputGroup() const
{
return mWidget->outputGroup();
}

///@endcond

0 comments on commit f27195c

Please sign in to comment.