Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] add "not set" support for range parameter (fix #29374)
  • Loading branch information
alexbruy committed Jan 2, 2020
1 parent 6500050 commit 9f66bf2
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 13 deletions.
21 changes: 18 additions & 3 deletions src/core/processing/qgsprocessingparameters.cpp
Expand Up @@ -1534,9 +1534,23 @@ QList<double> QgsProcessingParameters::parameterAsRange( const QgsProcessingPara
}

if ( resultStringList.size() < 2 )
return QList< double >() << 0.0 << 0.0;
return QList< double >() << std::numeric_limits<double>::quiet_NaN() << std::numeric_limits<double>::quiet_NaN() ;

return QList< double >() << resultStringList.at( 0 ).toDouble() << resultStringList.at( 1 ).toDouble();
QList< double > result;
bool ok = false;
double n = resultStringList.at( 0 ).toDouble( &ok );
if ( ok )
result << n;
else
result << std::numeric_limits<double>::quiet_NaN() ;
ok = false;
n = resultStringList.at( 1 ).toDouble( &ok );
if ( ok )
result << n;
else
result << std::numeric_limits<double>::quiet_NaN() ;

return result;
}

QStringList QgsProcessingParameters::parameterAsFields( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context )
Expand Down Expand Up @@ -3179,7 +3193,8 @@ bool QgsProcessingParameterRange::fromVariantMap( const QVariantMap &map )

QgsProcessingParameterRange *QgsProcessingParameterRange::fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition )
{
return new QgsProcessingParameterRange( name, description, QgsProcessingParameterNumber::Double, definition.isEmpty() ? QVariant() : definition, isOptional );
return new QgsProcessingParameterRange( name, description, QgsProcessingParameterNumber::Double, definition.isEmpty() ? QVariant()
: ( definition.toLower().trimmed() == QStringLiteral( "none" ) ? QVariant() : definition ), isOptional );
}

QgsProcessingParameterRasterLayer::QgsProcessingParameterRasterLayer( const QString &name, const QString &description, const QVariant &defaultValue, bool optional )
Expand Down
80 changes: 70 additions & 10 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
Expand Up @@ -1110,12 +1110,28 @@ QWidget *QgsProcessingRangeWidgetWrapper::createWidget()
mMinSpinBox->setMaximum( 99999999.999999 );
mMaxSpinBox->setMaximum( 99999999.999999 );

if ( rangeDef->flags() & QgsProcessingParameterDefinition::FlagOptional )
{
mAllowingNull = true;

const double min = mMinSpinBox->minimum() - 1;
mMinSpinBox->setMinimum( min );
mMaxSpinBox->setMinimum( min );
mMinSpinBox->setValue( min );
mMaxSpinBox->setValue( min );

mMinSpinBox->setShowClearButton( true );
mMaxSpinBox->setShowClearButton( true );
mMinSpinBox->setSpecialValueText( tr( "Not set" ) );
mMaxSpinBox->setSpecialValueText( tr( "Not set" ) );
}

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

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

Expand All @@ -1125,7 +1141,7 @@ QWidget *QgsProcessingRangeWidgetWrapper::createWidget()
connect( mMaxSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( const double v )
{
mBlockChangedSignal++;
if ( v < mMinSpinBox->value() )
if ( !mAllowingNull && v < mMinSpinBox->value() )
mMinSpinBox->setValue( v );
mBlockChangedSignal--;

Expand All @@ -1142,22 +1158,66 @@ QWidget *QgsProcessingRangeWidgetWrapper::createWidget()
void QgsProcessingRangeWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
const QList< double > v = QgsProcessingParameters::parameterAsRange( parameterDefinition(), value, context );
if ( v.empty() )
return;
if ( mAllowingNull && v.empty() )
{
mMinSpinBox->clear();
mMaxSpinBox->clear();
}
else
{
if ( v.empty() )
return;

mBlockChangedSignal++;
mMinSpinBox->setValue( v.at( 0 ) );
if ( v.count() >= 2 )
mMaxSpinBox->setValue( v.at( 1 ) );
mBlockChangedSignal--;
if ( mAllowingNull )
{
mBlockChangedSignal++;
if ( std::isnan( v.at( 0 ) ) )
mMinSpinBox->clear();
else
mMinSpinBox->setValue( v.at( 0 ) );

if ( v.count() >= 2 )
{
if ( std::isnan( v.at( 1 ) ) )
mMaxSpinBox->clear();
else
mMaxSpinBox->setValue( v.at( 1 ) );
}
mBlockChangedSignal--;
}
else
{
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() );
if ( mAllowingNull )
{
QString value;
if ( qgsDoubleNear( mMinSpinBox->value(), mMinSpinBox->minimum() ) )
value = QStringLiteral( "None" );
else
value = QStringLiteral( "%1" ).arg( mMinSpinBox->value() );

if ( qgsDoubleNear( mMaxSpinBox->value(), mMaxSpinBox->minimum() ) )
value += QStringLiteral( ",None" );
else
value += QStringLiteral( ",%1" ).arg( mMaxSpinBox->value() );

return value;
}
else
return QStringLiteral( "%1,%2" ).arg( mMinSpinBox->value() ).arg( mMaxSpinBox->value() );
}

QStringList QgsProcessingRangeWidgetWrapper::compatibleParameterTypes() const
Expand Down
1 change: 1 addition & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.h
Expand Up @@ -378,6 +378,7 @@ class GUI_EXPORT QgsProcessingRangeWidgetWrapper : public QgsAbstractProcessingP
private:

int mBlockChangedSignal = 0;
bool mAllowingNull = false;

friend class TestProcessingGui;
};
Expand Down
47 changes: 47 additions & 0 deletions tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -3964,6 +3964,53 @@ void TestQgsProcessing::parameterRange()
QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
QCOMPARE( fromCode->flags(), def->flags() );
QCOMPARE( fromCode->defaultValue(), def->defaultValue() );

// optional, no default value
def.reset( new QgsProcessingParameterRange( "optional", QString(), QgsProcessingParameterNumber::Double, QVariant(), true ) );
QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) );
QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 ) );
QVERIFY( def->checkValueIsAcceptable( "" ) );
QVERIFY( def->checkValueIsAcceptable( QVariant() ) );

params.insert( "optional", QVariant() );
range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
QVERIFY( std::isnan( range.at( 0 ) ) );
QVERIFY( std::isnan( range.at( 1 ) ) );

params.insert( "optional", QStringLiteral( "None,2" ) );
range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
QVERIFY( std::isnan( range.at( 0 ) ) );
QGSCOMPARENEAR( range.at( 1 ), 2, 0.001 );

params.insert( "optional", QStringLiteral( "1.2,None" ) );
range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
QGSCOMPARENEAR( range.at( 0 ), 1.2, 0.001 );
QVERIFY( std::isnan( range.at( 1 ) ) );

params.insert( "optional", QStringLiteral( "None,None" ) );
range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
QVERIFY( std::isnan( range.at( 0 ) ) );
QVERIFY( std::isnan( range.at( 1 ) ) );

params.insert( "optional", QStringLiteral( "None" ) );
range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
QVERIFY( std::isnan( range.at( 0 ) ) );
QVERIFY( std::isnan( range.at( 1 ) ) );

params.insert( "optional", QVariant() );
range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
QVERIFY( std::isnan( range.at( 0 ) ) );
QVERIFY( std::isnan( range.at( 1 ) ) );

pythonCode = def->asPythonString();
QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterRange('optional', '', optional=True, type=QgsProcessingParameterNumber.Double, defaultValue=None)" ) );

fromCode.reset( dynamic_cast< QgsProcessingParameterRange * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##optional=optional range None" ) ) ) );
QVERIFY( fromCode.get() );
QCOMPARE( fromCode->name(), def->name() );
QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
QCOMPARE( fromCode->flags(), def->flags() );
QVERIFY( !fromCode->defaultValue().isValid() );
}

void TestQgsProcessing::parameterRasterLayer()
Expand Down
22 changes: 22 additions & 0 deletions tests/src/gui/testprocessinggui.cpp
Expand Up @@ -1974,6 +1974,28 @@ void TestProcessingGui::testRangeWrapper()
QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,100" ) );

delete w;

// optional
QgsProcessingParameterRange paramOptional( QStringLiteral( "range" ), QStringLiteral( "range" ), QgsProcessingParameterNumber::Double, QVariant(), true );

QgsProcessingRangeWidgetWrapper wrapperOptional( &paramOptional, type );

w = wrapperOptional.createWrappedWidget( context );
QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );
wrapperOptional.setParameterValue( QStringLiteral( "1,100" ), context );
QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "1,100" ) );
wrapperOptional.setParameterValue( QStringLiteral( "None,100" ), context );
QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,100" ) );
wrapperOptional.setParameterValue( QStringLiteral( "1,None" ), context );
QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "1,None" ) );
wrapperOptional.setParameterValue( QStringLiteral( "None,None" ), context );
QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );
wrapperOptional.setParameterValue( QStringLiteral( "None" ), context );
QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );
wrapperOptional.setParameterValue( QVariant(), context );
QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );

delete w;
};

// standard wrapper
Expand Down

0 comments on commit 9f66bf2

Please sign in to comment.