Skip to content

Commit

Permalink
[FEATURE][processing] Allow reordering model inputs
Browse files Browse the repository at this point in the history
Instead of forcing a quasi-random ordering of inputs for models,
this commit exposes a new "Reorder Model Inputs" option in the model
designer which allows users control over the exact order of
inputs to show users for their model.

No more illogical ordering like showing a field choice before the
layer choice it's based on!

Sponsored by NaturalGIS
  • Loading branch information
nyalldawson committed Apr 15, 2020
1 parent 2770c49 commit 7fd72f3
Show file tree
Hide file tree
Showing 11 changed files with 483 additions and 7 deletions.
Expand Up @@ -298,6 +298,27 @@ this name a new component will be added to the model and returned.
.. seealso:: :py:func:`setParameterComponents`

.. seealso:: :py:func:`setParameterComponent`
%End

QList< QgsProcessingModelParameter > orderedParameters() const;
%Docstring
Returns an ordered list of parameters for the model.

.. seealso:: :py:func:`setParameterOrder`

.. versionadded:: 3.14
%End

void setParameterOrder( const QStringList &order );
%Docstring
Sets the ``order`` for showing parameters for the model.

The ``order`` list should consist of parameter names corresponding to existing
model parameterComponents().

.. seealso:: :py:func:`orderedParameters`

.. versionadded:: 3.14
%End

void updateDestinationParameters();
Expand Down
59 changes: 55 additions & 4 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -1180,6 +1180,35 @@ QgsProcessingModelParameter &QgsProcessingModelAlgorithm::parameterComponent( co
return mParameterComponents[ name ];
}

QList< QgsProcessingModelParameter > QgsProcessingModelAlgorithm::orderedParameters() const
{
QList< QgsProcessingModelParameter > res;
QSet< QString > found;
for ( const QString &parameter : mParameterOrder )
{
if ( mParameterComponents.contains( parameter ) )
{
res << mParameterComponents.value( parameter );
found << parameter;
}
}

// add any missing ones to end of list
for ( auto it = mParameterComponents.constBegin(); it != mParameterComponents.constEnd(); ++it )
{
if ( !found.contains( it.key() ) )
{
res << it.value();
}
}
return res;
}

void QgsProcessingModelAlgorithm::setParameterOrder( const QStringList &order )
{
mParameterOrder = order;
}

void QgsProcessingModelAlgorithm::updateDestinationParameters()
{
//delete existing destination parameters
Expand Down Expand Up @@ -1294,6 +1323,8 @@ QVariant QgsProcessingModelAlgorithm::toVariant() const

map.insert( QStringLiteral( "designerParameterValues" ), mDesignerParameterValues );

map.insert( QStringLiteral( "parameterOrder" ), mParameterOrder );

return map;
}

Expand All @@ -1309,6 +1340,8 @@ bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
mVariables = map.value( QStringLiteral( "modelVariables" ) ).toMap();
mDesignerParameterValues = map.value( QStringLiteral( "designerParameterValues" ) ).toMap();

mParameterOrder = map.value( QStringLiteral( "parameterOrder" ) ).toStringList();

mChildAlgorithms.clear();
QVariantMap childMap = map.value( QStringLiteral( "children" ) ).toMap();
QVariantMap::const_iterator childIt = childMap.constBegin();
Expand Down Expand Up @@ -1339,23 +1372,41 @@ bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
qDeleteAll( mParameters );
mParameters.clear();
QVariantMap paramDefMap = map.value( QStringLiteral( "parameterDefinitions" ) ).toMap();
QVariantMap::const_iterator paramDefIt = paramDefMap.constBegin();
for ( ; paramDefIt != paramDefMap.constEnd(); ++paramDefIt )

auto addParam = [ = ]( const QVariant & value )
{
std::unique_ptr< QgsProcessingParameterDefinition > param( QgsProcessingParameters::parameterFromVariantMap( paramDefIt.value().toMap() ) );
std::unique_ptr< QgsProcessingParameterDefinition > param( QgsProcessingParameters::parameterFromVariantMap( value.toMap() ) );
// we be lenient here - even if we couldn't load a parameter, don't interrupt the model loading
// otherwise models may become unusable (e.g. due to removed plugins providing algs/parameters)
// with no way for users to repair them
if ( param )
addParameter( param.release() );
else
{
QVariantMap map = paramDefIt.value().toMap();
QVariantMap map = value.toMap();
QString type = map.value( QStringLiteral( "parameter_type" ) ).toString();
QString name = map.value( QStringLiteral( "name" ) ).toString();

QgsMessageLog::logMessage( QCoreApplication::translate( "Processing", "Could not load parameter %1 of type %2." ).arg( name, type ), QCoreApplication::translate( "Processing", "Processing" ) );
}
};

QSet< QString > loadedParams;
// first add parameters respecting mParameterOrder
for ( const QString &name : qgis::as_const( mParameterOrder ) )
{
if ( paramDefMap.contains( name ) )
{
addParam( paramDefMap.value( name ) );
loadedParams << name;
}
}
// then load any remaining parameters
QVariantMap::const_iterator paramDefIt = paramDefMap.constBegin();
for ( ; paramDefIt != paramDefMap.constEnd(); ++paramDefIt )
{
if ( !loadedParams.contains( paramDefIt.key() ) )
addParam( paramDefIt.value() );
}

mGroupBoxes.clear();
Expand Down
21 changes: 21 additions & 0 deletions src/core/processing/models/qgsprocessingmodelalgorithm.h
Expand Up @@ -255,6 +255,25 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
*/
QgsProcessingModelParameter &parameterComponent( const QString &name );

/**
* Returns an ordered list of parameters for the model.
*
* \see setParameterOrder()
* \since QGIS 3.14
*/
QList< QgsProcessingModelParameter > orderedParameters() const;

/**
* Sets the \a order for showing parameters for the model.
*
* The \a order list should consist of parameter names corresponding to existing
* model parameterComponents().
*
* \see orderedParameters()
* \since QGIS 3.14
*/
void setParameterOrder( const QStringList &order );

/**
* Updates the model's parameter definitions to include all relevant destination
* parameters as required by child algorithm ModelOutputs.
Expand Down Expand Up @@ -502,6 +521,8 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm

QMap< QString, QgsProcessingModelGroupBox > mGroupBoxes;

QStringList mParameterOrder;

void dependsOnChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends ) const;
void dependentChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends ) const;

Expand Down
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -293,6 +293,7 @@ SET(QGIS_GUI_SRCS
processing/models/qgsmodelgraphicsscene.cpp
processing/models/qgsmodelgraphicsview.cpp
processing/models/qgsmodelgroupboxdefinitionwidget.cpp
processing/models/qgsmodelinputreorderwidget.cpp
processing/models/qgsmodelsnapper.cpp
processing/models/qgsmodelundocommand.cpp
processing/models/qgsmodelviewmouseevent.cpp
Expand Down Expand Up @@ -999,6 +1000,7 @@ SET(QGIS_GUI_HDRS
processing/models/qgsmodelgraphicsscene.h
processing/models/qgsmodelgraphicsview.h
processing/models/qgsmodelgroupboxdefinitionwidget.h
processing/models/qgsmodelinputreorderwidget.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 @@ -28,6 +28,7 @@
#include "qgsmodelgraphicsscene.h"
#include "qgsmodelcomponentgraphicitem.h"
#include "processing/models/qgsprocessingmodelgroupbox.h"
#include "processing/models/qgsmodelinputreorderwidget.h"
#include "qgsmessageviewer.h"
#include "qgsmessagebaritem.h"

Expand Down Expand Up @@ -138,6 +139,8 @@ QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags
connect( mActionDeleteComponents, &QAction::triggered, this, &QgsModelDesignerDialog::deleteSelected );
connect( mActionSnapSelected, &QAction::triggered, mView, &QgsModelGraphicsView::snapSelected );
connect( mActionValidate, &QAction::triggered, this, &QgsModelDesignerDialog::validate );
connect( mActionReorderInputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderInputs );
connect( mReorderInputsButton, &QPushButton::clicked, this, &QgsModelDesignerDialog::reorderInputs );

mActionSnappingEnabled->setChecked( settings.value( QStringLiteral( "/Processing/Modeler/enableSnapToGrid" ), false ).toBool() );
connect( mActionSnappingEnabled, &QAction::toggled, this, [ = ]( bool enabled )
Expand Down Expand Up @@ -813,6 +816,19 @@ void QgsModelDesignerDialog::validate()
}
}

void QgsModelDesignerDialog::reorderInputs()
{
QgsModelInputReorderDialog dlg( this );
dlg.setInputs( mModel->orderedParameters() );
if ( dlg.exec() )
{
const QStringList inputOrder = dlg.inputOrder();
beginUndoCommand( tr( "Reorder Inputs" ) );
mModel->setParameterOrder( inputOrder );
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 @@ -144,6 +144,7 @@ class GUI_EXPORT QgsModelDesignerDialog : public QMainWindow, public Ui::QgsMode
void deleteSelected();
void populateZoomToMenu();
void validate();
void reorderInputs();

private:

Expand Down
101 changes: 101 additions & 0 deletions src/gui/processing/models/qgsmodelinputreorderwidget.cpp
@@ -0,0 +1,101 @@
/***************************************************************************
qgsmodelinputreorderwidget.cpp
------------------------------------
Date : April 2020
Copyright : (C) 2020 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 "qgsmodelinputreorderwidget.h"
#include "qgsgui.h"
#include <QDialogButtonBox>
#include <QStringListModel>
///@cond NOT_STABLE

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

mModel = new QStringListModel( this );
mInputsList->setModel( mModel );

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

mModel->moveRow( QModelIndex(), currentRow, QModelIndex(), currentRow + 2 );
} );

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

mModel->moveRow( QModelIndex(), currentRow, QModelIndex(), currentRow + 2 );
} );

}

void QgsModelInputReorderWidget::setInputs( const QList<QgsProcessingModelParameter> &inputs )
{
mParameters = inputs;
QStringList res;
for ( const QgsProcessingModelParameter &param : inputs )
res << param.description();
mModel->setStringList( res );
}

QStringList QgsModelInputReorderWidget::inputOrder() const
{
const QStringList order = mModel->stringList();
QStringList res;
for ( const QString &description : order )
{
for ( auto it = mParameters.constBegin(); it != mParameters.constEnd(); ++it )
{
if ( it->description() == description )
{
res << it->parameterName();
}
}
}
return res;
}


QgsModelInputReorderDialog::QgsModelInputReorderDialog( QWidget *parent )
: QDialog( parent )
{
setWindowTitle( tr( "Reorder Model Inputs" ) );
mWidget = new QgsModelInputReorderWidget();
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 QgsModelInputReorderDialog::setInputs( const QList<QgsProcessingModelParameter> &inputs )
{
mWidget->setInputs( inputs );
}

QStringList QgsModelInputReorderDialog::inputOrder() const
{
return mWidget->inputOrder();
}

///@endcond

0 comments on commit 7fd72f3

Please sign in to comment.