QGIS API Documentation  3.25.0-Master (10b47c2603)
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"
28 #include "qgsstyleentityvisitor.h"
29 #include "symbology/qgsrenderer.h"
30 #include "qgsproject.h"
31 #include "qgsvectorlayerutils.h"
32 #include "qgsmasksymbollayer.h"
33 #include "qgsvectorlayerlabeling.h"
34 #include "qgsmessagebaritem.h"
35 
36 QgsMaskingWidget::QgsMaskingWidget( QWidget *parent ) :
37  QgsPanelWidget( parent )
38 {
39  setupUi( this );
40 }
41 
42 void 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 
61 void 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 
93 QList<QPair<QgsSymbolLayerId, QList<QgsSymbolLayerReference>>> symbolLayerMasks( const QgsVectorLayer *layer )
94 {
95  if ( ! layer->renderer() )
96  return {};
97 
98  QList<QPair<QgsSymbolLayerId, QList<QgsSymbolLayerReference>>> mMasks;
99  SymbolLayerVisitor collector( [&]( const QgsSymbolLayer * sl, const QgsSymbolLayerId & 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 
108 void QgsMaskingWidget::setLayer( QgsVectorLayer *layer )
109 {
110  if ( mLayer != layer )
111  {
112  mLayer = layer;
113  mMustPopulate = true;
114  }
115 }
116 
117 void 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<QgsSymbolLayerId> 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<QgsSymbolLayerId, QList<QgsSymbolLayerReference>>> slMasks = symbolLayerMasks( vl );
137  for ( const QPair<QgsSymbolLayerId, QList<QgsSymbolLayerReference>> &p : slMasks )
138  {
139  const QgsSymbolLayerId &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.symbolLayerId() );
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 = QgsSymbolLayerId( ruleKey, {} );
170  maskSources.append( source );
171  }
172  }
173  }
174  }
175 
176  mMaskSourcesWidget->setSelection( maskSources );
177  mMaskTargetsWidget->setSelection( maskedSymbolLayers );
178 }
179 
180 void QgsMaskingWidget::apply()
181 {
182  QList<QgsMaskSourceSelectionWidget::MaskSource> maskSources = mMaskSourcesWidget->selection();
183  QSet<QgsSymbolLayerId> 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 QgsSymbolLayerId & 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 QgsSymbolLayerId &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.symbolKey() == labelProvider )
253  {
254  for ( const QgsSymbolLayerId &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 
279 bool QgsMaskingWidget::hasBeenPopulated()
280 {
281  return !mMustPopulate;
282 }
283 
284 SymbolLayerVisitor::SymbolLayerVisitor( SymbolLayerVisitor::SymbolLayerCallback callback ) :
285  mCallback( callback )
286 {}
287 
288 bool SymbolLayerVisitor::visitEnter( const QgsStyleEntityVisitorInterface::Node &node )
289 {
291  return false;
292 
293  mSymbolKey = node.identifier;
294  return true;
295 }
296 
297 void 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, QgsSymbolLayerId( mSymbolKey + leafIdentifier, indexPath ) );
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 
315 bool 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:73
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:479
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:575
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:1342
@ SymbolEntity
Symbols.
Definition: qgsstyle.h:180
We may need stable references to symbol layers, when pointers to symbol layers is not usable (when a ...
QString symbolKey() const
Returns the key associated to the symbol.
Type used to refer to a specific symbol layer in a symbol of a layer.
QgsSymbolLayerId symbolLayerId() 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.
virtual QList< QgsSymbolLayerReference > masks() const
Returns masks defined by this symbol layer.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
Definition: qgssymbol.cpp:423
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
QgsSymbolLayerId symbolLayerId
The symbol layer id.
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.