QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsmaskingwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaskingwidget.cpp
3 ---------------------
4 begin : September 2019
5 copyright : (C) 2019 by Hugo Mercier
6 email : hugo dot mercier at oslandia 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
16
18
19#include <QSet>
20#include <QCheckBox>
21
22#include "qgsmaskingwidget.h"
26#include "qgsvectorlayer.h"
27#include "qgssymbol.h"
30#include "qgsproject.h"
31#include "qgsvectorlayerutils.h"
32#include "qgsmasksymbollayer.h"
34#include "qgsmessagebaritem.h"
35
36QgsMaskingWidget::QgsMaskingWidget( QWidget *parent ) :
37 QgsPanelWidget( parent )
38{
39 setupUi( this );
40}
41
42void QgsMaskingWidget::onSelectionChanged()
43{
44 // display message if configuration is not consistent
45 const bool printMessage = mMaskTargetsWidget->selection().empty() != mMaskSourcesWidget->selection().empty();
46
47 if ( mMessageBarItem && !printMessage )
48 {
49 mMessageBar->popWidget( mMessageBarItem );
50 delete mMessageBarItem;
51 }
52 else if ( !mMessageBarItem && printMessage )
53 {
54 mMessageBarItem = new QgsMessageBarItem( tr( "Select both sources and symbol layers or your configuration will be lost" ), Qgis::MessageLevel::Warning, 0, this );
55 mMessageBar->pushItem( mMessageBarItem );
56 }
57
58 emit widgetChanged();
59}
60
61void QgsMaskingWidget::showEvent( QShowEvent *event )
62{
63 Q_UNUSED( event );
64
65 // populate is quite long, so we delay it when the widget is first shown
66 if ( mMustPopulate )
67 {
68 disconnect( mMaskTargetsWidget, &QgsSymbolLayerSelectionWidget::changed, this, &QgsMaskingWidget::onSelectionChanged );
69 disconnect( mMaskSourcesWidget, &QgsMaskSourceSelectionWidget::changed, this, &QgsMaskingWidget::onSelectionChanged );
70
71 mMustPopulate = false;
72 populate();
73
74 connect( mMaskTargetsWidget, &QgsSymbolLayerSelectionWidget::changed, this, &QgsMaskingWidget::onSelectionChanged );
75 connect( mMaskSourcesWidget, &QgsMaskSourceSelectionWidget::changed, this, &QgsMaskingWidget::onSelectionChanged );
76
77 onSelectionChanged();
78 }
79}
80
81
82
83
93QList<QPair<QString, QList<QgsSymbolLayerReference>>> symbolLayerMasks( const QgsVectorLayer *layer )
94{
95 if ( ! layer->renderer() )
96 return {};
97
98 QList<QPair<QString, QList<QgsSymbolLayerReference>>> mMasks;
99 SymbolLayerVisitor collector( [&]( const QgsSymbolLayer * sl, const QString & lid )
100 {
101 if ( ! sl->masks().isEmpty() )
102 mMasks.push_back( qMakePair( lid, sl->masks() ) );
103 } );
104 layer->renderer()->accept( &collector );
105 return mMasks;
106}
107
108void QgsMaskingWidget::setLayer( QgsVectorLayer *layer )
109{
110 if ( mLayer != layer )
111 {
112 mLayer = layer;
113 mMustPopulate = true;
114 }
115}
116
117void QgsMaskingWidget::populate()
118{
119 mMaskSourcesWidget->update();
120 mMaskTargetsWidget->setLayer( mLayer );
121
122 // collect masks and filter on those which have the current layer as destination
123 QSet<QString> maskedSymbolLayers;
124 QList<QgsMaskSourceSelectionWidget::MaskSource> maskSources;
125 QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
126
128 for ( auto layerIt = layers.begin(); layerIt != layers.end(); layerIt++ )
129 {
130 const QString layerId = layerIt.key();
131 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() );
132 if ( ! vl )
133 continue;
134
135 // collect symbol layer masks
136 const QList<QPair<QString, QList<QgsSymbolLayerReference>>> slMasks = symbolLayerMasks( vl );
137 for ( const QPair<QString, QList<QgsSymbolLayerReference>> &p : slMasks )
138 {
139 const QString &sourceSymbolLayerId = p.first;
140 for ( const QgsSymbolLayerReference &ref : p.second )
141 {
142 if ( ref.layerId() == mLayer->id() )
143 {
144 // add to the set of destinations
145 maskedSymbolLayers.insert( ref.symbolLayerIdV2() );
146 // add to the list of mask sources
147 source.layerId = layerId;
148 source.isLabeling = false;
149 source.symbolLayerId = sourceSymbolLayerId;
150 maskSources.append( source );
151 }
152 }
153 }
154
155 // collect label masks
156 QHash<QString, QgsMaskedLayers> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
157 for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
158 {
159 const QString &ruleKey = it.key();
160 for ( auto it2 = it.value().begin(); it2 != it.value().end(); it2++ )
161 {
162 if ( it2.key() == mLayer->id() )
163 {
164 // merge with masked symbol layers
165 maskedSymbolLayers.unite( it2.value().symbolLayerIds );
166 // add the mask source
167 source.layerId = layerId;
168 source.isLabeling = true;
169 source.symbolLayerId = ruleKey;
170 maskSources.append( source );
171 }
172 }
173 }
174 }
175
176 mMaskSourcesWidget->setSelection( maskSources );
177 mMaskTargetsWidget->setSelection( maskedSymbolLayers );
178}
179
180void QgsMaskingWidget::apply()
181{
182 QList<QgsMaskSourceSelectionWidget::MaskSource> maskSources = mMaskSourcesWidget->selection();
183 QSet<QString> maskedSymbolLayers = mMaskTargetsWidget->selection();
184
185 QSet<QString> layersToRefresh;
186
187 QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
188 for ( auto layerIt = layers.begin(); layerIt != layers.end(); layerIt++ )
189 {
190 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() );
191 if ( ! vl )
192 continue;
193
194 //
195 // First reset symbol layer masks
196 SymbolLayerVisitor maskSetter( [&]( const QgsSymbolLayer * sl, const QString & slId )
197 {
198 if ( sl->layerType() == "MaskMarker" )
199 {
200 QgsMaskMarkerSymbolLayer *maskSl = const_cast<QgsMaskMarkerSymbolLayer *>( static_cast<const QgsMaskMarkerSymbolLayer *>( sl ) );
201
202 const QgsSymbolLayerReferenceList masks = maskSl->masks();
203 QgsSymbolLayerReferenceList newMasks;
204 for ( const QgsSymbolLayerReference &ref : masks )
205 {
206 // copy the original masks, only those with another destination layer
207 if ( ref.layerId() != mLayer->id() )
208 newMasks.append( ref );
209 }
210 for ( const QgsMaskSourceSelectionWidget::MaskSource &source : maskSources )
211 {
212 if ( ! source.isLabeling && source.layerId == layerIt.key() && source.symbolLayerId == slId )
213 {
214 // ... then add the new masked symbol layers, if any
215 for ( const QString &maskedId : maskedSymbolLayers )
216 {
217 newMasks.append( QgsSymbolLayerReference( mLayer->id(), maskedId ) );
218 }
219 // invalidate the cache of the source layer
220 layersToRefresh.insert( source.layerId );
221 }
222 }
223 maskSl->setMasks( newMasks );
224 }
225 } );
226 if ( vl->renderer() )
227 vl->renderer()->accept( &maskSetter );
228
229 //
230 // Now reset label masks
231 if ( ! vl->labeling() )
232 continue;
233 for ( const QString &labelProvider : vl->labeling()->subProviders() )
234 {
235 // clear symbol layers
236 QgsPalLayerSettings settings = vl->labeling()->settings( labelProvider );
237 QgsTextFormat format = settings.format();
238 if ( ! format.mask().enabled() )
239 continue;
240 const QgsSymbolLayerReferenceList masks = format.mask().maskedSymbolLayers();
242 for ( const QgsSymbolLayerReference &ref : masks )
243 {
244 // copy the original masks, only those with another destination layer
245 if ( ref.layerId() != mLayer->id() )
246 newMasks.append( ref );
247 }
248 for ( const QgsMaskSourceSelectionWidget::MaskSource &source : maskSources )
249 {
250 // ... then add the new masked symbol layers, if any
251
252 if ( source.isLabeling && source.layerId == layerIt.key() && source.symbolLayerId == labelProvider )
253 {
254 for ( const QString &maskedId : maskedSymbolLayers )
255 {
256 newMasks.append( QgsSymbolLayerReference( mLayer->id(), maskedId ) );
257 }
258 // invalidate the cache of the source layer
259 layersToRefresh.insert( source.layerId );
260 }
261 }
262 format.mask().setMaskedSymbolLayers( newMasks );
263 settings.setFormat( format );
264 vl->labeling()->setSettings( new QgsPalLayerSettings( settings ), labelProvider );
265 }
266 }
267
269 // trigger refresh of the current layer
270 mLayer->triggerRepaint();
271 // trigger refresh of dependent layers (i.e. mask source layers)
272 for ( const QString &layerId : layersToRefresh )
273 {
274 QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerId );
275 layer->triggerRepaint();
276 }
277}
278
279bool QgsMaskingWidget::hasBeenPopulated()
280{
281 return !mMustPopulate;
282}
283
284SymbolLayerVisitor::SymbolLayerVisitor( SymbolLayerVisitor::SymbolLayerCallback callback ) :
285 mCallback( callback )
286{}
287
288bool SymbolLayerVisitor::visitEnter( const QgsStyleEntityVisitorInterface::Node &node )
289{
291 return false;
292
293 mSymbolKey = node.identifier;
294 return true;
295}
296
297void SymbolLayerVisitor::visitSymbol( const QgsSymbol *symbol, const QString &leafIdentifier, QVector<int> rootPath )
298{
299 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
300 {
301 QVector<int> indexPath = rootPath;
302 indexPath.push_back( idx );
303
304 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
305
306 mCallback( sl, sl->id() );
307
308 // recurse over sub symbols
309 const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
310 if ( subSymbol )
311 visitSymbol( subSymbol, leafIdentifier, indexPath );
312 }
313}
314
315bool SymbolLayerVisitor::visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf )
316{
317 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
318 {
319 auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
320 if ( symbolEntity->symbol() )
321 visitSymbol( symbolEntity->symbol(), leaf.identifier, {} );
322 }
323 return true;
324}
325
virtual QStringList subProviders() const
Gets list of sub-providers within the layer's labeling.
virtual void setSettings(QgsPalLayerSettings *settings, const QString &providerId=QString())=0
Set pal settings for a specific provider (takes ownership).
virtual QgsPalLayerSettings settings(const QString &providerId=QString()) const =0
Gets associated label settings.
virtual bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified symbology visitor, causing it to visit all symbols associated with the renderer...
Base class for all map layer types.
Definition: qgsmaplayer.h:75
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
void changed()
Emitted when an item was changed.
Represents an item shown within a QgsMessageBar widget.
Contains settings for how a map layer will be labeled.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
const QgsTextFormat & format() const
Returns the label text formatting settings, e.g., font settings, buffer settings, etc.
Base class for any widget that can be shown as a inline panel.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:599
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
virtual QgsStyle::StyleEntity type() const =0
Returns the type of style entity.
@ SymbolRule
Rule based symbology or label child rule.
A symbol entity for QgsStyle databases.
Definition: qgsstyle.h:1372
@ SymbolEntity
Symbols.
Definition: qgsstyle.h:180
Type used to refer to a specific symbol layer in a symbol of a layer.
QString symbolLayerIdV2() const
The symbol layer's id.
QString layerId() const
The referenced vector layer / feature renderer.
void changed()
Signal emitted when something the configuration is changed.
virtual QString layerType() const =0
Returns a string that represents this layer type.
QString id() const
Returns symbol layer identifier This id is unique in the whole project.
virtual QList< QgsSymbolLayerReference > masks() const
Returns masks defined by this symbol layer.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:94
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
Definition: qgssymbol.cpp:760
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:215
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
void setMaskedSymbolLayers(const QList< QgsSymbolLayerReference > &maskedLayers)
Sets the symbol layers that will be masked by this buffer.
QList< QgsSymbolLayerReference > maskedSymbolLayers() const
Returns a list of references to symbol layers that are masked by this buffer.
bool enabled() const
Returns whether the mask is enabled.
static QHash< QString, QgsMaskedLayers > labelMasks(const QgsVectorLayer *)
Returns masks defined in labeling options of a layer.
Represents a vector layer which manages a vector based data sets.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QList< QgsSymbolLayerReference > QgsSymbolLayerReferenceList
bool isLabeling
Whether it is a labeling mask or not.
Contains information relating to a node (i.e.
QString identifier
A string identifying the node.
QgsStyleEntityVisitorInterface::NodeType type
Node type.
Contains information relating to the style entity currently being visited.
const QgsStyleEntityInterface * entity
Reference to style entity being visited.
QString identifier
A string identifying the style entity.