Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Port conditional styles to expression contexts
  • Loading branch information
nyalldawson committed Aug 22, 2015
1 parent 440926b commit 4bf8b13
Show file tree
Hide file tree
Showing 16 changed files with 306 additions and 74 deletions.
6 changes: 3 additions & 3 deletions python/core/qgsconditionalstyle.sip
Expand Up @@ -13,11 +13,11 @@ class QgsConditionalStyle

/**
* @brief Check if the rule matches using the given value and feature
* @param feature The feature to match the values from.
* @param value The current value being checked. The "value" variable from the context is replaced with this value.
* @param context Expression context for evaluating rule expression
* @return True of the rule matches against the given feature
*/
bool matches( QVariant value, QgsFeature *feature = 0 );

bool matches( QVariant value, QgsExpressionContext& context ) const;
/**
* @brief Render a preview icon of the rule.
* @return QPixmap preview of the style
Expand Down
5 changes: 5 additions & 0 deletions python/core/qgsexpressioncontext.sip
Expand Up @@ -74,6 +74,8 @@ class QgsExpressionContextScope
*/
QgsExpressionContextScope( const QString& name = QString() );

/** Copy constructor
*/
QgsExpressionContextScope( const QgsExpressionContextScope& other );

~QgsExpressionContextScope();
Expand Down Expand Up @@ -199,6 +201,9 @@ class QgsExpressionContext
public:

QgsExpressionContext( );

/** Copy constructor
*/
QgsExpressionContext( const QgsExpressionContext& other );

~QgsExpressionContext();
Expand Down
28 changes: 20 additions & 8 deletions python/core/qgsfielduiproperties.sip
Expand Up @@ -23,23 +23,35 @@ class QgsFieldUIProperties
* @brief Returns the condtional styles set for the field UI properties
* @return A list of condtional styles that have been set.
*/
const QList<QgsConditionalStyle> getConditionalStyles();
const QList<QgsConditionalStyle> getConditionalStyles() const;

/**
* @brief Find and return the matching style for the value and feature.
* If no match is found a invalid QgsCondtionalStyle is return.
*
* @return A condtional style that matches the value and feature.
* Check with QgsCondtionalStyle::isValid()
* @brief Find and return all matching styles for a value and context.
* If no match is found an empty list is returned.
* @param value current cell value
* @param context expression context for evaluating conditional rules
* @return A list of conditional styles that matches the value and context.
* @see matchingConditionalStyle
*/
const QgsConditionalStyle matchingConditionalStyle( QVariant value, QgsFeature* feature );
QList<QgsConditionalStyle> matchingConditionalStyles(QVariant value, QgsExpressionContext& context ) const;

/**
* @brief Find and return the matching style for the value and context.
* If no match is found a invalid QgsConditionalStyle is return.
* @param value current cell value
* @param context expression context for evaluating conditional rules
* @return A conditional style that matches the value and context.
* Check with QgsConditionalStyle::isValid()
* @see matchingConditionalStyles
*/
QgsConditionalStyle matchingConditionalStyle( QVariant value, QgsExpressionContext& context ) const;

/** Reads field ui properties specific state from Dom node.
*/
virtual bool readXml( const QDomNode& node );

/** Write field ui properties specific state from Dom node.
*/
virtual bool writeXml( QDomNode & node, QDomDocument & doc );
virtual bool writeXml( QDomNode & node, QDomDocument & doc ) const;

};
14 changes: 4 additions & 10 deletions src/core/qgsconditionalstyle.cpp
Expand Up @@ -67,17 +67,11 @@ void QgsConditionalStyle::setSymbol( QgsSymbolV2* value )
}
}

bool QgsConditionalStyle::matches( QVariant value, QgsFeature *feature )
bool QgsConditionalStyle::matches( QVariant value, QgsExpressionContext& context ) const
{
// TODO Replace with expression context
QgsExpression exp( QString( mRule ).replace( "@value", value.toString() ) );
if ( feature )
{
return exp.evaluate( feature, *feature->fields() ).toBool();
}
{
return exp.evaluate().toBool();
}
QgsExpression exp( mRule );
context.lastScope()->setVariable( "value", value );
return exp.evaluate( &context ).toBool();
}

QPixmap QgsConditionalStyle::renderPreview()
Expand Down
6 changes: 3 additions & 3 deletions src/core/qgsconditionalstyle.h
Expand Up @@ -25,11 +25,11 @@ class CORE_EXPORT QgsConditionalStyle

/**
* @brief Check if the rule matches using the given value and feature
* @param value The current value being checked. \@value is replaced in the rule with this value.
* @param feature The feature to match the values from.
* @param value The current value being checked. The "value" variable from the context is replaced with this value.
* @param context Expression context for evaluating rule expression
* @return True of the rule matches against the given feature
*/
bool matches( QVariant value, QgsFeature *feature = 0 );
bool matches( QVariant value, QgsExpressionContext& context ) const;

/**
* @brief Render a preview icon of the rule.
Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsexpression.h
Expand Up @@ -326,6 +326,9 @@ class CORE_EXPORT QgsExpression
static const char* UnaryOperatorText[];

typedef QVariant( *FcnEval )( const QVariantList& values, const QgsFeature* f, QgsExpression* parent );

/** Function definition for evaluation against an expression context
*/
typedef QVariant( *FcnEvalContext )( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent );

/**
Expand Down
6 changes: 6 additions & 0 deletions src/core/qgsexpressioncontext.h
Expand Up @@ -101,6 +101,8 @@ class CORE_EXPORT QgsExpressionContextScope
*/
QgsExpressionContextScope( const QString& name = QString() );

/** Copy constructor
*/
QgsExpressionContextScope( const QgsExpressionContextScope& other );

QgsExpressionContextScope& operator=( const QgsExpressionContextScope& other );
Expand Down Expand Up @@ -229,7 +231,11 @@ class CORE_EXPORT QgsExpressionContext
public:

QgsExpressionContext( ) {}

/** Copy constructor
*/
QgsExpressionContext( const QgsExpressionContext& other );

QgsExpressionContext& operator=( const QgsExpressionContext& other );

~QgsExpressionContext();
Expand Down
12 changes: 6 additions & 6 deletions src/core/qgsfielduiproperties.cpp
Expand Up @@ -13,33 +13,33 @@ void QgsFieldUIProperties::setConditionalStyles( QList<QgsConditionalStyle> styl
mStyles = styles;
}

QList<QgsConditionalStyle> QgsFieldUIProperties::getConditionalStyles()
QList<QgsConditionalStyle> QgsFieldUIProperties::getConditionalStyles() const
{
return mStyles;
}

QList<QgsConditionalStyle> QgsFieldUIProperties::matchingConditionalStyles( QVariant value, QgsFeature *feature )
QList<QgsConditionalStyle> QgsFieldUIProperties::matchingConditionalStyles( QVariant value, QgsExpressionContext& context ) const
{
QList<QgsConditionalStyle> styles;
foreach ( QgsConditionalStyle style, mStyles )
{
if ( style.matches( value, feature ) )
if ( style.matches( value, context ) )
styles.append( style );
}
return styles;
}

QgsConditionalStyle QgsFieldUIProperties::matchingConditionalStyle( QVariant value, QgsFeature *feature )
QgsConditionalStyle QgsFieldUIProperties::matchingConditionalStyle( QVariant value, QgsExpressionContext& context ) const
{
foreach ( QgsConditionalStyle style, mStyles )
{
if ( style.matches( value, feature ) )
if ( style.matches( value, context ) )
return style;
}
return QgsConditionalStyle();
}

bool QgsFieldUIProperties::writeXml( QDomNode &node, QDomDocument &doc )
bool QgsFieldUIProperties::writeXml( QDomNode &node, QDomDocument &doc ) const
{
QDomElement stylesel = doc.createElement( "conditionalstyles" );
foreach ( QgsConditionalStyle style, mStyles )
Expand Down
31 changes: 17 additions & 14 deletions src/core/qgsfielduiproperties.h
Expand Up @@ -30,33 +30,36 @@ class CORE_EXPORT QgsFieldUIProperties
* @brief Returns the condtional styles set for the field UI properties
* @return A list of condtional styles that have been set.
*/
QList<QgsConditionalStyle> getConditionalStyles();
QList<QgsConditionalStyle> getConditionalStyles() const;

/**
* @brief Find and return the matching styles for the value and feature.
* If no match is found a invalid QgsCondtionalStyle is return.
*
* @return A condtional style that matches the value and feature.
* Check with QgsCondtionalStyle::isValid()
* @brief Find and return all matching styles for a value and context.
* If no match is found an empty list is returned.
* @param value current cell value
* @param context expression context for evaluating conditional rules
* @return A list of conditional styles that matches the value and context.
* @see matchingConditionalStyle
*/
QList<QgsConditionalStyle> matchingConditionalStyles( QVariant value, QgsFeature* feature );
QList<QgsConditionalStyle> matchingConditionalStyles(QVariant value, QgsExpressionContext& context ) const;

/**
* @brief Find and return the matching style for the value and feature.
* If no match is found a invalid QgsCondtionalStyle is return.
*
* @return A condtional style that matches the value and feature.
* Check with QgsCondtionalStyle::isValid()
* @brief Find and return the matching style for the value and context.
* If no match is found a invalid QgsConditionalStyle is return.
* @param value current cell value
* @param context expression context for evaluating conditional rules
* @return A conditional style that matches the value and context.
* Check with QgsConditionalStyle::isValid()
* @see matchingConditionalStyles
*/
QgsConditionalStyle matchingConditionalStyle( QVariant value, QgsFeature* feature );
QgsConditionalStyle matchingConditionalStyle( QVariant value, QgsExpressionContext& context ) const;

/** Reads field ui properties specific state from Dom node.
*/
virtual bool readXml( const QDomNode& node );

/** Write field ui properties specific state from Dom node.
*/
virtual bool writeXml( QDomNode & node, QDomDocument & doc );
virtual bool writeXml( QDomNode & node, QDomDocument & doc ) const;

private:
QList<QgsConditionalStyle> mStyles;
Expand Down
53 changes: 31 additions & 22 deletions src/gui/attributetable/qgsattributetablemodel.cpp
Expand Up @@ -43,6 +43,10 @@ QgsAttributeTableModel::QgsAttributeTableModel( QgsVectorLayerCache *layerCache,
{
QgsDebugMsg( "entered." );

mExpressionContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope()
<< QgsExpressionContextUtils::layerScope( layerCache->layer() );

if ( layerCache->layer()->geometryType() == QGis::NoGeometry )
{
mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry );
Expand Down Expand Up @@ -583,30 +587,35 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
return mWidgetFactories[ index.column()]->representValue( layer(), fieldId, mWidgetConfigs[ index.column()], mAttributeWidgetCaches[ index.column()], val );
}

QgsFieldUIProperties props = layer()->fieldUIProperties( field.name() );
QList<QgsConditionalStyle> styles = props.matchingConditionalStyles( val, &mFeat );
QgsConditionalStyle style;
foreach ( QgsConditionalStyle s, styles )
if ( role == Qt::BackgroundColorRole || role == Qt::TextColorRole || role == Qt::DecorationRole || role == Qt::FontRole )
{
style.setFont( s.font() );
if ( s.backgroundColor().isValid() && s.backgroundColor().alpha() != 0 )
style.setBackgroundColor( s.backgroundColor() );
if ( s.textColor().isValid() && s.textColor().alpha() != 0 )
style.setTextColor( s.textColor() );
if ( s.symbol() )
style.setSymbol( s.symbol() );
}
mExpressionContext.setFeature( mFeat );

if ( style.isValid() )
{
if ( role == Qt::BackgroundColorRole && style.backgroundColor().isValid() )
return style.backgroundColor();
if ( role == Qt::TextColorRole && style.textColor().isValid() )
return style.textColor();
if ( role == Qt::DecorationRole )
return style.icon();
if ( role == Qt::FontRole )
return style.font();
QgsFieldUIProperties props = layer()->fieldUIProperties( field.name() );
QList<QgsConditionalStyle> styles = props.matchingConditionalStyles( val, mExpressionContext );
QgsConditionalStyle style;
foreach ( QgsConditionalStyle s, styles )
{
style.setFont( s.font() );
if ( s.backgroundColor().isValid() && s.backgroundColor().alpha() != 0 )
style.setBackgroundColor( s.backgroundColor() );
if ( s.textColor().isValid() && s.textColor().alpha() != 0 )
style.setTextColor( s.textColor() );
if ( s.symbol() )
style.setSymbol( s.symbol() );
}

if ( style.isValid() )
{
if ( role == Qt::BackgroundColorRole && style.backgroundColor().isValid() )
return style.backgroundColor();
if ( role == Qt::TextColorRole && style.textColor().isValid() )
return style.textColor();
if ( role == Qt::DecorationRole )
return style.icon();
if ( role == Qt::FontRole )
return style.font();
}
}

return val;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/attributetable/qgsattributetablemodel.h
Expand Up @@ -300,6 +300,8 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
QHash<QgsFeatureId, int> mIdRowMap;
QHash<int, QgsFeatureId> mRowIdMap;

mutable QgsExpressionContext mExpressionContext;

/**
* Gets mFieldCount, mAttributes and mValueMaps
*/
Expand Down
9 changes: 8 additions & 1 deletion src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
Expand Up @@ -46,7 +46,14 @@ void QgsFieldConditionalFormatWidget::updateIcon()

void QgsFieldConditionalFormatWidget::setExpression()
{
QgsExpressionBuilderDialog dlg( mLayer, mRuleEdit->text(), this );
QgsExpressionContext context;
context << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope()
<< QgsExpressionContextUtils::layerScope( mLayer );
context.lastScope()->setVariable( "value", 0 );
context.setHighlightedVariables( QStringList() << "value" );

QgsExpressionBuilderDialog dlg( mLayer, mRuleEdit->text(), this, "generic", context );
dlg.setWindowTitle( tr( "Conditional style rule expression" ) );

if ( dlg.exec() )
Expand Down
15 changes: 8 additions & 7 deletions tests/src/python/test_qgsconditionalstyle.py
Expand Up @@ -13,13 +13,12 @@
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

from qgis.core import QgsConditionalStyle, QgsFeature, QgsFields, QgsField
from qgis.core import QgsConditionalStyle, QgsFeature, QgsFields, QgsField, QgsExpressionContextUtils
from utilities import (unitTestDataPath,
getQgisTestApp,
unittest,
TestCase,
compareWkt
)
compareWkt)
from PyQt4.QtCore import QVariant
#
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
Expand All @@ -29,11 +28,13 @@
class TestPyQgsConditionalStyle(TestCase):
def test_MatchesReturnsTrueForSimpleMatch(self):
style = QgsConditionalStyle("@value > 10")
assert style.matches(20)
context = QgsExpressionContextUtils.createFeatureBasedContext( QgsFeature(), QgsFields() )
assert style.matches(20,context)

def test_MatchesReturnsTrueForComplexMatch(self):
style = QgsConditionalStyle("@value > 10 and @value = 20")
assert style.matches(20)
context = QgsExpressionContextUtils.createFeatureBasedContext( QgsFeature(), QgsFields() )
assert style.matches(20,context)

def test_MatchesTrueForFields(self):
feature = QgsFeature()
Expand All @@ -42,9 +43,9 @@ def test_MatchesTrueForFields(self):
feature.setFields(fields, True)
feature["testfield"] = 20
style = QgsConditionalStyle('"testfield" = @value')
assert style.matches(20, feature)
context = QgsExpressionContextUtils.createFeatureBasedContext(feature,fields)
assert style.matches(20, context)


if __name__ == '__main__':
unittest.main()

20 changes: 20 additions & 0 deletions tests/testdata/landsat-int16-b1.tif.aux.xml
@@ -0,0 +1,20 @@
<PAMDataset>
<PAMRasterBand band="1">
<Histograms>
<HistItem>
<HistMin>121.5555555555556</HistMin>
<HistMax>130.4444444444445</HistMax>
<BucketCount>9</BucketCount>
<IncludeOutOfRange>0</IncludeOutOfRange>
<Approximate>0</Approximate>
<HistCounts>22|377|2878|10013|13500|9834|2936|409|31</HistCounts>
</HistItem>
</Histograms>
<Metadata>
<MDI key="STATISTICS_MAXIMUM">130</MDI>
<MDI key="STATISTICS_MEAN">126.001725</MDI>
<MDI key="STATISTICS_MINIMUM">122</MDI>
<MDI key="STATISTICS_STDDEV">1.1294343825018</MDI>
</Metadata>
</PAMRasterBand>
</PAMDataset>

0 comments on commit 4bf8b13

Please sign in to comment.