mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
[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.
This commit is contained in:
parent
2a873b83ad
commit
b8c2e68fc5
@ -67,6 +67,22 @@ class QgsSymbolLayer
|
||||
|
||||
virtual ~QgsSymbolLayer();
|
||||
|
||||
/**
|
||||
* Returns true if symbol layer is enabled and will be drawn.
|
||||
* @note added in QGIS 3.0
|
||||
* @see setEnabled()
|
||||
*/
|
||||
bool enabled() const;
|
||||
|
||||
/**
|
||||
* Sets whether symbol layer is enabled and should be drawn. Disabled
|
||||
* layers are not drawn, but remain part of the symbol and can be re-enabled
|
||||
* when desired.
|
||||
* @note added in QGIS 3.0
|
||||
* @see enabled()
|
||||
*/
|
||||
void setEnabled( bool enabled );
|
||||
|
||||
/**
|
||||
* The fill color.
|
||||
*/
|
||||
|
@ -396,7 +396,12 @@ void QgsSymbol::startRender( QgsRenderContext& context, const QgsFields& fields
|
||||
mSymbolRenderContext->setExpressionContextScope( scope );
|
||||
|
||||
Q_FOREACH ( QgsSymbolLayer* layer, mLayers )
|
||||
{
|
||||
if ( !layer->enabled() )
|
||||
continue;
|
||||
|
||||
layer->startRender( symbolContext );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSymbol::stopRender( QgsRenderContext& context )
|
||||
@ -405,7 +410,12 @@ void QgsSymbol::stopRender( QgsRenderContext& context )
|
||||
if ( mSymbolRenderContext )
|
||||
{
|
||||
Q_FOREACH ( QgsSymbolLayer* layer, mLayers )
|
||||
{
|
||||
if ( !layer->enabled() )
|
||||
continue;
|
||||
|
||||
layer->stopRender( *mSymbolRenderContext );
|
||||
}
|
||||
}
|
||||
|
||||
delete mSymbolRenderContext;
|
||||
@ -442,6 +452,9 @@ void QgsSymbol::drawPreviewIcon( QPainter* painter, QSize size, QgsRenderContext
|
||||
|
||||
Q_FOREACH ( QgsSymbolLayer* layer, mLayers )
|
||||
{
|
||||
if ( !layer->enabled() )
|
||||
continue;
|
||||
|
||||
if ( mType == Fill && layer->type() == Line )
|
||||
{
|
||||
// line symbol layer would normally draw just a line
|
||||
@ -587,6 +600,7 @@ QgsSymbolLayerList QgsSymbol::cloneLayers() const
|
||||
QgsSymbolLayer* layer = ( *it )->clone();
|
||||
layer->setLocked(( *it )->isLocked() );
|
||||
layer->setRenderingPass(( *it )->renderingPass() );
|
||||
layer->setEnabled(( *it )->enabled() );
|
||||
lst.append( layer );
|
||||
}
|
||||
return lst;
|
||||
@ -1417,7 +1431,7 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature* f, QgsRender
|
||||
if ( layerIdx != -1 )
|
||||
{
|
||||
QgsSymbolLayer* symbolLayer = mLayers.value( layerIdx );
|
||||
if ( symbolLayer )
|
||||
if ( symbolLayer && symbolLayer->enabled() )
|
||||
{
|
||||
if ( symbolLayer->type() == QgsSymbol::Marker )
|
||||
{
|
||||
@ -1432,6 +1446,9 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature* f, QgsRender
|
||||
|
||||
Q_FOREACH ( QgsSymbolLayer* symbolLayer, mLayers )
|
||||
{
|
||||
if ( !symbolLayer->enabled() )
|
||||
continue;
|
||||
|
||||
if ( symbolLayer->type() == QgsSymbol::Marker )
|
||||
{
|
||||
QgsMarkerSymbolLayer* markerLayer = static_cast<QgsMarkerSymbolLayer*>( symbolLayer );
|
||||
@ -1625,7 +1642,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF& points, const QgsFeature* f
|
||||
if ( layerIdx != -1 )
|
||||
{
|
||||
QgsSymbolLayer* symbolLayer = mLayers.value( layerIdx );
|
||||
if ( symbolLayer )
|
||||
if ( symbolLayer && symbolLayer->enabled() )
|
||||
{
|
||||
if ( symbolLayer->type() == QgsSymbol::Line )
|
||||
{
|
||||
@ -1640,6 +1657,9 @@ void QgsLineSymbol::renderPolyline( const QPolygonF& points, const QgsFeature* f
|
||||
|
||||
Q_FOREACH ( QgsSymbolLayer* symbolLayer, mLayers )
|
||||
{
|
||||
if ( !symbolLayer->enabled() )
|
||||
continue;
|
||||
|
||||
if ( symbolLayer->type() == QgsSymbol::Line )
|
||||
{
|
||||
QgsLineSymbolLayer* lineLayer = static_cast<QgsLineSymbolLayer*>( symbolLayer );
|
||||
@ -1704,7 +1724,7 @@ void QgsFillSymbol::renderPolygon( const QPolygonF& points, QList<QPolygonF>* ri
|
||||
if ( layerIdx != -1 )
|
||||
{
|
||||
QgsSymbolLayer* symbolLayer = mLayers.value( layerIdx );
|
||||
if ( symbolLayer )
|
||||
if ( symbolLayer && symbolLayer->enabled() )
|
||||
{
|
||||
if ( symbolLayer->type() == Fill || symbolLayer->type() == Line )
|
||||
renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext );
|
||||
@ -1716,6 +1736,9 @@ void QgsFillSymbol::renderPolygon( const QPolygonF& points, QList<QPolygonF>* ri
|
||||
|
||||
Q_FOREACH ( QgsSymbolLayer* symbolLayer, mLayers )
|
||||
{
|
||||
if ( !symbolLayer->enabled() )
|
||||
continue;
|
||||
|
||||
if ( symbolLayer->type() == Fill || symbolLayer->type() == Line )
|
||||
renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext );
|
||||
else
|
||||
|
@ -261,6 +261,7 @@ void QgsSymbolLayer::setPaintEffect( QgsPaintEffect *effect )
|
||||
|
||||
QgsSymbolLayer::QgsSymbolLayer( QgsSymbol::SymbolType type, bool locked )
|
||||
: mType( type )
|
||||
, mEnabled( true )
|
||||
, mLocked( locked )
|
||||
, mRenderingPass( 0 )
|
||||
, mPaintEffect( nullptr )
|
||||
|
@ -53,6 +53,22 @@ class CORE_EXPORT QgsSymbolLayer
|
||||
|
||||
virtual ~QgsSymbolLayer();
|
||||
|
||||
/**
|
||||
* Returns true if symbol layer is enabled and will be drawn.
|
||||
* @note added in QGIS 3.0
|
||||
* @see setEnabled()
|
||||
*/
|
||||
bool enabled() const { return mEnabled; }
|
||||
|
||||
/**
|
||||
* Sets whether symbol layer is enabled and should be drawn. Disabled
|
||||
* layers are not drawn, but remain part of the symbol and can be re-enabled
|
||||
* when desired.
|
||||
* @note added in QGIS 3.0
|
||||
* @see enabled()
|
||||
*/
|
||||
void setEnabled( bool enabled ) { mEnabled = enabled; }
|
||||
|
||||
/**
|
||||
* The fill color.
|
||||
*/
|
||||
@ -266,6 +282,10 @@ class CORE_EXPORT QgsSymbolLayer
|
||||
QgsSymbolLayer( QgsSymbol::SymbolType type, bool locked = false );
|
||||
|
||||
QgsSymbol::SymbolType mType;
|
||||
|
||||
//! True if layer is enabled and should be drawn
|
||||
bool mEnabled;
|
||||
|
||||
bool mLocked;
|
||||
QColor mColor;
|
||||
int mRenderingPass;
|
||||
|
@ -852,6 +852,7 @@ QgsSymbolLayer* QgsSymbolLayerUtils::loadSymbolLayer( QDomElement& element )
|
||||
{
|
||||
QString layerClass = element.attribute( "class" );
|
||||
bool locked = element.attribute( "locked" ).toInt();
|
||||
bool enabled = element.attribute( "enabled", "1" ).toInt();
|
||||
int pass = element.attribute( "pass" ).toInt();
|
||||
|
||||
// parse properties
|
||||
@ -863,6 +864,7 @@ QgsSymbolLayer* QgsSymbolLayerUtils::loadSymbolLayer( QDomElement& element )
|
||||
{
|
||||
layer->setLocked( locked );
|
||||
layer->setRenderingPass( pass );
|
||||
layer->setEnabled( enabled );
|
||||
|
||||
//restore layer effect
|
||||
QDomElement effectElem = element.firstChildElement( "effect" );
|
||||
@ -910,6 +912,7 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString& name, QgsSymbol* sym
|
||||
|
||||
QDomElement layerEl = doc.createElement( "layer" );
|
||||
layerEl.setAttribute( "class", layer->layerType() );
|
||||
layerEl.setAttribute( "enabled", layer->enabled() );
|
||||
layerEl.setAttribute( "locked", layer->isLocked() );
|
||||
layerEl.setAttribute( "pass", layer->renderingPass() );
|
||||
saveProperties( layer->properties(), doc, layerEl );
|
||||
|
@ -112,6 +112,7 @@ QgsLayerPropertiesWidget::QgsLayerPropertiesWidget( QgsSymbolLayer* layer, const
|
||||
// update layer type combo box
|
||||
int idx = cboLayerType->findData( mLayer->layerType() );
|
||||
cboLayerType->setCurrentIndex( idx );
|
||||
mEnabledCheckBox->setChecked( mLayer->enabled() );
|
||||
// set the corresponding widget
|
||||
updateSymbolLayerWidget( layer );
|
||||
connect( cboLayerType, SIGNAL( currentIndexChanged( int ) ), this, SLOT( layerTypeChanged() ) );
|
||||
@ -236,3 +237,9 @@ void QgsLayerPropertiesWidget::reloadLayer()
|
||||
{
|
||||
emit changeLayer( mLayer );
|
||||
}
|
||||
|
||||
void QgsLayerPropertiesWidget::on_mEnabledCheckBox_toggled( bool enabled )
|
||||
{
|
||||
mLayer->setEnabled( enabled );
|
||||
emitSignalChanged();
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ class GUI_EXPORT QgsLayerPropertiesWidget : public QgsPanelWidget, private Ui::L
|
||||
|
||||
private slots:
|
||||
void reloadLayer();
|
||||
void on_mEnabledCheckBox_toggled( bool enabled );
|
||||
|
||||
private:
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@ -50,6 +50,9 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QgsEffectStackCompactWidget" name="mEffectWidget" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<widget class="QWidget" name="pageDummy">
|
||||
@ -69,7 +72,34 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QgsEffectStackCompactWidget" name="mEffectWidget" native="true"/>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="mEnabledCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable layer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -29,7 +29,7 @@ import os
|
||||
|
||||
from qgis.PyQt.QtCore import pyqtWrapperType, Qt, QDir, QFile, QIODevice, QPointF
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
from qgis.PyQt.QtGui import QColor
|
||||
from qgis.PyQt.QtGui import QColor, QImage, QPainter
|
||||
|
||||
from qgis.core import (QgsCentroidFillSymbolLayer,
|
||||
QgsEllipseSymbolLayer,
|
||||
@ -55,7 +55,17 @@ from qgis.core import (QgsCentroidFillSymbolLayer,
|
||||
QgsShapeburstFillSymbolLayer,
|
||||
QgsArrowSymbolLayer,
|
||||
QgsSymbol,
|
||||
QgsUnitTypes
|
||||
QgsUnitTypes,
|
||||
QgsFillSymbol,
|
||||
QgsLineSymbol,
|
||||
QgsMarkerSymbol,
|
||||
QgsSymbolLayerUtils,
|
||||
QgsMapSettings,
|
||||
QgsGeometry,
|
||||
QgsFeature,
|
||||
QgsRenderContext,
|
||||
QgsRenderChecker,
|
||||
QgsRectangle
|
||||
)
|
||||
from qgis.testing import start_app, unittest
|
||||
from utilities import unitTestDataPath
|
||||
@ -79,6 +89,14 @@ class TestQgsSymbolLayer(unittest.TestCase):
|
||||
returns NULL
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.report = "<h1>Python QgsSymbolLayer Tests</h1>\n"
|
||||
|
||||
def tearDown(self):
|
||||
report_file_path = "%s/qgistest.html" % QDir.tempPath()
|
||||
with open(report_file_path, 'a') as report_file:
|
||||
report_file.write(self.report)
|
||||
|
||||
def testBinding(self):
|
||||
"""Test python bindings existence."""
|
||||
mType = type(QgsSymbolLayer)
|
||||
@ -270,6 +288,181 @@ class TestQgsSymbolLayer(unittest.TestCase):
|
||||
mMessage = 'Expected "%s" got "%s"' % (mExpectedType, mType)
|
||||
assert mExpectedType == mType, mMessage
|
||||
|
||||
def testGettersSetters(self):
|
||||
""" test base class getters/setters """
|
||||
layer = QgsSimpleFillSymbolLayer()
|
||||
|
||||
layer.setEnabled(False)
|
||||
self.assertFalse(layer.enabled())
|
||||
layer.setEnabled(True)
|
||||
self.assertTrue(layer.enabled())
|
||||
|
||||
layer.setLocked(False)
|
||||
self.assertFalse(layer.isLocked())
|
||||
layer.setLocked(True)
|
||||
self.assertTrue(layer.isLocked())
|
||||
|
||||
layer.setRenderingPass(5)
|
||||
self.assertEqual(layer.renderingPass(), 5)
|
||||
|
||||
def testSaveRestore(self):
|
||||
""" Test saving and restoring base symbol layer properties to xml"""
|
||||
|
||||
layer = QgsSimpleFillSymbolLayer()
|
||||
layer.setEnabled(False)
|
||||
layer.setLocked(True)
|
||||
layer.setRenderingPass(5)
|
||||
|
||||
symbol = QgsFillSymbol()
|
||||
symbol.changeSymbolLayer(0, layer)
|
||||
|
||||
doc = QDomDocument("testdoc")
|
||||
elem = QgsSymbolLayerUtils.saveSymbol('test', symbol, doc)
|
||||
|
||||
restored_symbol = QgsSymbolLayerUtils.loadSymbol(elem)
|
||||
restored_layer = restored_symbol.symbolLayer(0)
|
||||
self.assertFalse(restored_layer.enabled())
|
||||
self.assertTrue(restored_layer.isLocked())
|
||||
self.assertEqual(restored_layer.renderingPass(), 5)
|
||||
|
||||
def testClone(self):
|
||||
""" test that base symbol layer properties are cloned with layer """
|
||||
|
||||
layer = QgsSimpleFillSymbolLayer()
|
||||
layer.setEnabled(False)
|
||||
layer.setLocked(True)
|
||||
layer.setRenderingPass(5)
|
||||
|
||||
symbol = QgsFillSymbol()
|
||||
symbol.changeSymbolLayer(0, layer)
|
||||
|
||||
cloned_symbol = symbol.clone()
|
||||
cloned_layer = cloned_symbol.symbolLayer(0)
|
||||
self.assertFalse(cloned_layer.enabled())
|
||||
self.assertTrue(cloned_layer.isLocked())
|
||||
self.assertEqual(cloned_layer.renderingPass(), 5)
|
||||
|
||||
def imageCheck(self, name, reference_image, image):
|
||||
self.report += "<h2>Render {}</h2>\n".format(name)
|
||||
temp_dir = QDir.tempPath() + '/'
|
||||
file_name = temp_dir + 'symbollayer_' + name + ".png"
|
||||
image.save(file_name, "PNG")
|
||||
checker = QgsRenderChecker()
|
||||
checker.setControlPathPrefix("symbol_layer")
|
||||
checker.setControlName("expected_" + reference_image)
|
||||
checker.setRenderedImage(file_name)
|
||||
checker.setColorTolerance(2)
|
||||
result = checker.compareImages(name, 20)
|
||||
self.report += checker.report()
|
||||
print((self.report))
|
||||
return result
|
||||
|
||||
def testRenderFillLayerDisabled(self):
|
||||
""" test that rendering a fill symbol with disabled layer works"""
|
||||
layer = QgsSimpleFillSymbolLayer()
|
||||
layer.setEnabled(False)
|
||||
|
||||
symbol = QgsFillSymbol()
|
||||
symbol.changeSymbolLayer(0, layer)
|
||||
|
||||
image = QImage(200, 200, QImage.Format_RGB32)
|
||||
painter = QPainter()
|
||||
ms = QgsMapSettings()
|
||||
|
||||
geom = QgsGeometry.fromWkt('Polygon ((0 0, 10 0, 10 10, 0 10, 0 0))')
|
||||
f = QgsFeature()
|
||||
f.setGeometry(geom)
|
||||
|
||||
extent = geom.geometry().boundingBox()
|
||||
# buffer extent by 10%
|
||||
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
|
||||
|
||||
ms.setExtent(extent)
|
||||
ms.setOutputSize(image.size())
|
||||
context = QgsRenderContext.fromMapSettings(ms)
|
||||
context.setPainter(painter)
|
||||
context.setScaleFactor(96 / 25.4) # 96 DPI
|
||||
|
||||
painter.begin(image)
|
||||
image.fill(QColor(255, 255, 255))
|
||||
|
||||
symbol.startRender(context)
|
||||
symbol.renderFeature(f, context)
|
||||
symbol.stopRender(context)
|
||||
painter.end()
|
||||
|
||||
self.assertTrue(self.imageCheck('symbol_layer', 'symbollayer_disabled', image))
|
||||
|
||||
def testRenderLineLayerDisabled(self):
|
||||
""" test that rendering a line symbol with disabled layer works"""
|
||||
layer = QgsSimpleLineSymbolLayer()
|
||||
layer.setEnabled(False)
|
||||
|
||||
symbol = QgsLineSymbol()
|
||||
symbol.changeSymbolLayer(0, layer)
|
||||
|
||||
image = QImage(200, 200, QImage.Format_RGB32)
|
||||
painter = QPainter()
|
||||
ms = QgsMapSettings()
|
||||
|
||||
geom = QgsGeometry.fromWkt('LineString (0 0,3 4,4 3)')
|
||||
f = QgsFeature()
|
||||
f.setGeometry(geom)
|
||||
|
||||
extent = geom.geometry().boundingBox()
|
||||
# buffer extent by 10%
|
||||
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
|
||||
|
||||
ms.setExtent(extent)
|
||||
ms.setOutputSize(image.size())
|
||||
context = QgsRenderContext.fromMapSettings(ms)
|
||||
context.setPainter(painter)
|
||||
context.setScaleFactor(96 / 25.4) # 96 DPI
|
||||
|
||||
painter.begin(image)
|
||||
image.fill(QColor(255, 255, 255))
|
||||
|
||||
symbol.startRender(context)
|
||||
symbol.renderFeature(f, context)
|
||||
symbol.stopRender(context)
|
||||
painter.end()
|
||||
|
||||
self.assertTrue(self.imageCheck('symbol_layer', 'symbollayer_disabled', image))
|
||||
|
||||
def testRenderMarkerLayerDisabled(self):
|
||||
""" test that rendering a marker symbol with disabled layer works"""
|
||||
layer = QgsSimpleMarkerSymbolLayer()
|
||||
layer.setEnabled(False)
|
||||
|
||||
symbol = QgsMarkerSymbol()
|
||||
symbol.changeSymbolLayer(0, layer)
|
||||
|
||||
image = QImage(200, 200, QImage.Format_RGB32)
|
||||
painter = QPainter()
|
||||
ms = QgsMapSettings()
|
||||
|
||||
geom = QgsGeometry.fromWkt('Point (1 2)')
|
||||
f = QgsFeature()
|
||||
f.setGeometry(geom)
|
||||
|
||||
extent = QgsRectangle(0, 0, 4, 4)
|
||||
|
||||
ms.setExtent(extent)
|
||||
ms.setOutputSize(image.size())
|
||||
context = QgsRenderContext.fromMapSettings(ms)
|
||||
context.setPainter(painter)
|
||||
context.setScaleFactor(96 / 25.4) # 96 DPI
|
||||
|
||||
painter.begin(image)
|
||||
image.fill(QColor(255, 255, 255))
|
||||
|
||||
symbol.startRender(context)
|
||||
symbol.renderFeature(f, context)
|
||||
symbol.stopRender(context)
|
||||
painter.end()
|
||||
|
||||
self.assertTrue(self.imageCheck('symbol_layer', 'symbollayer_disabled', image))
|
||||
|
||||
def testQgsSimpleFillSymbolLayer(self):
|
||||
"""Create a new style from a .sld file and match test.
|
||||
"""
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 610 B |
Loading…
x
Reference in New Issue
Block a user