diff --git a/python/core/symbology/qgsrulebasedrenderer.sip.in b/python/core/symbology/qgsrulebasedrenderer.sip.in index bdeefe7ed499..29c7777464fd 100644 --- a/python/core/symbology/qgsrulebasedrenderer.sip.in +++ b/python/core/symbology/qgsrulebasedrenderer.sip.in @@ -324,9 +324,14 @@ Returns which legend keys match the feature .. versionadded:: 2.14 %End - QgsRuleBasedRenderer::RuleList rulesForFeature( QgsFeature &feat, QgsRenderContext *context = 0 ); + QgsRuleBasedRenderer::RuleList rulesForFeature( QgsFeature &feat, QgsRenderContext *context = 0, bool onlyActive = true ); %Docstring -tell which rules will be used to render the feature +Returns the list of rules used to render the feature in a specific +context. + +:param feat: The feature for which rules have to be find +:param context: The rendering context +:param onlyActive: True to search for active rules only, false otherwise %End void stopRender( QgsRenderContext &context ); @@ -411,7 +416,7 @@ Sets if this rule is an ELSE rule :param iselse: If true, this rule is an ELSE rule %End - bool isElse(); + bool isElse() const; %Docstring Check if this rule is an ELSE rule diff --git a/src/core/symbology/qgsrulebasedrenderer.cpp b/src/core/symbology/qgsrulebasedrenderer.cpp index 136b7cae6d27..31c52072cf2b 100644 --- a/src/core/symbology/qgsrulebasedrenderer.cpp +++ b/src/core/symbology/qgsrulebasedrenderer.cpp @@ -541,13 +541,26 @@ bool QgsRuleBasedRenderer::Rule::willRenderFeature( QgsFeature &feat, QgsRenderC { if ( !isFilterOK( feat, context ) ) return false; + if ( mSymbol ) return true; Q_FOREACH ( Rule *rule, mActiveChildren ) { - if ( rule->willRenderFeature( feat, context ) ) + if ( rule->isElse() ) + { + RuleList lst = rulesForFeature( feat, context, false ); + lst.removeOne( rule ); + + if ( lst.empty() ) + { + return true; + } + } + else if ( !rule->isElse( ) && rule->willRenderFeature( feat, context ) ) + { return true; + } } return false; } @@ -576,12 +589,31 @@ QSet QgsRuleBasedRenderer::Rule::legendKeysForFeature( QgsFeature &feat Q_FOREACH ( Rule *rule, mActiveChildren ) { - lst.unite( rule->legendKeysForFeature( feat, context ) ); + bool validKey = false; + if ( rule->isElse() ) + { + RuleList lst = rulesForFeature( feat, context, false ); + lst.removeOne( rule ); + + if ( lst.empty() ) + { + validKey = true; + } + } + else if ( !rule->isElse( ) && rule->willRenderFeature( feat, context ) ) + { + validKey = true; + } + + if ( validKey ) + { + lst.unite( rule->legendKeysForFeature( feat, context ) ); + } } return lst; } -QgsRuleBasedRenderer::RuleList QgsRuleBasedRenderer::Rule::rulesForFeature( QgsFeature &feat, QgsRenderContext *context ) +QgsRuleBasedRenderer::RuleList QgsRuleBasedRenderer::Rule::rulesForFeature( QgsFeature &feat, QgsRenderContext *context, bool onlyActive ) { RuleList lst; if ( !isFilterOK( feat, context ) ) @@ -590,9 +622,13 @@ QgsRuleBasedRenderer::RuleList QgsRuleBasedRenderer::Rule::rulesForFeature( QgsF if ( mSymbol ) lst.append( this ); - Q_FOREACH ( Rule *rule, mActiveChildren ) + RuleList listChildren = children(); + if ( onlyActive ) + listChildren = mActiveChildren; + + Q_FOREACH ( Rule *rule, listChildren ) { - lst += rule->rulesForFeature( feat, context ); + lst += rule->rulesForFeature( feat, context, onlyActive ); } return lst; } diff --git a/src/core/symbology/qgsrulebasedrenderer.h b/src/core/symbology/qgsrulebasedrenderer.h index 6cc8202ef2be..8d8a9e0ae29c 100644 --- a/src/core/symbology/qgsrulebasedrenderer.h +++ b/src/core/symbology/qgsrulebasedrenderer.h @@ -342,8 +342,15 @@ class CORE_EXPORT QgsRuleBasedRenderer : public QgsFeatureRenderer */ QSet< QString > legendKeysForFeature( QgsFeature &feat, QgsRenderContext *context = nullptr ); - //! tell which rules will be used to render the feature - QgsRuleBasedRenderer::RuleList rulesForFeature( QgsFeature &feat, QgsRenderContext *context = nullptr ); + /** + * Returns the list of rules used to render the feature in a specific + * context. + * + * \param feat The feature for which rules have to be find + * \param context The rendering context + * \param onlyActive True to search for active rules only, false otherwise + */ + QgsRuleBasedRenderer::RuleList rulesForFeature( QgsFeature &feat, QgsRenderContext *context = nullptr, bool onlyActive = true ); /** * Stop a rendering process. Used to clean up the internal state of this rule @@ -419,7 +426,7 @@ class CORE_EXPORT QgsRuleBasedRenderer : public QgsFeatureRenderer * * \returns True if this rule is an else rule */ - bool isElse() { return mElseRule; } + bool isElse() const { return mElseRule; } protected: void initFilter(); diff --git a/tests/src/python/test_qgsrulebasedrenderer.py b/tests/src/python/test_qgsrulebasedrenderer.py index d7197ab7fc4d..8a3c3a9d2402 100644 --- a/tests/src/python/test_qgsrulebasedrenderer.py +++ b/tests/src/python/test_qgsrulebasedrenderer.py @@ -40,7 +40,8 @@ QgsRendererCategory, QgsCategorizedSymbolRenderer, QgsGraduatedSymbolRenderer, - QgsRendererRange + QgsRendererRange, + QgsRenderContext ) from qgis.testing import start_app, unittest from utilities import unitTestDataPath @@ -101,6 +102,52 @@ def testDisabledElse(self): assert result + def testWillRenderFeature(self): + vl = self.mapsettings.layers()[0] + ft = vl.getFeature(0) # 'id' = 1 + renderer = vl.renderer() + + ctx = QgsRenderContext.fromMapSettings(self.mapsettings) + ctx.expressionContext().setFeature(ft) + + renderer.rootRule().children()[0].setActive(False) + renderer.rootRule().children()[1].setActive(True) + renderer.rootRule().children()[2].setActive(True) + + renderer.startRender(ctx, vl.fields()) # build mActiveChlidren + rendered = renderer.willRenderFeature(ft, ctx) + renderer.stopRender(ctx) + renderer.rootRule().children()[0].setActive(True) + assert rendered == False + + renderer.startRender(ctx, vl.fields()) # build mActiveChlidren + rendered = renderer.willRenderFeature(ft, ctx) + renderer.stopRender(ctx) + assert rendered == True + + def testFeatureCount(self): + vl = self.mapsettings.layers()[0] + ft = vl.getFeature(2) # 'id' = 3 => ELSE + renderer = vl.renderer() + + ctx = QgsRenderContext.fromMapSettings(self.mapsettings) + ctx.expressionContext().setFeature(ft) + + counter = vl.countSymbolFeatures() + counter.waitForFinished() + + renderer.startRender(ctx, vl.fields()) + + elseRule = None + for rule in renderer.rootRule().children(): + if rule.filterExpression() == 'ELSE': + elseRule = rule + + assert elseRule != None + + cnt = counter.featureCount(elseRule.ruleKey()) + assert cnt == 1 + def testRefineWithCategories(self): # Test refining rule with categories (refs #10815)