QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
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 "qgsmaskingwidget.h"
20
22#include "qgsmasksymbollayer.h"
23#include "qgsmessagebaritem.h"
24#include "qgsproject.h"
25#include "qgsrenderer.h"
30#include "qgssymbol.h"
33#include "qgsvectorlayer.h"
35#include "qgsvectorlayerutils.h"
36
37#include <QCheckBox>
38#include <QSet>
39
40#include "moc_qgsmaskingwidget.cpp"
41
42QgsMaskingWidget::QgsMaskingWidget( QWidget *parent )
43 : QgsPanelWidget( parent )
44{
45 setupUi( this );
46
47 connect( mMaskTargetsWidget, &QgsSymbolLayerSelectionWidget::changed, this, &QgsMaskingWidget::onSelectionChanged );
48 connect( mMaskSourcesWidget, &QgsMaskSourceSelectionWidget::changed, this, &QgsMaskingWidget::onSelectionChanged );
49}
50
51void QgsMaskingWidget::onSelectionChanged()
52{
53 // display message if configuration is not consistent
54 const bool printMessage = mMaskTargetsWidget->selection().empty() != mMaskSourcesWidget->sourceSet().isEmpty();
55
56 if ( mMessageBarItem && !printMessage )
57 {
58 mMessageBar->popWidget( mMessageBarItem );
59 delete mMessageBarItem;
60 }
61 else if ( !mMessageBarItem && printMessage )
62 {
63 mMessageBarItem = new QgsMessageBarItem( tr( "Select both masking and masked symbol layers or your configuration will be lost" ), Qgis::MessageLevel::Warning, 0, this );
64 mMessageBar->pushItem( mMessageBarItem );
65 }
66
67 emit widgetChanged();
68}
69
79QList<QPair<QString, QList<QgsSymbolLayerReference>>> symbolLayerMasks( const QgsVectorLayer *layer )
80{
81 if ( !layer->renderer() )
82 return {};
83
84 QList<QPair<QString, QList<QgsSymbolLayerReference>>> mMasks;
85 SymbolLayerVisitor collector( [&]( const QgsSymbolLayer *sl ) {
86 if ( !sl->masks().isEmpty() )
87 mMasks.push_back( qMakePair( sl->id(), sl->masks() ) );
88 } );
89 layer->renderer()->accept( &collector );
90 return mMasks;
91}
92
93QString symbolLayerSelectiveMaskingSourceId( const QgsVectorLayer *layer, QSet<QString> &maskedSymbolLayers )
94{
95 if ( !layer->renderer() )
96 return {};
97
98 QString maskingSourceSetId;
99 SymbolLayerVisitor collector( [&]( const QgsSymbolLayer *sl ) {
100 if ( !sl->selectiveMaskingSourceSetId().isEmpty() )
101 {
102 maskingSourceSetId = sl->selectiveMaskingSourceSetId();
103 maskedSymbolLayers.insert( sl->id() );
104 }
105 } );
106 layer->renderer()->accept( &collector );
107 return maskingSourceSetId;
108}
109
110void QgsMaskingWidget::setLayer( QgsVectorLayer *layer )
111{
112 mLayer = layer;
113 populate();
114}
115
116void QgsMaskingWidget::populate()
117{
118 const QSignalBlocker blockerSourceWidget( mMaskSourcesWidget );
119 const QSignalBlocker blockerTargetWidget( mMaskTargetsWidget );
120
121 mMaskSourcesWidget->update();
122 mMaskTargetsWidget->setLayer( mLayer );
123
125 QSet<QString> maskedSymbolLayers;
126
127 // The widget only allows a single set of masking sources for ALL the symbol layers in a target layer.
128 // So if ANY of the symbols layers in the target layer have an explicit selective masking source set id present,
129 // then use this as the SOURCE for all the masked symbol layers in that map layer.
130 const QString selectiveMaskingSourceSetId = symbolLayerSelectiveMaskingSourceId( mLayer, maskedSymbolLayers );
131 if ( !selectiveMaskingSourceSetId.isEmpty() )
132 {
133 maskSources = QgsProject::instance()->selectiveMaskingSourceSetManager()->setById( selectiveMaskingSourceSetId );
134 }
135 else
136 {
137 // collect masks and filter on those which have the current layer as destination
138 QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
139
140 for ( auto layerIt = layers.begin(); layerIt != layers.end(); layerIt++ )
141 {
142 const QString layerId = layerIt.key();
143 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() );
144 if ( !vl )
145 continue;
146
147 // collect symbol layer masks
148 const QList<QPair<QString, QList<QgsSymbolLayerReference>>> slMasks = symbolLayerMasks( vl );
149 for ( const QPair<QString, QList<QgsSymbolLayerReference>> &p : slMasks )
150 {
151 const QString &sourceSymbolLayerId = p.first;
152 for ( const QgsSymbolLayerReference &ref : p.second )
153 {
154 if ( ref.layerId() == mLayer->id() )
155 {
156 // add to the set of destinations
157 maskedSymbolLayers.insert( ref.symbolLayerIdV2() );
158 // add to the list of mask sources
159 maskSources.append( QgsSelectiveMaskSource( layerId, Qgis::SelectiveMaskSourceType::SymbolLayer, sourceSymbolLayerId ) );
160 }
161 }
162 }
163
164 // collect label masks
165 QHash<QString, QgsMaskedLayers> labelMasks = QgsVectorLayerUtils::collectObjectsMaskedByLabelsFromLayer( vl, {}, {} );
166 for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
167 {
168 const QString &ruleKey = it.key();
169 for ( auto it2 = it.value().begin(); it2 != it.value().end(); it2++ )
170 {
171 if ( it2.key() == mLayer->id() )
172 {
173 // merge with masked symbol layers
174 maskedSymbolLayers.unite( it2.value().symbolLayerIdsToMask );
175 // add the mask source
176 maskSources.append( QgsSelectiveMaskSource( layerId, Qgis::SelectiveMaskSourceType::Label, ruleKey ) );
177 }
178 }
179 }
180 }
181 }
182
183 mMaskSourcesWidget->setSourceSet( maskSources );
184 mMaskTargetsWidget->setSelection( maskedSymbolLayers );
185}
186
187void QgsMaskingWidget::apply()
188{
189 if ( !mLayer )
190 return;
191
192 QSet<QString> layersToRefresh;
193 const QSet<QString> maskedSymbolLayerIds = mMaskTargetsWidget->selection();
194
195 const QgsSelectiveMaskingSourceSet maskSourceSet = mMaskSourcesWidget->sourceSet();
196
197 SymbolLayerVisitor selectiveMaskingSourceSetter( [&]( const QgsSymbolLayer *sl ) {
198 QgsSymbolLayer *mutableSl = const_cast<QgsSymbolLayer *>( sl );
199 if ( !maskSourceSet.isValid() )
200 {
201 mutableSl->setSelectiveMaskingSourceSetId( QString() );
202 }
203 else
204 {
205 const bool isMasked = maskedSymbolLayerIds.contains( sl->id() );
206 mutableSl->setSelectiveMaskingSourceSetId( isMasked ? maskSourceSet.id() : QString() );
207 }
208 } );
209 if ( mLayer && mLayer->renderer() )
210 {
211 mLayer->renderer()->accept( &selectiveMaskingSourceSetter );
212 }
213 for ( const QgsSelectiveMaskSource &source : maskSourceSet.sources() )
214 {
215 layersToRefresh.insert( source.layerId() );
216 }
217
218 QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
219 for ( auto layerIt = layers.begin(); layerIt != layers.end(); layerIt++ )
220 {
221 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() );
222 if ( !vl )
223 continue;
224
225 //
226 // First reset symbol layer masks
227 SymbolLayerVisitor maskSetter( [&]( const QgsSymbolLayer *sl ) {
228 if ( sl->layerType() == "MaskMarker" )
229 {
230 QgsMaskMarkerSymbolLayer *maskSl = const_cast<QgsMaskMarkerSymbolLayer *>( static_cast<const QgsMaskMarkerSymbolLayer *>( sl ) );
231
232 const QgsSymbolLayerReferenceList masks = maskSl->masks();
234 for ( const QgsSymbolLayerReference &ref : masks )
235 {
236 // copy the original masks, only those with another destination layer
237 if ( ref.layerId() != mLayer->id() )
238 newMasks.append( ref );
239 }
240 if ( !maskSourceSet.isValid() )
241 {
242 for ( const QgsSelectiveMaskSource &source : maskSourceSet.sources() )
243 {
244 switch ( source.sourceType() )
245 {
247 if ( source.layerId() == layerIt.key() && source.sourceId() == sl->id() )
248 {
249 // ... then add the new masked symbol layers, if any
250 for ( const QString &maskedId : maskedSymbolLayerIds )
251 {
252 newMasks.append( QgsSymbolLayerReference( mLayer->id(), maskedId ) );
253 }
254 // invalidate the cache of the source layer
255 layersToRefresh.insert( source.layerId() );
256 }
257 break;
259 break;
260 }
261 }
262 }
263 maskSl->setMasks( newMasks );
264 }
265 } );
266 if ( vl->renderer() )
267 vl->renderer()->accept( &maskSetter );
268
269 //
270 // Now reset label masks
271 if ( !vl->labeling() )
272 continue;
273 for ( const QString &labelProvider : vl->labeling()->subProviders() )
274 {
275 // clear symbol layers
276 QgsPalLayerSettings settings = vl->labeling()->settings( labelProvider );
277 QgsTextFormat format = settings.format();
278 if ( !format.mask().enabled() )
279 continue;
280 const QgsSymbolLayerReferenceList masks = format.mask().maskedSymbolLayers();
282 for ( const QgsSymbolLayerReference &ref : masks )
283 {
284 // copy the original masks, only those with another destination layer
285 if ( ref.layerId() != mLayer->id() )
286 newMasks.append( ref );
287 }
288 if ( !maskSourceSet.isValid() )
289 {
290 for ( const QgsSelectiveMaskSource &source : maskSourceSet.sources() )
291 {
292 // ... then add the new masked symbol layers, if any
293 switch ( source.sourceType() )
294 {
296 if ( source.layerId() == layerIt.key() && source.sourceId() == labelProvider )
297 {
298 for ( const QString &maskedId : maskedSymbolLayerIds )
299 {
300 newMasks.append( QgsSymbolLayerReference( mLayer->id(), maskedId ) );
301 }
302 // invalidate the cache of the source layer
303 layersToRefresh.insert( source.layerId() );
304 }
305 break;
306
308 break;
309 }
310 }
311 }
312 format.mask().setMaskedSymbolLayers( newMasks );
313 settings.setFormat( format );
314 vl->labeling()->setSettings( new QgsPalLayerSettings( settings ), labelProvider );
315 }
316 }
317
319 // trigger refresh of the current layer
320 mLayer->triggerRepaint();
321 // trigger refresh of dependent layers (i.e. mask source layers)
322 for ( const QString &layerId : layersToRefresh )
323 {
324 QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerId );
325 layer->triggerRepaint();
326 }
327}
328
329SymbolLayerVisitor::SymbolLayerVisitor( SymbolLayerVisitor::SymbolLayerCallback callback )
330 : mCallback( std::move( callback ) )
331{}
332
333bool SymbolLayerVisitor::visitEnter( const QgsStyleEntityVisitorInterface::Node &node )
334{
336 return false;
337
338 return true;
339}
340
341void SymbolLayerVisitor::visitSymbol( const QgsSymbol *symbol, const QString &leafIdentifier )
342{
343 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
344 {
345 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
346
347 mCallback( sl );
348
349 // recurse over sub symbols
350 const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
351 if ( subSymbol )
352 visitSymbol( subSymbol, leafIdentifier );
353 }
354}
355
356bool SymbolLayerVisitor::visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf )
357{
358 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
359 {
360 auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
361 if ( symbolEntity->symbol() )
362 visitSymbol( symbolEntity->symbol(), leaf.identifier );
363 }
364 return true;
365}
366
@ Label
A mask generated from a labeling provider.
Definition qgis.h:3127
@ SymbolLayer
A mask generated from a symbol layer.
Definition qgis.h:3126
@ Warning
Warning message.
Definition qgis.h:161
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:83
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
Special symbol layer that uses its sub symbol as a selective mask.
void setMasks(const QList< QgsSymbolLayerReference > &maskedLayers)
Sets the symbol layers that will be masked by the sub symbol's shape.
QList< QgsSymbolLayerReference > masks() const override
Returns a list of references to symbol layers that are masked by the sub symbol's shape.
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 an inline panel.
static QgsProject * instance()
Returns the QgsProject singleton instance.
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).
const QgsSelectiveMaskingSourceSetManager * selectiveMaskingSourceSetManager() const
Returns the project's selective masking set manager, which manages storage of a set of selective mask...
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Encapsulates a single source for selective masking (e.g.
QgsSelectiveMaskingSourceSet setById(const QString &id) const
Returns the set with a matching id, or an invalid set if no matching sets were found.
Represents a named set of selective masking sources (QgsSelectiveMaskSource).
QString id() const
Returns a unique identifier for the set.
QVector< QgsSelectiveMaskSource > sources() const
Returns the list of selective mask sources configured in this set.
void append(const QgsSelectiveMaskSource &source)
Appends a source to the set.
bool isValid() const
Returns true if the source set is valid, or false if it is invalid (default constructed).
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:1398
@ SymbolEntity
Symbols.
Definition qgsstyle.h:206
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.
Abstract base class for symbol layers.
QString selectiveMaskingSourceSetId() const
Returns the selective masking source set ID for this symbol layer.
void setSelectiveMaskingSourceSetId(const QString &id)
Sets the selective masking source set id for this symbol layer.
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:231
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition qgssymbol.h:353
Container for all settings relating to text rendering.
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 > collectObjectsMaskedByLabelsFromLayer(const QgsVectorLayer *layer, const QHash< QString, QgsSelectiveMaskingSourceSet > &selectiveMaskingSourceSets, const QVector< QgsVectorLayer * > &allRenderedVectorLayers)
Returns all objects that will be masked by the labels for a given vector layer.
Represents a vector layer which manages a vector based dataset.
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
Contains information relating to a node (i.e.
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.