Skip to content

Commit b8c2e68

Browse files
committedOct 18, 2016
[FEATURE] Allow symbol layers to be temporarily disabled
Adds a new checkbox at the bottom of each symbol layer's properties which allows you to control whether the layer is enabled or not. Disabled layers are not drawn, but are saved and can be enabled at a later stage. This makes it easier to tweak symbol appearance without having to totally delete a symbol layer.
1 parent 2a873b8 commit b8c2e68

File tree

10 files changed

+301
-7
lines changed

10 files changed

+301
-7
lines changed
 

‎python/core/symbology-ng/qgssymbollayer.sip

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ class QgsSymbolLayer
6767

6868
virtual ~QgsSymbolLayer();
6969

70+
/**
71+
* Returns true if symbol layer is enabled and will be drawn.
72+
* @note added in QGIS 3.0
73+
* @see setEnabled()
74+
*/
75+
bool enabled() const;
76+
77+
/**
78+
* Sets whether symbol layer is enabled and should be drawn. Disabled
79+
* layers are not drawn, but remain part of the symbol and can be re-enabled
80+
* when desired.
81+
* @note added in QGIS 3.0
82+
* @see enabled()
83+
*/
84+
void setEnabled( bool enabled );
85+
7086
/**
7187
* The fill color.
7288
*/

‎src/core/symbology-ng/qgssymbol.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,12 @@ void QgsSymbol::startRender( QgsRenderContext& context, const QgsFields& fields
396396
mSymbolRenderContext->setExpressionContextScope( scope );
397397

398398
Q_FOREACH ( QgsSymbolLayer* layer, mLayers )
399+
{
400+
if ( !layer->enabled() )
401+
continue;
402+
399403
layer->startRender( symbolContext );
404+
}
400405
}
401406

402407
void QgsSymbol::stopRender( QgsRenderContext& context )
@@ -405,7 +410,12 @@ void QgsSymbol::stopRender( QgsRenderContext& context )
405410
if ( mSymbolRenderContext )
406411
{
407412
Q_FOREACH ( QgsSymbolLayer* layer, mLayers )
413+
{
414+
if ( !layer->enabled() )
415+
continue;
416+
408417
layer->stopRender( *mSymbolRenderContext );
418+
}
409419
}
410420

411421
delete mSymbolRenderContext;
@@ -442,6 +452,9 @@ void QgsSymbol::drawPreviewIcon( QPainter* painter, QSize size, QgsRenderContext
442452

443453
Q_FOREACH ( QgsSymbolLayer* layer, mLayers )
444454
{
455+
if ( !layer->enabled() )
456+
continue;
457+
445458
if ( mType == Fill && layer->type() == Line )
446459
{
447460
// line symbol layer would normally draw just a line
@@ -587,6 +600,7 @@ QgsSymbolLayerList QgsSymbol::cloneLayers() const
587600
QgsSymbolLayer* layer = ( *it )->clone();
588601
layer->setLocked(( *it )->isLocked() );
589602
layer->setRenderingPass(( *it )->renderingPass() );
603+
layer->setEnabled(( *it )->enabled() );
590604
lst.append( layer );
591605
}
592606
return lst;
@@ -1417,7 +1431,7 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature* f, QgsRender
14171431
if ( layerIdx != -1 )
14181432
{
14191433
QgsSymbolLayer* symbolLayer = mLayers.value( layerIdx );
1420-
if ( symbolLayer )
1434+
if ( symbolLayer && symbolLayer->enabled() )
14211435
{
14221436
if ( symbolLayer->type() == QgsSymbol::Marker )
14231437
{
@@ -1432,6 +1446,9 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature* f, QgsRender
14321446

14331447
Q_FOREACH ( QgsSymbolLayer* symbolLayer, mLayers )
14341448
{
1449+
if ( !symbolLayer->enabled() )
1450+
continue;
1451+
14351452
if ( symbolLayer->type() == QgsSymbol::Marker )
14361453
{
14371454
QgsMarkerSymbolLayer* markerLayer = static_cast<QgsMarkerSymbolLayer*>( symbolLayer );
@@ -1625,7 +1642,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF& points, const QgsFeature* f
16251642
if ( layerIdx != -1 )
16261643
{
16271644
QgsSymbolLayer* symbolLayer = mLayers.value( layerIdx );
1628-
if ( symbolLayer )
1645+
if ( symbolLayer && symbolLayer->enabled() )
16291646
{
16301647
if ( symbolLayer->type() == QgsSymbol::Line )
16311648
{
@@ -1640,6 +1657,9 @@ void QgsLineSymbol::renderPolyline( const QPolygonF& points, const QgsFeature* f
16401657

16411658
Q_FOREACH ( QgsSymbolLayer* symbolLayer, mLayers )
16421659
{
1660+
if ( !symbolLayer->enabled() )
1661+
continue;
1662+
16431663
if ( symbolLayer->type() == QgsSymbol::Line )
16441664
{
16451665
QgsLineSymbolLayer* lineLayer = static_cast<QgsLineSymbolLayer*>( symbolLayer );
@@ -1704,7 +1724,7 @@ void QgsFillSymbol::renderPolygon( const QPolygonF& points, QList<QPolygonF>* ri
17041724
if ( layerIdx != -1 )
17051725
{
17061726
QgsSymbolLayer* symbolLayer = mLayers.value( layerIdx );
1707-
if ( symbolLayer )
1727+
if ( symbolLayer && symbolLayer->enabled() )
17081728
{
17091729
if ( symbolLayer->type() == Fill || symbolLayer->type() == Line )
17101730
renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext );
@@ -1716,6 +1736,9 @@ void QgsFillSymbol::renderPolygon( const QPolygonF& points, QList<QPolygonF>* ri
17161736

17171737
Q_FOREACH ( QgsSymbolLayer* symbolLayer, mLayers )
17181738
{
1739+
if ( !symbolLayer->enabled() )
1740+
continue;
1741+
17191742
if ( symbolLayer->type() == Fill || symbolLayer->type() == Line )
17201743
renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext );
17211744
else

‎src/core/symbology-ng/qgssymbollayer.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ void QgsSymbolLayer::setPaintEffect( QgsPaintEffect *effect )
261261

262262
QgsSymbolLayer::QgsSymbolLayer( QgsSymbol::SymbolType type, bool locked )
263263
: mType( type )
264+
, mEnabled( true )
264265
, mLocked( locked )
265266
, mRenderingPass( 0 )
266267
, mPaintEffect( nullptr )

‎src/core/symbology-ng/qgssymbollayer.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ class CORE_EXPORT QgsSymbolLayer
5353

5454
virtual ~QgsSymbolLayer();
5555

56+
/**
57+
* Returns true if symbol layer is enabled and will be drawn.
58+
* @note added in QGIS 3.0
59+
* @see setEnabled()
60+
*/
61+
bool enabled() const { return mEnabled; }
62+
63+
/**
64+
* Sets whether symbol layer is enabled and should be drawn. Disabled
65+
* layers are not drawn, but remain part of the symbol and can be re-enabled
66+
* when desired.
67+
* @note added in QGIS 3.0
68+
* @see enabled()
69+
*/
70+
void setEnabled( bool enabled ) { mEnabled = enabled; }
71+
5672
/**
5773
* The fill color.
5874
*/
@@ -266,6 +282,10 @@ class CORE_EXPORT QgsSymbolLayer
266282
QgsSymbolLayer( QgsSymbol::SymbolType type, bool locked = false );
267283

268284
QgsSymbol::SymbolType mType;
285+
286+
//! True if layer is enabled and should be drawn
287+
bool mEnabled;
288+
269289
bool mLocked;
270290
QColor mColor;
271291
int mRenderingPass;

‎src/core/symbology-ng/qgssymbollayerutils.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,7 @@ QgsSymbolLayer* QgsSymbolLayerUtils::loadSymbolLayer( QDomElement& element )
852852
{
853853
QString layerClass = element.attribute( "class" );
854854
bool locked = element.attribute( "locked" ).toInt();
855+
bool enabled = element.attribute( "enabled", "1" ).toInt();
855856
int pass = element.attribute( "pass" ).toInt();
856857

857858
// parse properties
@@ -863,6 +864,7 @@ QgsSymbolLayer* QgsSymbolLayerUtils::loadSymbolLayer( QDomElement& element )
863864
{
864865
layer->setLocked( locked );
865866
layer->setRenderingPass( pass );
867+
layer->setEnabled( enabled );
866868

867869
//restore layer effect
868870
QDomElement effectElem = element.firstChildElement( "effect" );
@@ -910,6 +912,7 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString& name, QgsSymbol* sym
910912

911913
QDomElement layerEl = doc.createElement( "layer" );
912914
layerEl.setAttribute( "class", layer->layerType() );
915+
layerEl.setAttribute( "enabled", layer->enabled() );
913916
layerEl.setAttribute( "locked", layer->isLocked() );
914917
layerEl.setAttribute( "pass", layer->renderingPass() );
915918
saveProperties( layer->properties(), doc, layerEl );

‎src/gui/symbology-ng/qgslayerpropertieswidget.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ QgsLayerPropertiesWidget::QgsLayerPropertiesWidget( QgsSymbolLayer* layer, const
112112
// update layer type combo box
113113
int idx = cboLayerType->findData( mLayer->layerType() );
114114
cboLayerType->setCurrentIndex( idx );
115+
mEnabledCheckBox->setChecked( mLayer->enabled() );
115116
// set the corresponding widget
116117
updateSymbolLayerWidget( layer );
117118
connect( cboLayerType, SIGNAL( currentIndexChanged( int ) ), this, SLOT( layerTypeChanged() ) );
@@ -236,3 +237,9 @@ void QgsLayerPropertiesWidget::reloadLayer()
236237
{
237238
emit changeLayer( mLayer );
238239
}
240+
241+
void QgsLayerPropertiesWidget::on_mEnabledCheckBox_toggled( bool enabled )
242+
{
243+
mLayer->setEnabled( enabled );
244+
emitSignalChanged();
245+
}

‎src/gui/symbology-ng/qgslayerpropertieswidget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class GUI_EXPORT QgsLayerPropertiesWidget : public QgsPanelWidget, private Ui::L
8282

8383
private slots:
8484
void reloadLayer();
85+
void on_mEnabledCheckBox_toggled( bool enabled );
8586

8687
private:
8788

‎src/ui/symbollayer/widget_layerproperties.ui

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
<number>3</number>
3131
</property>
3232
<item row="1" column="0">
33-
<layout class="QHBoxLayout" name="horizontalLayout">
33+
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
3434
<item>
3535
<widget class="QLabel" name="label">
3636
<property name="text">
@@ -50,6 +50,9 @@
5050
</item>
5151
</layout>
5252
</item>
53+
<item row="4" column="0">
54+
<widget class="QgsEffectStackCompactWidget" name="mEffectWidget" native="true"/>
55+
</item>
5356
<item row="2" column="0">
5457
<widget class="QStackedWidget" name="stackedWidget">
5558
<widget class="QWidget" name="pageDummy">
@@ -69,7 +72,34 @@
6972
</widget>
7073
</item>
7174
<item row="3" column="0">
72-
<widget class="QgsEffectStackCompactWidget" name="mEffectWidget" native="true"/>
75+
<layout class="QHBoxLayout" name="horizontalLayout_3">
76+
<property name="spacing">
77+
<number>0</number>
78+
</property>
79+
<property name="bottomMargin">
80+
<number>0</number>
81+
</property>
82+
<item>
83+
<widget class="QCheckBox" name="mEnabledCheckBox">
84+
<property name="text">
85+
<string>Enable layer</string>
86+
</property>
87+
</widget>
88+
</item>
89+
<item>
90+
<spacer name="horizontalSpacer">
91+
<property name="orientation">
92+
<enum>Qt::Horizontal</enum>
93+
</property>
94+
<property name="sizeHint" stdset="0">
95+
<size>
96+
<width>40</width>
97+
<height>20</height>
98+
</size>
99+
</property>
100+
</spacer>
101+
</item>
102+
</layout>
73103
</item>
74104
</layout>
75105
</widget>

‎tests/src/python/test_qgssymbollayer.py

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from qgis.PyQt.QtCore import pyqtWrapperType, Qt, QDir, QFile, QIODevice, QPointF
3131
from qgis.PyQt.QtXml import QDomDocument
32-
from qgis.PyQt.QtGui import QColor
32+
from qgis.PyQt.QtGui import QColor, QImage, QPainter
3333

3434
from qgis.core import (QgsCentroidFillSymbolLayer,
3535
QgsEllipseSymbolLayer,
@@ -55,7 +55,17 @@
5555
QgsShapeburstFillSymbolLayer,
5656
QgsArrowSymbolLayer,
5757
QgsSymbol,
58-
QgsUnitTypes
58+
QgsUnitTypes,
59+
QgsFillSymbol,
60+
QgsLineSymbol,
61+
QgsMarkerSymbol,
62+
QgsSymbolLayerUtils,
63+
QgsMapSettings,
64+
QgsGeometry,
65+
QgsFeature,
66+
QgsRenderContext,
67+
QgsRenderChecker,
68+
QgsRectangle
5969
)
6070
from qgis.testing import start_app, unittest
6171
from utilities import unitTestDataPath
@@ -79,6 +89,14 @@ class TestQgsSymbolLayer(unittest.TestCase):
7989
returns NULL
8090
"""
8191

92+
def setUp(self):
93+
self.report = "<h1>Python QgsSymbolLayer Tests</h1>\n"
94+
95+
def tearDown(self):
96+
report_file_path = "%s/qgistest.html" % QDir.tempPath()
97+
with open(report_file_path, 'a') as report_file:
98+
report_file.write(self.report)
99+
82100
def testBinding(self):
83101
"""Test python bindings existence."""
84102
mType = type(QgsSymbolLayer)
@@ -270,6 +288,181 @@ def testBinding(self):
270288
mMessage = 'Expected "%s" got "%s"' % (mExpectedType, mType)
271289
assert mExpectedType == mType, mMessage
272290

291+
def testGettersSetters(self):
292+
""" test base class getters/setters """
293+
layer = QgsSimpleFillSymbolLayer()
294+
295+
layer.setEnabled(False)
296+
self.assertFalse(layer.enabled())
297+
layer.setEnabled(True)
298+
self.assertTrue(layer.enabled())
299+
300+
layer.setLocked(False)
301+
self.assertFalse(layer.isLocked())
302+
layer.setLocked(True)
303+
self.assertTrue(layer.isLocked())
304+
305+
layer.setRenderingPass(5)
306+
self.assertEqual(layer.renderingPass(), 5)
307+
308+
def testSaveRestore(self):
309+
""" Test saving and restoring base symbol layer properties to xml"""
310+
311+
layer = QgsSimpleFillSymbolLayer()
312+
layer.setEnabled(False)
313+
layer.setLocked(True)
314+
layer.setRenderingPass(5)
315+
316+
symbol = QgsFillSymbol()
317+
symbol.changeSymbolLayer(0, layer)
318+
319+
doc = QDomDocument("testdoc")
320+
elem = QgsSymbolLayerUtils.saveSymbol('test', symbol, doc)
321+
322+
restored_symbol = QgsSymbolLayerUtils.loadSymbol(elem)
323+
restored_layer = restored_symbol.symbolLayer(0)
324+
self.assertFalse(restored_layer.enabled())
325+
self.assertTrue(restored_layer.isLocked())
326+
self.assertEqual(restored_layer.renderingPass(), 5)
327+
328+
def testClone(self):
329+
""" test that base symbol layer properties are cloned with layer """
330+
331+
layer = QgsSimpleFillSymbolLayer()
332+
layer.setEnabled(False)
333+
layer.setLocked(True)
334+
layer.setRenderingPass(5)
335+
336+
symbol = QgsFillSymbol()
337+
symbol.changeSymbolLayer(0, layer)
338+
339+
cloned_symbol = symbol.clone()
340+
cloned_layer = cloned_symbol.symbolLayer(0)
341+
self.assertFalse(cloned_layer.enabled())
342+
self.assertTrue(cloned_layer.isLocked())
343+
self.assertEqual(cloned_layer.renderingPass(), 5)
344+
345+
def imageCheck(self, name, reference_image, image):
346+
self.report += "<h2>Render {}</h2>\n".format(name)
347+
temp_dir = QDir.tempPath() + '/'
348+
file_name = temp_dir + 'symbollayer_' + name + ".png"
349+
image.save(file_name, "PNG")
350+
checker = QgsRenderChecker()
351+
checker.setControlPathPrefix("symbol_layer")
352+
checker.setControlName("expected_" + reference_image)
353+
checker.setRenderedImage(file_name)
354+
checker.setColorTolerance(2)
355+
result = checker.compareImages(name, 20)
356+
self.report += checker.report()
357+
print((self.report))
358+
return result
359+
360+
def testRenderFillLayerDisabled(self):
361+
""" test that rendering a fill symbol with disabled layer works"""
362+
layer = QgsSimpleFillSymbolLayer()
363+
layer.setEnabled(False)
364+
365+
symbol = QgsFillSymbol()
366+
symbol.changeSymbolLayer(0, layer)
367+
368+
image = QImage(200, 200, QImage.Format_RGB32)
369+
painter = QPainter()
370+
ms = QgsMapSettings()
371+
372+
geom = QgsGeometry.fromWkt('Polygon ((0 0, 10 0, 10 10, 0 10, 0 0))')
373+
f = QgsFeature()
374+
f.setGeometry(geom)
375+
376+
extent = geom.geometry().boundingBox()
377+
# buffer extent by 10%
378+
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
379+
380+
ms.setExtent(extent)
381+
ms.setOutputSize(image.size())
382+
context = QgsRenderContext.fromMapSettings(ms)
383+
context.setPainter(painter)
384+
context.setScaleFactor(96 / 25.4) # 96 DPI
385+
386+
painter.begin(image)
387+
image.fill(QColor(255, 255, 255))
388+
389+
symbol.startRender(context)
390+
symbol.renderFeature(f, context)
391+
symbol.stopRender(context)
392+
painter.end()
393+
394+
self.assertTrue(self.imageCheck('symbol_layer', 'symbollayer_disabled', image))
395+
396+
def testRenderLineLayerDisabled(self):
397+
""" test that rendering a line symbol with disabled layer works"""
398+
layer = QgsSimpleLineSymbolLayer()
399+
layer.setEnabled(False)
400+
401+
symbol = QgsLineSymbol()
402+
symbol.changeSymbolLayer(0, layer)
403+
404+
image = QImage(200, 200, QImage.Format_RGB32)
405+
painter = QPainter()
406+
ms = QgsMapSettings()
407+
408+
geom = QgsGeometry.fromWkt('LineString (0 0,3 4,4 3)')
409+
f = QgsFeature()
410+
f.setGeometry(geom)
411+
412+
extent = geom.geometry().boundingBox()
413+
# buffer extent by 10%
414+
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
415+
416+
ms.setExtent(extent)
417+
ms.setOutputSize(image.size())
418+
context = QgsRenderContext.fromMapSettings(ms)
419+
context.setPainter(painter)
420+
context.setScaleFactor(96 / 25.4) # 96 DPI
421+
422+
painter.begin(image)
423+
image.fill(QColor(255, 255, 255))
424+
425+
symbol.startRender(context)
426+
symbol.renderFeature(f, context)
427+
symbol.stopRender(context)
428+
painter.end()
429+
430+
self.assertTrue(self.imageCheck('symbol_layer', 'symbollayer_disabled', image))
431+
432+
def testRenderMarkerLayerDisabled(self):
433+
""" test that rendering a marker symbol with disabled layer works"""
434+
layer = QgsSimpleMarkerSymbolLayer()
435+
layer.setEnabled(False)
436+
437+
symbol = QgsMarkerSymbol()
438+
symbol.changeSymbolLayer(0, layer)
439+
440+
image = QImage(200, 200, QImage.Format_RGB32)
441+
painter = QPainter()
442+
ms = QgsMapSettings()
443+
444+
geom = QgsGeometry.fromWkt('Point (1 2)')
445+
f = QgsFeature()
446+
f.setGeometry(geom)
447+
448+
extent = QgsRectangle(0, 0, 4, 4)
449+
450+
ms.setExtent(extent)
451+
ms.setOutputSize(image.size())
452+
context = QgsRenderContext.fromMapSettings(ms)
453+
context.setPainter(painter)
454+
context.setScaleFactor(96 / 25.4) # 96 DPI
455+
456+
painter.begin(image)
457+
image.fill(QColor(255, 255, 255))
458+
459+
symbol.startRender(context)
460+
symbol.renderFeature(f, context)
461+
symbol.stopRender(context)
462+
painter.end()
463+
464+
self.assertTrue(self.imageCheck('symbol_layer', 'symbollayer_disabled', image))
465+
273466
def testQgsSimpleFillSymbolLayer(self):
274467
"""Create a new style from a .sld file and match test.
275468
"""

0 commit comments

Comments
 (0)
Please sign in to comment.