Skip to content
Permalink
Browse files

[FEATURE] Evaluate expressions entered in QgsDoubleSpinBox

Allows entry of QGIS expressions into the spin box. The expression
is evaluated on enter or loss of focus and then discarded.

(refs #10544)
  • Loading branch information
nyalldawson committed Dec 5, 2014
1 parent 8a182b7 commit 7400106e41f9e52b85f58ab98e92b4d79034f2e3
@@ -110,6 +110,15 @@ class QgsExpression
const QgsDistanceArea* distanceArea = 0
);

/**Attempts to evaluate a text string as an expression to a resultant double
* value.
* @param text text to evaluate as expression
* @param fallbackValue value to return if text can not be evaluated as a double
* @returns evaluated double value, or fallback value
* @note added in QGIS 2.7
*/
static double evaluateToDouble( const QString& text, const double fallbackValue );

enum UnaryOperator
{
uoNot,
@@ -19,24 +19,41 @@ class QgsDoubleSpinBox : QDoubleSpinBox
void setShowClearButton( const bool showClearButton );
bool showClearButton() const;

/**Sets if the widget will allow entry of simple expressions, which are
* evaluated and then discarded.
* @param enabled set to true to allow expression entry
* @note added in QGIS 2.7
*/
void setExpressionsEnabled( const bool enabled );
/**Returns whether the widget will allow entry of simple expressions, which are
* evaluated and then discarded.
* @returns true if spin box allows expression entry
* @note added in QGIS 2.7
*/
bool expressionsEnabled() const;

//! Set the current value to the value defined by the clear value.
virtual void clear();

/**
* @brief setClearValue defines the clear value as a custom value and will automatically set the clear value mode to CustomValue
* @param defines the numerical value used as the clear value
* @param customValue defines the numerical value used as the clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValue( double customValue, QString clearValueText = QString() );
/**
* @brief setClearValueMode defines if the clear value should be the minimum or maximum values of the widget or a custom value
* @param mode mode to user for clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValueMode( ClearValueMode mode, QString clearValueText = QString() );

//! returns the value used when clear() is called.
double clearValue() const;

virtual double valueFromText( const QString & text ) const;
virtual QValidator::State validate( QString & input, int & pos ) const;

protected:
virtual void resizeEvent( QResizeEvent* event );
virtual void changeEvent( QEvent* event );
@@ -23,13 +23,14 @@ class QgsSpinBox : QSpinBox
virtual void clear();

/**
* @brief setClearValue defines the clear value as a custom value and will automatically set the clear value mode to CustomValue
* @param defines the numerical value used as the clear value
* @brief setClearValue defines the clear value for the widget and will automatically set the clear value mode to CustomValue
* @param customValue defines the numerical value used as the clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValue( int customValue, QString clearValueText = QString() );
/**
* @brief setClearValueMode defines if the clear value should be the minimum or maximum values of the widget or a custom value
* @param mode mode to user for clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValueMode( ClearValueMode mode, QString clearValueText = QString() );
@@ -2074,6 +2074,27 @@ QString QgsExpression::replaceExpressionText( const QString &action, const QgsFe
return expr_action;
}

double QgsExpression::evaluateToDouble( const QString &text, const double fallbackValue )
{
bool ok;
//first test if text is directly convertible to double
double convertedValue = text.toDouble( &ok );
if ( ok )
{
return convertedValue;
}

//otherwise try to evalute as expression
QgsExpression expr( text );
QVariant result = expr.evaluate();
convertedValue = result.toDouble( &ok );
if ( expr.hasEvalError() || !ok )
{
return fallbackValue;
}
return convertedValue;
}


///////////////////////////////////////////////
// nodes
@@ -200,6 +200,16 @@ class CORE_EXPORT QgsExpression
const QMap<QString, QVariant> *substitutionMap = 0,
const QgsDistanceArea* distanceArea = 0
);

/**Attempts to evaluate a text string as an expression to a resultant double
* value.
* @param text text to evaluate as expression
* @param fallbackValue value to return if text can not be evaluated as a double
* @returns evaluated double value, or fallback value
* @note added in QGIS 2.7
*/
static double evaluateToDouble( const QString& text, const double fallbackValue );

enum UnaryOperator
{
uoNot,
@@ -20,7 +20,7 @@
#include <QToolButton>

#include "qgsdoublespinbox.h"

#include "qgsexpression.h"
#include "qgsapplication.h"
#include "qgslogger.h"

@@ -29,6 +29,7 @@ QgsDoubleSpinBox::QgsDoubleSpinBox( QWidget *parent )
, mShowClearButton( true )
, mClearValueMode( MinimumValue )
, mCustomClearValue( 0.0 )
, mExpressionsEnabled( true )
{
mClearButton = new QToolButton( this );
mClearButton->setIcon( QgsApplication::getThemeIcon( "/mIconClear.svg" ) );
@@ -51,6 +52,11 @@ void QgsDoubleSpinBox::setShowClearButton( const bool showClearButton )
mClearButton->setVisible( shouldShowClearForValue( value() ) );
}

void QgsDoubleSpinBox::setExpressionsEnabled( const bool enabled )
{
mExpressionsEnabled = enabled;
}

void QgsDoubleSpinBox::changeEvent( QEvent *event )
{
QDoubleSpinBox::changeEvent( event );
@@ -105,6 +111,63 @@ double QgsDoubleSpinBox::clearValue() const
return mCustomClearValue;
}

QString QgsDoubleSpinBox::stripped( const QString &originalText ) const
{
//adapted from QAbstractSpinBoxPrivate::stripped
//trims whitespace, prefix and suffix from spin box text
QString text = originalText;
if ( specialValueText().size() == 0 || text != specialValueText() )
{
int from = 0;
int size = text.size();
bool changed = false;
if ( prefix().size() && text.startsWith( prefix() ) )
{
from += prefix().size();
size -= from;
changed = true;
}
if ( suffix().size() && text.endsWith( suffix() ) )
{
size -= suffix().size();
changed = true;
}
if ( changed )
text = text.mid( from, size );
}

text = text.trimmed();

return text;
}

double QgsDoubleSpinBox::valueFromText( const QString &text ) const
{
if ( !mExpressionsEnabled )
{
return QDoubleSpinBox::valueFromText( text );
}

QString trimmedText = stripped( text );
if ( trimmedText.isEmpty() )
{
return clearValue();
}

return QgsExpression::evaluateToDouble( trimmedText, value() );
}

QValidator::State QgsDoubleSpinBox::validate( QString &input, int &pos ) const
{
if ( !mExpressionsEnabled )
{
QValidator::State r = QDoubleSpinBox::validate( input, pos );
return r;
}

return QValidator::Acceptable;
}

int QgsDoubleSpinBox::frameWidth() const
{
return style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
@@ -28,6 +28,7 @@ class GUI_EXPORT QgsDoubleSpinBox : public QDoubleSpinBox
{
Q_OBJECT
Q_PROPERTY( bool showClearButton READ showClearButton WRITE setShowClearButton )
Q_PROPERTY( bool expressionsEnabled READ expressionsEnabled WRITE setExpressionsEnabled )

public:
enum ClearValueMode
@@ -43,24 +44,41 @@ class GUI_EXPORT QgsDoubleSpinBox : public QDoubleSpinBox
void setShowClearButton( const bool showClearButton );
bool showClearButton() const {return mShowClearButton;}

/**Sets if the widget will allow entry of simple expressions, which are
* evaluated and then discarded.
* @param enabled set to true to allow expression entry
* @note added in QGIS 2.7
*/
void setExpressionsEnabled( const bool enabled );
/**Returns whether the widget will allow entry of simple expressions, which are
* evaluated and then discarded.
* @returns true if spin box allows expression entry
* @note added in QGIS 2.7
*/
bool expressionsEnabled() const {return mExpressionsEnabled;}

//! Set the current value to the value defined by the clear value.
virtual void clear();

/**
* @brief setClearValue defines the clear value as a custom value and will automatically set the clear value mode to CustomValue
* @param defines the numerical value used as the clear value
* @param customValue defines the numerical value used as the clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValue( double customValue, QString clearValueText = QString() );
/**
* @brief setClearValueMode defines if the clear value should be the minimum or maximum values of the widget or a custom value
* @param mode mode to user for clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValueMode( ClearValueMode mode, QString clearValueText = QString() );

//! returns the value used when clear() is called.
double clearValue() const;

virtual double valueFromText( const QString & text ) const;
virtual QValidator::State validate( QString & input, int & pos ) const;

protected:
virtual void resizeEvent( QResizeEvent* event );
virtual void changeEvent( QEvent* event );
@@ -76,7 +94,10 @@ class GUI_EXPORT QgsDoubleSpinBox : public QDoubleSpinBox
ClearValueMode mClearValueMode;
double mCustomClearValue;

bool mExpressionsEnabled;

QToolButton* mClearButton;
QString stripped( const QString &originalText ) const;
};

#endif // QGSDOUBLESPPINBOX_H
@@ -48,12 +48,13 @@ class GUI_EXPORT QgsSpinBox : public QSpinBox

/**
* @brief setClearValue defines the clear value for the widget and will automatically set the clear value mode to CustomValue
* @param defines the numerical value used as the clear value
* @param customValue defines the numerical value used as the clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValue( int customValue, QString clearValueText = QString() );
/**
* @brief setClearValueMode defines if the clear value should be the minimum or maximum values of the widget or a custom value
* @param mode mode to user for clear value
* @param clearValueText is the text displayed when the spin box is at the clear value. If not specified, no special value text is used.
*/
void setClearValueMode( ClearValueMode mode, QString clearValueText = QString() );
@@ -285,6 +285,7 @@ class TestQgsExpression: public QObject
QTest::newRow( "max(1,3.5,-2.1)" ) << "max(1,3.5,-2.1)" << false << QVariant( 3.5 );
QTest::newRow( "min(-1.5)" ) << "min(-1.5)" << false << QVariant( -1.5 );
QTest::newRow( "min(-16.6,3.5,-2.1)" ) << "min(-16.6,3.5,-2.1)" << false << QVariant( -16.6 );
QTest::newRow( "min(5,3.5,-2.1)" ) << "min(5,3.5,-2.1)" << false << QVariant( -2.1 );
QTest::newRow( "clamp(-2,1,5)" ) << "clamp(-2,1,5)" << false << QVariant( 1.0 );
QTest::newRow( "clamp(-2,-10,5)" ) << "clamp(-2,-10,5)" << false << QVariant( -2.0 );
QTest::newRow( "clamp(-2,100,5)" ) << "clamp(-2,100,5)" << false << QVariant( 5.0 );
@@ -327,6 +328,7 @@ class TestQgsExpression: public QObject
QTest::newRow( "substr" ) << "substr('HeLLo', 3,2)" << false << QVariant( "LL" );
QTest::newRow( "substr outside" ) << "substr('HeLLo', -5,2)" << false << QVariant( "" );
QTest::newRow( "regexp_substr" ) << "regexp_substr('abc123','(\\\\d+)')" << false << QVariant( "123" );
QTest::newRow( "regexp_substr no hit" ) << "regexp_substr('abcdef','(\\\\d+)')" << false << QVariant( "" );
QTest::newRow( "regexp_substr invalid" ) << "regexp_substr('abc123','([[[')" << true << QVariant();
QTest::newRow( "strpos" ) << "strpos('Hello World','World')" << false << QVariant( 6 );
QTest::newRow( "strpos outside" ) << "strpos('Hello World','blah')" << false << QVariant( -1 );
@@ -345,6 +347,8 @@ class TestQgsExpression: public QObject
QTest::newRow( "wordwrap" ) << "wordwrap('university of qgis',-3,' ')" << false << QVariant( "university\nof qgis" );
QTest::newRow( "wordwrap" ) << "wordwrap('university of qgis\nsupports many multiline',-5,' ')" << false << QVariant( "university\nof qgis\nsupports\nmany multiline" );
QTest::newRow( "format" ) << "format('%1 %2 %3 %1', 'One', 'Two', 'Three')" << false << QVariant( "One Two Three One" );
QTest::newRow( "concat" ) << "concat('a', 'b', 'c', 'd')" << false << QVariant( "abcd" );
QTest::newRow( "concat single" ) << "concat('a')" << false << QVariant( "a" );

// implicit conversions
QTest::newRow( "implicit int->text" ) << "length(123)" << false << QVariant( 3 );
@@ -977,6 +981,15 @@ class TestQgsExpression: public QObject
lst << i;
QtConcurrent::blockingMap( lst, _parseAndEvalExpr );
}

void evaluateToDouble()
{
QCOMPARE( QgsExpression::evaluateToDouble( QString( "5" ), 0.0 ), 5.0 );
QCOMPARE( QgsExpression::evaluateToDouble( QString( "5+6" ), 0.0 ), 11.0 );
QCOMPARE( QgsExpression::evaluateToDouble( QString( "5*" ), 7.0 ), 7.0 );
QCOMPARE( QgsExpression::evaluateToDouble( QString( "a" ), 9.0 ), 9.0 );
QCOMPARE( QgsExpression::evaluateToDouble( QString(), 9.0 ), 9.0 );
}
};

QTEST_MAIN( TestQgsExpression )
@@ -119,6 +119,7 @@ ADD_QGIS_TEST(zoomtest testqgsmaptoolzoom.cpp)
ADD_QGIS_TEST(projectionissues testprojectionissues.cpp)
ADD_QGIS_TEST(scalecombobox testqgsscalecombobox.cpp)
ADD_QGIS_TEST(dualviewtest testqgsdualview.cpp )
ADD_QGIS_TEST(doublespinbox testqgsdoublespinbox.cpp)
ADD_QGIS_TEST(rubberbandtest testqgsrubberband.cpp )
ADD_QGIS_TEST(mapcanvastest testqgsmapcanvas.cpp )

0 comments on commit 7400106

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