Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] Port expression widget wrapper to new API
Fixes confusing expression parameter definitions in modeler
child algorithms (there's no direction as to what the
widget should be) and ensures that the correct expression
context is revealed to the widget when in all modes.
  • Loading branch information
nyalldawson committed Feb 11, 2019
1 parent 6f6d562 commit 2729af6
Show file tree
Hide file tree
Showing 4 changed files with 344 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/gui/processing/qgsprocessingguiregistry.cpp
Expand Up @@ -36,6 +36,7 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry()
addParameterWidgetFactory( new QgsProcessingAuthConfigWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingMatrixWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingFileWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingExpressionWidgetWrapper() );
}

QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry()
Expand Down
187 changes: 187 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
Expand Up @@ -27,6 +27,8 @@
#include "qgsapplication.h"
#include "qgsfilewidget.h"
#include "qgssettings.h"
#include "qgsexpressionlineedit.h"
#include "qgsfieldexpressionwidget.h"
#include <QLabel>
#include <QHBoxLayout>
#include <QCheckBox>
Expand Down Expand Up @@ -1210,5 +1212,190 @@ QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingFileWidgetWrapper::cre
}




//
// QgsProcessingExpressionWidgetWrapper
//

QgsProcessingExpressionWidgetWrapper::QgsProcessingExpressionWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
: QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
{

}

QWidget *QgsProcessingExpressionWidgetWrapper::createWidget()
{
const QgsProcessingParameterExpression *expParam = dynamic_cast< const QgsProcessingParameterExpression *>( parameterDefinition() );
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Modeler:
case QgsProcessingGui::Batch:
{
if ( expParam->parentLayerParameterName().isEmpty() )
{
mExpLineEdit = new QgsExpressionLineEdit();
mExpLineEdit->setToolTip( parameterDefinition()->toolTip() );
mExpLineEdit->setExpressionDialogTitle( parameterDefinition()->description() );
mExpLineEdit->registerExpressionContextGenerator( this );
connect( mExpLineEdit, &QgsExpressionLineEdit::expressionChanged, this, [ = ]( const QString & )
{
emit widgetValueHasChanged( this );
} );
return mExpLineEdit;
}
else
{
mFieldExpWidget = new QgsFieldExpressionWidget();
mFieldExpWidget->setToolTip( parameterDefinition()->toolTip() );
mFieldExpWidget->setExpressionDialogTitle( parameterDefinition()->description() );
mFieldExpWidget->registerExpressionContextGenerator( this );
connect( mFieldExpWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, [ = ]( const QString & )
{
emit widgetValueHasChanged( this );
} );
return mFieldExpWidget;
}
};
}
return nullptr;
}

void QgsProcessingExpressionWidgetWrapper::postInitialize( const QList<QgsAbstractProcessingParameterWidgetWrapper *> &wrappers )
{
QgsAbstractProcessingParameterWidgetWrapper::postInitialize( wrappers );
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Batch:
{
for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers )
{
if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterExpression * >( parameterDefinition() )->parentLayerParameterName() )
{
setParentLayerWrapperValue( wrapper );
connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ]
{
setParentLayerWrapperValue( wrapper );
} );
break;
}
}
break;
}

case QgsProcessingGui::Modeler:
break;
}
}

void QgsProcessingExpressionWidgetWrapper::setParentLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper )
{
// evaluate value to layer
QgsProcessingContext *context = nullptr;
std::unique_ptr< QgsProcessingContext > tmpContext;
if ( mProcessingContextGenerator )
context = mProcessingContextGenerator->processingContext();

if ( !context )
{
tmpContext = qgis::make_unique< QgsProcessingContext >();
context = tmpContext.get();
}

QgsVectorLayer *layer = QgsProcessingParameters::parameterAsVectorLayer( parentWrapper->parameterDefinition(), parentWrapper->parameterValue(), *context );
if ( !layer )
{
if ( mFieldExpWidget )
mFieldExpWidget->setLayer( nullptr );
else if ( mExpLineEdit )
mExpLineEdit->setLayer( nullptr );
return;
}

// need to grab ownership of layer if required - otherwise layer may be deleted when context
// goes out of scope
std::unique_ptr< QgsMapLayer > ownedLayer( context->takeResultLayer( layer->id() ) );
if ( ownedLayer && ownedLayer->type() == QgsMapLayer::VectorLayer )
{
mParentLayer.reset( qobject_cast< QgsVectorLayer * >( ownedLayer.release() ) );
layer = mParentLayer.get();
}
else
{
// don't need ownership of this layer - it wasn't owned by context (so e.g. is owned by the project)
}

if ( mFieldExpWidget )
mFieldExpWidget->setLayer( layer );
else if ( mExpLineEdit )
mExpLineEdit->setLayer( layer );
}

void QgsProcessingExpressionWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
const QString v = QgsProcessingParameters::parameterAsString( parameterDefinition(), value, context );
if ( mFieldExpWidget )
mFieldExpWidget->setExpression( v );
else if ( mExpLineEdit )
mExpLineEdit->setExpression( v );
}

QVariant QgsProcessingExpressionWidgetWrapper::widgetValue() const
{
if ( mFieldExpWidget )
return mFieldExpWidget->expression();
else if ( mExpLineEdit )
return mExpLineEdit->expression();
else
return QVariant();
}

QStringList QgsProcessingExpressionWidgetWrapper::compatibleParameterTypes() const
{
return QStringList()
<< QgsProcessingParameterExpression::typeName()
<< QgsProcessingParameterString::typeName()
<< QgsProcessingParameterNumber::typeName()
<< QgsProcessingParameterDistance::typeName();
}

QStringList QgsProcessingExpressionWidgetWrapper::compatibleOutputTypes() const
{
return QStringList()
<< QgsProcessingOutputString::typeName()
<< QgsProcessingOutputNumber::typeName();
}

QList<int> QgsProcessingExpressionWidgetWrapper::compatibleDataTypes() const
{
return QList< int >();
}

QString QgsProcessingExpressionWidgetWrapper::modelerExpressionFormatString() const
{
return tr( "string representation of an expression" );
}

const QgsVectorLayer *QgsProcessingExpressionWidgetWrapper::linkedVectorLayer() const
{
if ( mFieldExpWidget && mFieldExpWidget->layer() )
return mFieldExpWidget->layer();

return QgsAbstractProcessingParameterWidgetWrapper::linkedVectorLayer();
}

QString QgsProcessingExpressionWidgetWrapper::parameterType() const
{
return QgsProcessingParameterExpression::typeName();
}

QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingExpressionWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type )
{
return new QgsProcessingExpressionWidgetWrapper( parameter, type );
}


///@endcond PRIVATE

41 changes: 41 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.h
Expand Up @@ -32,6 +32,8 @@ class QgsDoubleSpinBox;
class QgsAuthConfigSelect;
class QgsProcessingMatrixParameterPanel;
class QgsFileWidget;
class QgsFieldExpressionWidget;
class QgsExpressionLineEdit;

///@cond PRIVATE

Expand Down Expand Up @@ -360,6 +362,45 @@ class GUI_EXPORT QgsProcessingFileWidgetWrapper : public QgsAbstractProcessingPa
friend class TestProcessingGui;
};

class GUI_EXPORT QgsProcessingExpressionWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface
{
Q_OBJECT

public:

QgsProcessingExpressionWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr,
QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = nullptr );

// QgsProcessingParameterWidgetFactoryInterface
QString parameterType() const override;
QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override;

// QgsProcessingParameterWidgetWrapper interface
QWidget *createWidget() override SIP_FACTORY;
void postInitialize( const QList< QgsAbstractProcessingParameterWidgetWrapper * > &wrappers ) override;
public slots:
void setParentLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper );
protected:

void setWidgetValue( const QVariant &value, QgsProcessingContext &context ) override;
QVariant widgetValue() const override;

QStringList compatibleParameterTypes() const override;

QStringList compatibleOutputTypes() const override;

QList< int > compatibleDataTypes() const override;
QString modelerExpressionFormatString() const override;
const QgsVectorLayer *linkedVectorLayer() const override;
private:

QgsFieldExpressionWidget *mFieldExpWidget = nullptr;
QgsExpressionLineEdit *mExpLineEdit = nullptr;
std::unique_ptr< QgsVectorLayer > mParentLayer;

friend class TestProcessingGui;
};

///@endcond PRIVATE

#endif // QGSPROCESSINGWIDGETWRAPPERIMPL_H
115 changes: 115 additions & 0 deletions tests/src/gui/testprocessinggui.cpp
Expand Up @@ -47,6 +47,8 @@
#include "qgsprocessingmatrixparameterdialog.h"
#include "models/qgsprocessingmodelalgorithm.h"
#include "qgsfilewidget.h"
#include "qgsexpressionlineedit.h"
#include "qgsfieldexpressionwidget.h"

class TestParamType : public QgsProcessingParameterDefinition
{
Expand Down Expand Up @@ -160,6 +162,7 @@ class TestProcessingGui : public QObject
void testRangeWrapper();
void testMatrixDialog();
void testMatrixWrapper();
void testExpressionWrapper();

private:

Expand Down Expand Up @@ -1836,6 +1839,118 @@ void TestProcessingGui::testMatrixWrapper()
testWrapper( QgsProcessingGui::Modeler );
}

void TestProcessingGui::testExpressionWrapper()
{
const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) );
const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) );

auto testWrapper = [layerDef]( QgsProcessingGui::WidgetType type )
{
QgsProcessingParameterExpression param( QStringLiteral( "expression" ), QStringLiteral( "expression" ) );

QgsProcessingExpressionWidgetWrapper wrapper( &param, type );

QgsProcessingContext context;
QWidget *w = wrapper.createWrappedWidget( context );

QSignalSpy spy( &wrapper, &QgsProcessingExpressionWidgetWrapper::widgetValueHasChanged );
wrapper.setWidgetValue( QStringLiteral( "1+2" ), context );
QCOMPARE( spy.count(), 1 );
QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "1+2" ) );
QCOMPARE( static_cast< QgsExpressionLineEdit * >( wrapper.wrappedWidget() )->expression(), QStringLiteral( "1+2" ) );
wrapper.setWidgetValue( QString(), context );
QCOMPARE( spy.count(), 2 );
QVERIFY( wrapper.widgetValue().toString().isEmpty() );
QVERIFY( static_cast< QgsExpressionLineEdit * >( wrapper.wrappedWidget() )->expression().isEmpty() );

QLabel *l = wrapper.createWrappedLabel();
if ( wrapper.type() != QgsProcessingGui::Batch )
{
QVERIFY( l );
QCOMPARE( l->text(), QStringLiteral( "expression" ) );
QCOMPARE( l->toolTip(), param.toolTip() );
delete l;
}
else
{
QVERIFY( !l );
}

// check signal
static_cast< QgsExpressionLineEdit * >( wrapper.wrappedWidget() )->setExpression( QStringLiteral( "3+4" ) );
QCOMPARE( spy.count(), 3 );

delete w;

// with layer
param.setParentLayerParameterName( QStringLiteral( "other" ) );
QgsProcessingExpressionWidgetWrapper wrapper2( &param, type );
w = wrapper2.createWrappedWidget( context );

QSignalSpy spy2( &wrapper2, &QgsProcessingExpressionWidgetWrapper::widgetValueHasChanged );
wrapper2.setWidgetValue( QStringLiteral( "11+12" ), context );
QCOMPARE( spy2.count(), 1 );
QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "11+12" ) );
QCOMPARE( static_cast< QgsFieldExpressionWidget * >( wrapper2.wrappedWidget() )->expression(), QStringLiteral( "11+12" ) );

wrapper2.setWidgetValue( QString(), context );
QCOMPARE( spy2.count(), 2 );
QVERIFY( wrapper2.widgetValue().toString().isEmpty() );
QVERIFY( static_cast< QgsFieldExpressionWidget * >( wrapper2.wrappedWidget() )->expression().isEmpty() );

static_cast< QgsFieldExpressionWidget * >( wrapper2.wrappedWidget() )->setExpression( QStringLiteral( "3+4" ) );
QCOMPARE( spy2.count(), 3 );

TestLayerWrapper layerWrapper( layerDef );
QgsProject p;
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
p.addMapLayer( vl );

QVERIFY( !wrapper2.mFieldExpWidget->layer() );
layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QCOMPARE( wrapper2.mFieldExpWidget->layer(), vl );

// should not be owned by wrapper
QVERIFY( !wrapper2.mParentLayer.get() );
layerWrapper.setWidgetValue( QVariant(), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QVERIFY( !wrapper2.mFieldExpWidget->layer() );

layerWrapper.setWidgetValue( vl->id(), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QVERIFY( !wrapper2.mFieldExpWidget->layer() );
QVERIFY( !wrapper2.mParentLayer.get() );

// with project layer
context.setProject( &p );
TestProcessingContextGenerator generator( context );
wrapper2.registerProcessingContextGenerator( &generator );

layerWrapper.setWidgetValue( vl->id(), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QCOMPARE( wrapper2.mFieldExpWidget->layer(), vl );
QVERIFY( !wrapper2.mParentLayer.get() );

// non-project layer
QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" );
layerWrapper.setWidgetValue( pointFileName, context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QCOMPARE( wrapper2.mFieldExpWidget->layer()->publicSource(), pointFileName );
// must be owned by wrapper, or layer may be deleted while still required by wrapper
QCOMPARE( wrapper2.mParentLayer->publicSource(), pointFileName );
};

// standard wrapper
testWrapper( QgsProcessingGui::Standard );

// batch wrapper
testWrapper( QgsProcessingGui::Batch );

// modeler wrapper
testWrapper( QgsProcessingGui::Modeler );
}

void TestProcessingGui::cleanupTempDir()
{
QDir tmpDir = QDir( mTempDir );
Expand Down

0 comments on commit 2729af6

Please sign in to comment.