QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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  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::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  QString layerId = layerIt.key();
131  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() );
132  if ( ! vl )
133  continue;
134 
135  // collect symbol layer masks
136  QList<QPair<QgsSymbolLayerId, QList<QgsSymbolLayerReference>>> slMasks = symbolLayerMasks( vl );
137  for ( auto 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, QHash<QString, QSet<QgsSymbolLayerId>>> 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() );
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  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 ( 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;
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 ( 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 
@ Warning
Definition: qgis.h:91
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:85
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:501
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:552
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:1201
@ 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 QgsSymbolLayerReferenceList masks() const
Returns masks defined by this symbol layer.
virtual QString layerType() const =0
Returns a string that represents this layer type.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:65
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
Definition: qgssymbol.cpp:411
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:199
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
QgsSymbolLayerReferenceList maskedSymbolLayers() const
Returns a list of references to symbol layers that are masked by this buffer.
void setMaskedSymbolLayers(QgsSymbolLayerReferenceList maskedLayers)
Sets the symbol layers that will be masked by this buffer.
bool enabled() const
Returns whether the mask is enabled.
static QHash< QString, QHash< QString, QSet< QgsSymbolLayerId > > > 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.