QGIS API Documentation 3.29.0-Master (19d7edcfed)
qgsconditionalstyle.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsconditionalstyle.cpp
3 ---------------------
4 begin : August 2015
5 copyright : (C) 2015 by Nathan Woodrow
6 email : woodrow dot nathan at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15#include <QPainter>
16
17#include "qgsconditionalstyle.h"
18#include "qgsexpression.h"
19#include "qgsfontutils.h"
20#include "qgssymbollayerutils.h"
22#include "qgsmarkersymbol.h"
23
25 : QObject( parent )
26{
27}
28
30{
31 switch ( strength )
32 {
34 {
35 QgsConditionalStyle hardConstraintFailureStyle;
36 hardConstraintFailureStyle.setBackgroundColor( QColor( 255, 152, 0 ) );
37 hardConstraintFailureStyle.setTextColor( QColor( 0, 0, 0 ) );
38 return hardConstraintFailureStyle;
39 }
40
42 {
43 QgsConditionalStyle softConstraintFailureStyle;
44 softConstraintFailureStyle.setBackgroundColor( QColor( 255, 191, 12 ) );
45 softConstraintFailureStyle.setTextColor( QColor( 0, 0, 0 ) );
46 return softConstraintFailureStyle;
47 }
48
50 return QgsConditionalStyle();
51 }
52
53 return QgsConditionalStyle();
54}
55
57{
58 return mRowStyles;
59}
60
62{
63 if ( styles == mRowStyles )
64 return;
65
66 mRowStyles = styles;
67 emit changed();
68}
69
70void QgsConditionalLayerStyles::setFieldStyles( const QString &fieldName, const QList<QgsConditionalStyle> &styles )
71{
72 if ( mFieldStyles.value( fieldName ) == styles )
73 return;
74
75 mFieldStyles.insert( fieldName, styles );
76 emit changed();
77}
78
79QList<QgsConditionalStyle> QgsConditionalLayerStyles::fieldStyles( const QString &fieldName ) const
80{
81 return mFieldStyles.value( fieldName );
82}
83
84bool QgsConditionalLayerStyles::writeXml( QDomNode &node, QDomDocument &doc, const QgsReadWriteContext &context ) const
85{
86 QDomElement stylesel = doc.createElement( QStringLiteral( "conditionalstyles" ) );
87
88 QDomElement rowel = doc.createElement( QStringLiteral( "rowstyles" ) );
89 const auto constMRowStyles = mRowStyles;
90 for ( const QgsConditionalStyle &style : constMRowStyles )
91 {
92 style.writeXml( rowel, doc, context );
93 }
94 stylesel.appendChild( rowel );
95
96 QDomElement fieldsel = doc.createElement( QStringLiteral( "fieldstyles" ) );
97 QHash<QString, QgsConditionalStyles>::const_iterator it = mFieldStyles.constBegin();
98 for ( ; it != mFieldStyles.constEnd(); ++it )
99 {
100 QDomElement fieldel = doc.createElement( QStringLiteral( "fieldstyle" ) );
101 fieldel.setAttribute( QStringLiteral( "fieldname" ), it.key() );
102 const QgsConditionalStyles styles = it.value();
103 const auto constStyles = styles;
104 for ( const QgsConditionalStyle &style : constStyles )
105 {
106 style.writeXml( fieldel, doc, context );
107 }
108 fieldsel.appendChild( fieldel );
109 }
110 stylesel.appendChild( fieldsel );
111
112 node.appendChild( stylesel );
113 return true;
114}
115
117{
118 for ( const QgsConditionalStyle &style : std::as_const( mRowStyles ) )
119 {
120 if ( QgsExpression( style.rule() ).needsGeometry() )
121 {
122 return true;
123 }
124 }
125 return false;
126}
127
128bool QgsConditionalLayerStyles::readXml( const QDomNode &node, const QgsReadWriteContext &context )
129{
130 mRowStyles.clear();
131 mFieldStyles.clear();
132
133 const QDomElement condel = node.firstChildElement( QStringLiteral( "conditionalstyles" ) );
134
135 const QDomElement rowstylesel = condel.firstChildElement( QStringLiteral( "rowstyles" ) );
136 QDomNodeList nodelist = rowstylesel.toElement().elementsByTagName( QStringLiteral( "style" ) );
137 for ( int i = 0; i < nodelist.count(); i++ )
138 {
139 const QDomElement styleElm = nodelist.at( i ).toElement();
141 style.readXml( styleElm, context );
142 mRowStyles.append( style );
143 }
144
145 const QDomElement fieldstylesel = condel.firstChildElement( QStringLiteral( "fieldstyles" ) );
146 nodelist = fieldstylesel.toElement().elementsByTagName( QStringLiteral( "fieldstyle" ) );
147 QList<QgsConditionalStyle> styles;
148 for ( int i = 0; i < nodelist.count(); i++ )
149 {
150 styles.clear();
151 const QDomElement fieldel = nodelist.at( i ).toElement();
152 const QString fieldName = fieldel.attribute( QStringLiteral( "fieldname" ) );
153 const QDomNodeList stylenodelist = fieldel.toElement().elementsByTagName( QStringLiteral( "style" ) );
154 styles.reserve( stylenodelist.count() );
155 for ( int j = 0; j < stylenodelist.count(); j++ )
156 {
157 const QDomElement styleElm = stylenodelist.at( j ).toElement();
159 style.readXml( styleElm, context );
160 styles.append( style );
161 }
162 mFieldStyles.insert( fieldName, styles );
163 }
164
165 return true;
166}
167
169{}
170
172{
173 setRule( rule );
174}
175
177
179 : mValid( other.mValid )
180 , mName( other.mName )
181 , mRule( other.mRule )
182 , mFont( other.mFont )
183 , mBackColor( other.mBackColor )
184 , mTextColor( other.mTextColor )
185 , mIcon( other.mIcon )
186{
187 if ( other.mSymbol )
188 mSymbol.reset( other.mSymbol->clone() );
189}
190
192{
193 mValid = other.mValid;
194 mRule = other.mRule;
195 mFont = other.mFont;
196 mBackColor = other.mBackColor;
197 mTextColor = other.mTextColor;
198 mIcon = other.mIcon;
199 mName = other.mName;
200 if ( other.mSymbol )
201 {
202 mSymbol.reset( other.mSymbol->clone() );
203 }
204 else
205 {
206 mSymbol.reset();
207 }
208 return ( *this );
209}
210
212{
213 if ( name().isEmpty() )
214 return rule();
215 else
216 return QStringLiteral( "%1 \n%2" ).arg( name(), rule() );
217}
218
220{
221 mValid = true;
222 if ( value )
223 {
224 mSymbol.reset( value->clone() );
225 mIcon = QgsSymbolLayerUtils::symbolPreviewPixmap( mSymbol.get(), QSize( 16, 16 ) );
226 }
227 else
228 {
229 mSymbol.reset();
230 }
231}
232
233bool QgsConditionalStyle::matches( const QVariant &value, QgsExpressionContext &context ) const
234{
235 QgsExpression exp( mRule );
236 context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "value" ), value, true ) );
237 return exp.evaluate( &context ).toBool();
238}
239
240QPixmap QgsConditionalStyle::renderPreview( const QSize &size ) const
241{
242 QPixmap pixmap( size.isValid() ? size.width() : 64, size.isValid() ? size.height() : 32 );
243 pixmap.fill( Qt::transparent );
244
245 QPainter painter( &pixmap );
246
247 if ( validBackgroundColor() )
248 painter.setBrush( mBackColor );
249
250 QRect rect = QRect( 0, 0, pixmap.width(), pixmap.height() );
251 painter.setPen( Qt::NoPen );
252 painter.drawRect( rect );
253 const QPixmap symbolIcon = icon();
254 if ( !symbolIcon.isNull() )
255 {
256 painter.drawPixmap( ( pixmap.width() / 3 - symbolIcon.width() ) / 2, ( pixmap.height() - symbolIcon.height() ) / 2, symbolIcon );
257 }
258
259 if ( validTextColor() )
260 painter.setPen( mTextColor );
261 else
262 painter.setPen( Qt::black );
263
264 painter.setRenderHint( QPainter::Antialiasing );
265 painter.setFont( font() );
266 rect = QRect( pixmap.width() / 3, 0, 2 * pixmap.width() / 3, pixmap.height() );
267 painter.drawText( rect, Qt::AlignCenter, QStringLiteral( "abc\n123" ) );
268 painter.end();
269 return pixmap;
270}
271
273{
274 return ( backgroundColor().isValid() && backgroundColor().alpha() != 0 );
275}
276
278{
279 return ( textColor().isValid() && textColor().alpha() != 0 );
280}
281
282QList<QgsConditionalStyle> QgsConditionalStyle::matchingConditionalStyles( const QList<QgsConditionalStyle> &styles, const QVariant &value, QgsExpressionContext &context )
283{
284 QList<QgsConditionalStyle> matchingstyles;
285 const auto constStyles = styles;
286 for ( const QgsConditionalStyle &style : constStyles )
287 {
288 if ( style.matches( value, context ) )
289 matchingstyles.append( style );
290 }
291 return matchingstyles;
292}
293
294QgsConditionalStyle QgsConditionalStyle::matchingConditionalStyle( const QList<QgsConditionalStyle> &styles, const QVariant &value, QgsExpressionContext &context )
295{
296 const auto constStyles = styles;
297 for ( const QgsConditionalStyle &style : constStyles )
298 {
299 if ( style.matches( value, context ) )
300 return style;
301 }
302 return QgsConditionalStyle();
303}
304
305QgsConditionalStyle QgsConditionalStyle::compressStyles( const QList<QgsConditionalStyle> &styles )
306{
308 for ( const QgsConditionalStyle &s : styles )
309 {
310 if ( !s.isValid() )
311 continue;
312
313 style.setFont( s.font() );
314 if ( s.backgroundColor().isValid() && s.backgroundColor().alpha() != 0 )
315 style.setBackgroundColor( s.backgroundColor() );
316 if ( s.textColor().isValid() && s.textColor().alpha() != 0 )
317 style.setTextColor( s.textColor() );
318 if ( auto *lSymbol = s.symbol() )
319 style.setSymbol( lSymbol );
320 }
321 return style;
322}
323
324bool QgsConditionalStyle::writeXml( QDomNode &node, QDomDocument &doc, const QgsReadWriteContext &context ) const
325{
326 QDomElement stylesel = doc.createElement( QStringLiteral( "style" ) );
327 stylesel.setAttribute( QStringLiteral( "rule" ), mRule );
328 stylesel.setAttribute( QStringLiteral( "name" ), mName );
329 if ( mBackColor.isValid() )
330 {
331 stylesel.setAttribute( QStringLiteral( "background_color" ), mBackColor.name() );
332 stylesel.setAttribute( QStringLiteral( "background_color_alpha" ), mBackColor.alpha() );
333 }
334 if ( mTextColor.isValid() )
335 {
336 stylesel.setAttribute( QStringLiteral( "text_color" ), mTextColor.name() );
337 stylesel.setAttribute( QStringLiteral( "text_color_alpha" ), mTextColor.alpha() );
338 }
339 const QDomElement labelFontElem = QgsFontUtils::toXmlElement( mFont, doc, QStringLiteral( "font" ) );
340 stylesel.appendChild( labelFontElem );
341 if ( mSymbol )
342 {
343 const QDomElement symbolElm = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "icon" ), mSymbol.get(), doc, context );
344 stylesel.appendChild( symbolElm );
345 }
346 node.appendChild( stylesel );
347 return true;
348}
349
351{
352 return mValid == other.mValid
353 && mName == other.mName
354 && mRule == other.mRule
355 && mFont == other.mFont
356 && mBackColor == other.mBackColor
357 && mTextColor == other.mTextColor
358 && static_cast< bool >( mSymbol ) == static_cast< bool >( other.mSymbol )
359 && ( ! mSymbol || QgsSymbolLayerUtils::symbolProperties( mSymbol.get() ) == QgsSymbolLayerUtils::symbolProperties( other.mSymbol.get() ) );
360}
361
363{
364 return !( *this == other );
365}
366
367bool QgsConditionalStyle::readXml( const QDomNode &node, const QgsReadWriteContext &context )
368{
369 const QDomElement styleElm = node.toElement();
370 setRule( styleElm.attribute( QStringLiteral( "rule" ) ) );
371 setName( styleElm.attribute( QStringLiteral( "name" ) ) );
372 if ( styleElm.hasAttribute( QStringLiteral( "background_color" ) ) )
373 {
374 QColor bColor = QColor( styleElm.attribute( QStringLiteral( "background_color" ) ) );
375 if ( styleElm.hasAttribute( QStringLiteral( "background_color_alpha" ) ) )
376 {
377 bColor.setAlpha( styleElm.attribute( QStringLiteral( "background_color_alpha" ) ).toInt() );
378 }
379 if ( bColor.alpha() == 0 )
380 setBackgroundColor( QColor() );
381 else
382 setBackgroundColor( bColor );
383 }
384 else
385 {
386 setBackgroundColor( QColor() );
387 }
388 if ( styleElm.hasAttribute( QStringLiteral( "text_color" ) ) )
389 {
390 QColor tColor = QColor( styleElm.attribute( QStringLiteral( "text_color" ) ) );
391 if ( styleElm.hasAttribute( QStringLiteral( "text_color_alpha" ) ) )
392 {
393 tColor.setAlpha( styleElm.attribute( QStringLiteral( "text_color_alpha" ) ).toInt() );
394 }
395 if ( tColor.alpha() == 0 )
396 setTextColor( QColor() );
397 else
398 setTextColor( tColor );
399 }
400 else
401 {
402 setTextColor( QColor() );
403 }
404 QgsFontUtils::setFromXmlChildNode( mFont, styleElm, QStringLiteral( "font" ) );
405 const QDomElement symbolElm = styleElm.firstChildElement( QStringLiteral( "symbol" ) );
406 if ( !symbolElm.isNull() )
407 {
408 QgsSymbol *symbol = QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( symbolElm, context );
409 setSymbol( symbol );
410 }
411 return true;
412}
413
void changed()
Emitted when the conditional styles are changed.
bool readXml(const QDomNode &node, const QgsReadWriteContext &context)
Reads the condition styles state from a DOM node.
QgsConditionalStyles rowStyles() const
Returns a list of row styles associated with the layer.
QgsConditionalLayerStyles(QObject *parent=nullptr)
Constructor for QgsConditionalLayerStyles, with the specified parent object.
QgsConditionalStyle constraintFailureStyles(QgsFieldConstraints::ConstraintStrength strength)
Returns a style associated to a constraint failure.
bool writeXml(QDomNode &node, QDomDocument &doc, const QgsReadWriteContext &context) const
Writes the condition styles state to a DOM node.
QList< QgsConditionalStyle > fieldStyles(const QString &fieldName) const
Returns the conditional styles set for the field with matching fieldName.
bool rulesNeedGeometry() const
Returns true if at least one rule needs geometry.
void setFieldStyles(const QString &fieldName, const QList< QgsConditionalStyle > &styles)
Set the conditional styles for a field, with the specified fieldName.
void setRowStyles(const QgsConditionalStyles &styles)
Sets the conditional styles that apply to full rows of data in the attribute table.
Conditional styling for a rule.
bool readXml(const QDomNode &node, const QgsReadWriteContext &context)
Reads vector conditional style specific state from layer Dom node.
bool operator==(const QgsConditionalStyle &other) const
QString displayText() const
The name of the style.
QString name() const
The name of the style.
static QgsConditionalStyle compressStyles(const QList< QgsConditionalStyle > &styles)
Compress a list of styles into a single style.
bool operator!=(const QgsConditionalStyle &other) const
void setSymbol(QgsSymbol *value)
Set the icon for the style.
void setName(const QString &value)
Set the name of the style.
QPixmap renderPreview(const QSize &size=QSize()) const
Render a preview icon of the rule, at the specified size.
bool matches(const QVariant &value, QgsExpressionContext &context) const
Check if the rule matches using the given value and feature.
QgsConditionalStyle & operator=(const QgsConditionalStyle &other)
void setTextColor(const QColor &value)
Set the text color for the style.
static QList< QgsConditionalStyle > matchingConditionalStyles(const QList< QgsConditionalStyle > &styles, const QVariant &value, QgsExpressionContext &context)
Find and return the matching styles for the value and feature.
void setRule(const QString &value)
Set the rule for the style.
void setBackgroundColor(const QColor &value)
Set the background color for the style.
void setFont(const QFont &value)
Set the font for the style.
QColor backgroundColor() const
The background color for style.
QColor textColor() const
The text color set for style.
QString rule() const
The condition rule set for the style.
QFont font() const
The font for the style.
bool writeXml(QDomNode &node, QDomDocument &doc, const QgsReadWriteContext &context) const
Write vector conditional style specific state from layer Dom node.
bool validTextColor() const
Check if the text color is valid for render.
QgsSymbol * symbol() const
The symbol used to generate the icon for the style.
bool isValid() const
isValid Check if this rule is valid.
static QgsConditionalStyle matchingConditionalStyle(const QList< QgsConditionalStyle > &styles, const QVariant &value, QgsExpressionContext &context)
Find and return the matching style for the value and feature.
QPixmap icon() const
The icon set for style generated from the set symbol.
bool validBackgroundColor() const
Check if the background color is valid for render.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsExpressionContextScope * lastScope()
Returns the last scope added to the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
QVariant evaluate()
Evaluate the feature and return the result.
ConstraintStrength
Strength of constraints.
@ ConstraintStrengthNotSet
Constraint is not set.
@ ConstraintStrengthSoft
User is warned if constraint is violated but feature can still be accepted.
@ ConstraintStrengthHard
Constraint must be honored before feature can be accepted.
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
static QDomElement toXmlElement(const QFont &font, QDomDocument &document, const QString &elementName)
Returns a DOM element containing the properties of the font.
The class is used as a container of context for various read/write operations on other objects.
static QPixmap symbolPreviewPixmap(const QgsSymbol *symbol, QSize size, int padding=0, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *shape=nullptr)
Returns a pixmap preview for a color ramp.
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
static QString symbolProperties(QgsSymbol *symbol)
Returns a string representing the symbol.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
QList< QgsConditionalStyle > QgsConditionalStyles
Single variable definition for use within a QgsExpressionContextScope.