Skip to content
Permalink
Browse files

[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 2729af65c30d36564ea3dd2ce4dafc3855fe7069
@@ -36,6 +36,7 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry()
addParameterWidgetFactory( new QgsProcessingAuthConfigWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingMatrixWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingFileWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingExpressionWidgetWrapper() );
}

QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry()
@@ -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>
@@ -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

@@ -32,6 +32,8 @@ class QgsDoubleSpinBox;
class QgsAuthConfigSelect;
class QgsProcessingMatrixParameterPanel;
class QgsFileWidget;
class QgsFieldExpressionWidget;
class QgsExpressionLineEdit;

///@cond PRIVATE

@@ -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
@@ -47,6 +47,8 @@
#include "qgsprocessingmatrixparameterdialog.h"
#include "models/qgsprocessingmodelalgorithm.h"
#include "qgsfilewidget.h"
#include "qgsexpressionlineedit.h"
#include "qgsfieldexpressionwidget.h"

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

private:

@@ -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 );

0 comments on commit 2729af6

Please sign in to comment.
You can’t perform that action at this time.