Skip to content

Commit

Permalink
[processing] Port Range parameter widget to new API
Browse files Browse the repository at this point in the history
Fixes use of range parameters inside models, allowing range
inputs to be used for range parameter values.

Fixes #19785
  • Loading branch information
nyalldawson committed Sep 21, 2018
1 parent adfaf76 commit 1ec3654
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/gui/processing/qgsprocessingguiregistry.cpp
Expand Up @@ -32,6 +32,7 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry()
addParameterWidgetFactory( new QgsProcessingStringWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingNumericWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingDistanceWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingRangeWidgetWrapper() );
}

QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry()
Expand Down
142 changes: 142 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
Expand Up @@ -799,4 +799,146 @@ QVariant QgsProcessingDistanceWidgetWrapper::widgetValue() const
}


//
// QgsProcessingRangeWidgetWrapper
//

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

}

QWidget *QgsProcessingRangeWidgetWrapper::createWidget()
{
const QgsProcessingParameterRange *rangeDef = static_cast< const QgsProcessingParameterRange * >( parameterDefinition() );
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Modeler:
case QgsProcessingGui::Batch:
{
QHBoxLayout *layout = new QHBoxLayout();

mMinSpinBox = new QgsDoubleSpinBox();
mMaxSpinBox = new QgsDoubleSpinBox();

mMinSpinBox->setExpressionsEnabled( true );
mMinSpinBox->setShowClearButton( false );
mMaxSpinBox->setExpressionsEnabled( true );
mMaxSpinBox->setShowClearButton( false );

QLabel *minLabel = new QLabel( tr( "Min" ) );
layout->addWidget( minLabel );
layout->addWidget( mMinSpinBox, 1 );

QLabel *maxLabel = new QLabel( tr( "Max" ) );
layout->addWidget( maxLabel );
layout->addWidget( mMaxSpinBox, 1 );

QWidget *w = new QWidget();
layout->setMargin( 0 );
layout->setContentsMargins( 0, 0, 0, 0 );
w->setLayout( layout );

if ( rangeDef->dataType() == QgsProcessingParameterNumber::Double )
{
mMinSpinBox->setDecimals( 6 );
mMaxSpinBox->setDecimals( 6 );
}
else
{
mMinSpinBox->setDecimals( 0 );
mMaxSpinBox->setDecimals( 0 );
}

mMinSpinBox->setMinimum( -99999999.999999 );
mMaxSpinBox->setMinimum( -99999999.999999 );
mMinSpinBox->setMaximum( 99999999.999999 );
mMaxSpinBox->setMaximum( 99999999.999999 );

w->setToolTip( parameterDefinition()->toolTip() );

connect( mMinSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( const double v )
{
mBlockChangedSignal++;
if ( v > mMaxSpinBox->value() )
mMaxSpinBox->setValue( v );
mBlockChangedSignal--;

if ( !mBlockChangedSignal )
emit widgetValueHasChanged( this );
} );
connect( mMaxSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( const double v )
{
mBlockChangedSignal++;
if ( v < mMinSpinBox->value() )
mMinSpinBox->setValue( v );
mBlockChangedSignal--;

if ( !mBlockChangedSignal )
emit widgetValueHasChanged( this );
} );

return w;
};
}
return nullptr;
}

void QgsProcessingRangeWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
const QList< double > v = QgsProcessingParameters::parameterAsRange( parameterDefinition(), value, context );
if ( v.empty() )
return;

mBlockChangedSignal++;
mMinSpinBox->setValue( v.at( 0 ) );
if ( v.count() >= 2 )
mMaxSpinBox->setValue( v.at( 1 ) );
mBlockChangedSignal--;

if ( !mBlockChangedSignal )
emit widgetValueHasChanged( this );
}

QVariant QgsProcessingRangeWidgetWrapper::widgetValue() const
{
return QStringLiteral( "%1,%2" ).arg( mMinSpinBox->value() ).arg( mMaxSpinBox->value() );
}

QStringList QgsProcessingRangeWidgetWrapper::compatibleParameterTypes() const
{
return QStringList()
<< QgsProcessingParameterRange::typeName()
<< QgsProcessingParameterString::typeName();
}

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

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

QString QgsProcessingRangeWidgetWrapper::modelerExpressionFormatString() const
{
return tr( "string as two comma delimited floats, e.g. '1,10'" );
}

QString QgsProcessingRangeWidgetWrapper::parameterType() const
{
return QgsProcessingParameterRange::typeName();
}

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


///@endcond PRIVATE

38 changes: 38 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.h
Expand Up @@ -215,6 +215,44 @@ class GUI_EXPORT QgsProcessingDistanceWidgetWrapper : public QgsProcessingNumeri
friend class TestProcessingGui;
};


class GUI_EXPORT QgsProcessingRangeWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface
{
Q_OBJECT

public:

QgsProcessingRangeWidgetWrapper( 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;

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;

protected:

QgsDoubleSpinBox *mMinSpinBox = nullptr;
QgsDoubleSpinBox *mMaxSpinBox = nullptr;

private:

int mBlockChangedSignal = 0;

friend class TestProcessingGui;
};

///@endcond PRIVATE

#endif // QGSPROCESSINGWIDGETWRAPPERIMPL_H
112 changes: 112 additions & 0 deletions tests/src/gui/testprocessinggui.cpp
Expand Up @@ -150,6 +150,7 @@ class TestProcessingGui : public QObject
void testNumericWrapperDouble();
void testNumericWrapperInt();
void testDistanceWrapper();
void testRangeWrapper();
};

void TestProcessingGui::initTestCase()
Expand Down Expand Up @@ -1328,5 +1329,116 @@ void TestProcessingGui::testDistanceWrapper()
delete l;
}

void TestProcessingGui::testRangeWrapper()
{
auto testWrapper = []( QgsProcessingGui::WidgetType type )
{
QgsProcessingContext context;

QgsProcessingParameterRange param( QStringLiteral( "range" ), QStringLiteral( "range" ), QgsProcessingParameterNumber::Double );
param.setDefaultValue( QStringLiteral( "0.0,100.0" ) );
QgsProcessingRangeWidgetWrapper wrapper( &param, type );

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

// initial value
QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "0,100" ) );

QVERIFY( wrapper.mMinSpinBox->expressionsEnabled() );
QVERIFY( wrapper.mMaxSpinBox->expressionsEnabled() );
QCOMPARE( wrapper.mMinSpinBox->decimals(), 6 ); // you can change this, if it's an intentional change!
QCOMPARE( wrapper.mMaxSpinBox->decimals(), 6 ); // you can change this, if it's an intentional change!
QGSCOMPARENEAR( wrapper.mMinSpinBox->minimum(), -99999999.999999, 1 );
QGSCOMPARENEAR( wrapper.mMaxSpinBox->minimum(), -99999999.999999, 1 );
QGSCOMPARENEAR( wrapper.mMinSpinBox->maximum(), 99999999.999999, 1 );
QGSCOMPARENEAR( wrapper.mMaxSpinBox->maximum(), 99999999.999999, 1 );

QSignalSpy spy( &wrapper, &QgsProcessingRangeWidgetWrapper::widgetValueHasChanged );
wrapper.setWidgetValue( QVariantList() << 5 << 7, context );
QCOMPARE( spy.count(), 1 );
QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "5,7" ) );
QCOMPARE( wrapper.mMinSpinBox->value(), 5.0 );
QCOMPARE( wrapper.mMaxSpinBox->value(), 7.0 );
wrapper.setWidgetValue( QStringLiteral( "28.1,36.5" ), context );
QCOMPARE( spy.count(), 2 );
QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "28.1,36.5" ) );
QCOMPARE( wrapper.mMinSpinBox->value(), 28.1 );
QCOMPARE( wrapper.mMaxSpinBox->value(), 36.5 );

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

// check signal
wrapper.mMinSpinBox->setValue( 7.0 );
QCOMPARE( spy.count(), 3 );
QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "7,36.5" ) );
wrapper.mMaxSpinBox->setValue( 9.0 );
QCOMPARE( spy.count(), 4 );
QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "7,9" ) );

// check that min/max are mutually adapted
wrapper.setParameterValue( QStringLiteral( "200.0,100.0" ), context );
QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "100,100" ) );

wrapper.mMaxSpinBox->setValue( 50 );
QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "50,50" ) );
wrapper.mMinSpinBox->setValue( 100 );
QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "100,100" ) );

delete w;

// ints
QgsProcessingParameterRange param2( QStringLiteral( "range" ), QStringLiteral( "range" ), QgsProcessingParameterNumber::Integer );
param2.setDefaultValue( QStringLiteral( "0.1,100.1" ) );

QgsProcessingRangeWidgetWrapper wrapper2( &param2, type );

w = wrapper2.createWrappedWidget( context );
QVERIFY( w );
QCOMPARE( wrapper2.mMinSpinBox->decimals(), 0 );
QCOMPARE( wrapper2.mMaxSpinBox->decimals(), 0 ); // you can't change this, vampire worms will bite you at night if you do

// check initial value
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "0,100" ) );
// check rounding
wrapper2.setParameterValue( QStringLiteral( "100.1,200.1" ), context );
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,200" ) );
wrapper2.setParameterValue( QStringLiteral( "100.6,200.6" ), context );
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "101,201" ) );
// check set/get
wrapper2.setParameterValue( QStringLiteral( "100.1,200.1" ), context );
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,200" ) );
// check that min/max are mutually adapted
wrapper2.setParameterValue( QStringLiteral( "200.1,100.1" ), context );
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,100" ) );
wrapper2.mMaxSpinBox->setValue( 50.1 );
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "50,50" ) );
wrapper2.mMinSpinBox->setValue( 100.1 );
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,100" ) );

delete w;
};

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

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

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

QGSTEST_MAIN( TestProcessingGui )
#include "testprocessinggui.moc"

0 comments on commit 1ec3654

Please sign in to comment.