Skip to content

Commit

Permalink
[FEATURE] Evaluate expressions entered in QgsDoubleSpinBox
Browse files Browse the repository at this point in the history
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 7400106
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 6 deletions.
9 changes: 9 additions & 0 deletions python/core/qgsexpression.sip
Expand Up @@ -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,
Expand Down
19 changes: 18 additions & 1 deletion python/gui/editorwidgets/qgsdoublespinbox.sip
Expand Up @@ -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 );
Expand Down
5 changes: 3 additions & 2 deletions python/gui/editorwidgets/qgsspinbox.sip
Expand Up @@ -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() );
Expand Down
21 changes: 21 additions & 0 deletions src/core/qgsexpression.cpp
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/core/qgsexpression.h
Expand Up @@ -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,
Expand Down
65 changes: 64 additions & 1 deletion src/gui/editorwidgets/qgsdoublespinbox.cpp
Expand Up @@ -20,7 +20,7 @@
#include <QToolButton>

#include "qgsdoublespinbox.h"

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

Expand All @@ -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" ) );
Expand All @@ -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 );
Expand Down Expand Up @@ -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 );
Expand Down
23 changes: 22 additions & 1 deletion src/gui/editorwidgets/qgsdoublespinbox.h
Expand Up @@ -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
Expand All @@ -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 );
Expand All @@ -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
3 changes: 2 additions & 1 deletion src/gui/editorwidgets/qgsspinbox.h
Expand Up @@ -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() );
Expand Down
13 changes: 13 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
Expand Down Expand Up @@ -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 )
Expand Down
1 change: 1 addition & 0 deletions tests/src/gui/CMakeLists.txt
Expand Up @@ -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.