42 changes: 42 additions & 0 deletions src/core/symbology-ng/qgsrulebasedrendererv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,38 @@ bool QgsRuleBasedRendererV2::Rule::renderFeature( QgsRuleBasedRendererV2::Featur
return rendered;
}

bool QgsRuleBasedRendererV2::Rule::willRenderFeature( QgsFeature& feat )
{
if ( !isFilterOK( feat ) )
return false;
if ( mSymbol )
return true;

for ( QList<Rule*>::iterator it = mActiveChildren.begin(); it != mActiveChildren.end(); ++it )
{
Rule* rule = *it;
if ( rule->willRenderFeature( feat ) )
return true;
}
return false;
}

QgsSymbolV2List QgsRuleBasedRendererV2::Rule::symbolsForFeature( QgsFeature& feat )
{
QgsSymbolV2List lst;
if ( !isFilterOK( feat ) )
return lst;
if ( mSymbol )
lst.append( mSymbol );

for ( QList<Rule*>::iterator it = mActiveChildren.begin(); it != mActiveChildren.end(); ++it )
{
Rule* rule = *it;
lst += rule->symbolsForFeature( feat );
}
return lst;
}


void QgsRuleBasedRendererV2::Rule::stopRender( QgsRenderContext& context )
{
Expand Down Expand Up @@ -581,3 +613,13 @@ QString QgsRuleBasedRendererV2::dump()
msg += mRootRule->dump();
return msg;
}

bool QgsRuleBasedRendererV2::willRenderFeature( QgsFeature& feat )
{
return mRootRule->willRenderFeature( feat );
}

QgsSymbolV2List QgsRuleBasedRendererV2::symbolsForFeature( QgsFeature& feat )
{
return mRootRule->symbolsForFeature( feat );
}
23 changes: 23 additions & 0 deletions src/core/symbology-ng/qgsrulebasedrendererv2.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2

bool renderFeature( FeatureToRender& featToRender, QgsRenderContext& context, RenderQueue& renderQueue );

//! only tell whether a feature will be rendered without actually rendering it
//! @note added in 1.9
bool willRenderFeature( QgsFeature& feat );

//! tell which symbols will be used to render the feature
//! @note added in 1.9
QgsSymbolV2List symbolsForFeature( QgsFeature& feat );

void stopRender( QgsRenderContext& context );

static Rule* create( QDomElement& ruleElem, QgsSymbolV2Map& symbolMap );
Expand Down Expand Up @@ -204,6 +212,21 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2
//! for debugging
virtual QString dump();

//! return whether the renderer will render a feature or not.
//! Must be called between startRender() and stopRender() calls.
//! @note added in 1.9
virtual bool willRenderFeature( QgsFeature& feat );

//! return list of symbols used for rendering the feature.
//! For renderers that do not support MoreSymbolsPerFeature it is more efficient
//! to use symbolForFeature()
//! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );

//! returns bitwise OR-ed capabilities of the renderer
//! \note added in 2.0
virtual int capabilities() { return MoreSymbolsPerFeature; }

/////

Rule* rootRule() { return mRootRule; }
Expand Down
1 change: 1 addition & 0 deletions src/plugins/grass/modules/r.mask.qgm
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<qgisgrassmodule label="Create a MASK for limiting raster operation" module="r.mask">
<option key="input" />
<option key="maskcats" />
<flag key="r" />
</qgisgrassmodule>
165 changes: 109 additions & 56 deletions tests/src/core/testqgsrulebasedrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,75 +18,128 @@
//header for class being tested
#include <qgsrulebasedrendererv2.h>

#include <qgsapplication.h>
#include <qgssymbolv2.h>
#include <qgsvectorlayer.h>

#if QT_VERSION < 0x40701
// See http://hub.qgis.org/issues/4284
Q_DECLARE_METATYPE( QVariant )
#endif

typedef QgsRuleBasedRendererV2::Rule RRule;

class TestQgsRuleBasedRenderer: public QObject
{
Q_OBJECT
private slots:

void test_load_xml()
{
QDomDocument doc;
xml2domElement( "rulebasedrenderer_simple.xml", doc );
QDomElement elem = doc.documentElement();

QgsRuleBasedRendererV2* r = static_cast<QgsRuleBasedRendererV2*>( QgsRuleBasedRendererV2::create( elem ) );
QVERIFY( r );
check_tree_valid( r->rootRule() );
delete r;
}

void test_load_invalid_xml()
{
QDomDocument doc;
xml2domElement( "rulebasedrenderer_invalid.xml", doc );
QDomElement elem = doc.documentElement();

QgsRuleBasedRendererV2* r = static_cast<QgsRuleBasedRendererV2*>( QgsRuleBasedRendererV2::create( elem ) );
QVERIFY( r == NULL );
}

private:
void xml2domElement( QString testFile, QDomDocument& doc )
{
QString fileName = QString( TEST_DATA_DIR ) + QDir::separator() + testFile;
QFile f(fileName);
bool fileOpen = f.open(QIODevice::ReadOnly);
QVERIFY( fileOpen );

QString msg;
int line, col;
bool parse = doc.setContent( &f, &msg, &line, &col );
QVERIFY( parse );
}

void check_tree_valid( QgsRuleBasedRendererV2::Rule* root )
{
// root must always exist (although it does not have children)
QVERIFY( root );
// and does not have a parent
QVERIFY( root->parent() == NULL );

foreach ( QgsRuleBasedRendererV2::Rule* node, root->children() )
void initTestCase()
{
// we need memory provider, so make sure to load providers
QgsApplication::init();
QgsApplication::initQgis();
}

void test_load_xml()
{
QDomDocument doc;
xml2domElement( "rulebasedrenderer_simple.xml", doc );
QDomElement elem = doc.documentElement();

QgsRuleBasedRendererV2* r = static_cast<QgsRuleBasedRendererV2*>( QgsRuleBasedRendererV2::create( elem ) );
QVERIFY( r );
check_tree_valid( r->rootRule() );
delete r;
}

void test_load_invalid_xml()
{
QDomDocument doc;
xml2domElement( "rulebasedrenderer_invalid.xml", doc );
QDomElement elem = doc.documentElement();

QgsRuleBasedRendererV2* r = static_cast<QgsRuleBasedRendererV2*>( QgsRuleBasedRendererV2::create( elem ) );
QVERIFY( r == NULL );
}

void test_willRenderFeature_symbolsForFeature()
{
// prepare features
QgsVectorLayer* layer = new QgsVectorLayer( "point?field=fld:int", "x", "memory" );
int idx = layer->fieldNameIndex( "fld" );
QVERIFY( idx != -1 );
QgsFeature f1; f1.addAttribute( idx, QVariant( 2 ) );
QgsFeature f2; f2.addAttribute( idx, QVariant( 8 ) );
QgsFeature f3; f3.addAttribute( idx, QVariant( 100 ) );

// prepare renderer
QgsSymbolV2* s1 = QgsSymbolV2::defaultSymbol( QGis::Point );
QgsSymbolV2* s2 = QgsSymbolV2::defaultSymbol( QGis::Point );
RRule* rootRule = new RRule( NULL );
rootRule->appendChild( new RRule( s1, 0, 0, "fld >= 5 and fld <= 20" ) );
rootRule->appendChild( new RRule( s2, 0, 0, "fld <= 10" ) );
QgsRuleBasedRendererV2 r( rootRule );

QVERIFY( r.capabilities() & QgsFeatureRendererV2::MoreSymbolsPerFeature );

QgsRenderContext ctx; // dummy render context
r.startRender( ctx, layer );

// test willRenderFeature
QVERIFY( r.willRenderFeature( f1 ) == true );
QVERIFY( r.willRenderFeature( f2 ) == true );
QVERIFY( r.willRenderFeature( f3 ) == false );

// test symbolsForFeature
QgsSymbolV2List lst1 = r.symbolsForFeature( f1 );
QVERIFY( lst1.count() == 1 );
QgsSymbolV2List lst2 = r.symbolsForFeature( f2 );
QVERIFY( lst2.count() == 2 );
QgsSymbolV2List lst3 = r.symbolsForFeature( f3 );
QVERIFY( lst3.count() == 0 );

r.stopRender( ctx );

delete layer;
}

private:
void xml2domElement( QString testFile, QDomDocument& doc )
{
QString fileName = QString( TEST_DATA_DIR ) + QDir::separator() + testFile;
QFile f( fileName );
bool fileOpen = f.open( QIODevice::ReadOnly );
QVERIFY( fileOpen );

QString msg;
int line, col;
bool parse = doc.setContent( &f, &msg, &line, &col );
QVERIFY( parse );
}

void check_tree_valid( QgsRuleBasedRendererV2::Rule* root )
{
// root must always exist (although it does not have children)
QVERIFY( root );
// and does not have a parent
QVERIFY( root->parent() == NULL );

foreach( QgsRuleBasedRendererV2::Rule* node, root->children() )
check_non_root_rule( node );
}

void check_non_root_rule( QgsRuleBasedRendererV2::Rule* node )
{
qDebug() << node->dump();
// children must not be NULL
QVERIFY( node );
// and must have a parent
QVERIFY( node->parent() );
// check that all children are okay
foreach ( QgsRuleBasedRendererV2::Rule* child, node->children() )
}

void check_non_root_rule( QgsRuleBasedRendererV2::Rule* node )
{
qDebug() << node->dump();
// children must not be NULL
QVERIFY( node );
// and must have a parent
QVERIFY( node->parent() );
// check that all children are okay
foreach( QgsRuleBasedRendererV2::Rule* child, node->children() )
check_non_root_rule( child );
}
}

};

Expand Down