QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsmaplayerlegend.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaplayerlegend.cpp
3  --------------------------------------
4  Date : July 2014
5  Copyright : (C) 2014 by Martin Dobias
6  Email : wonder dot sk at gmail 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 #include "qgsmaplayerlegend.h"
17 
18 #include "qgssettings.h"
19 #include "qgslayertree.h"
21 #include "qgsmeshlayer.h"
22 #include "qgspluginlayer.h"
23 #include "qgsrasterlayer.h"
24 #include "qgsrenderer.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsdiagramrenderer.h"
27 
28 
30  : QObject( parent )
31 {
32 }
33 
34 void QgsMapLayerLegend::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
35 {
36  Q_UNUSED( elem )
37  Q_UNUSED( context )
38 }
39 
40 QDomElement QgsMapLayerLegend::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
41 {
42  Q_UNUSED( doc )
43  Q_UNUSED( context )
44  return QDomElement();
45 }
46 
48 {
49  return new QgsDefaultVectorLayerLegend( vl );
50 }
51 
53 {
54  return new QgsDefaultRasterLayerLegend( rl );
55 }
56 
58 {
59  return new QgsDefaultMeshLayerLegend( ml );
60 }
61 
62 // -------------------------------------------------------------------------
63 
64 
65 void QgsMapLayerLegendUtils::setLegendNodeOrder( QgsLayerTreeLayer *nodeLayer, const QList<int> &order )
66 {
67  QStringList orderStr;
68  const auto constOrder = order;
69  for ( int id : constOrder )
70  orderStr << QString::number( id );
71  QString str = orderStr.isEmpty() ? QStringLiteral( "empty" ) : orderStr.join( QStringLiteral( "," ) );
72 
73  nodeLayer->setCustomProperty( QStringLiteral( "legend/node-order" ), str );
74 }
75 
76 static int _originalLegendNodeCount( QgsLayerTreeLayer *nodeLayer )
77 {
78  // this is not particularly efficient way of finding out number of legend nodes
79  QList<QgsLayerTreeModelLegendNode *> lst = nodeLayer->layer()->legend()->createLayerTreeModelLegendNodes( nodeLayer );
80  int numNodes = lst.count();
81  qDeleteAll( lst );
82  return numNodes;
83 }
84 
85 static QList<int> _makeNodeOrder( QgsLayerTreeLayer *nodeLayer )
86 {
87  if ( !nodeLayer->layer() || !nodeLayer->layer()->legend() )
88  {
89  QgsDebugMsg( QStringLiteral( "Legend node order manipulation is invalid without existing legend" ) );
90  return QList<int>();
91  }
92 
93  int numNodes = _originalLegendNodeCount( nodeLayer );
94 
95  QList<int> order;
96  order.reserve( numNodes );
97  for ( int i = 0; i < numNodes; ++i )
98  order << i;
99  return order;
100 }
101 
103 {
104  QString orderStr = nodeLayer->customProperty( QStringLiteral( "legend/node-order" ) ).toString();
105 
106  if ( orderStr.isEmpty() )
107  return _makeNodeOrder( nodeLayer );
108 
109  if ( orderStr == QLatin1String( "empty" ) )
110  return QList<int>();
111 
112  int numNodes = _originalLegendNodeCount( nodeLayer );
113 
114  QList<int> lst;
115  const auto constSplit = orderStr.split( ',' );
116  for ( const QString &item : constSplit )
117  {
118  bool ok;
119  int id = item.toInt( &ok );
120  if ( !ok || id < 0 || id >= numNodes )
121  return _makeNodeOrder( nodeLayer );
122 
123  lst << id;
124  }
125 
126  return lst;
127 }
128 
130 {
131  return nodeLayer->customProperties().contains( QStringLiteral( "legend/node-order" ) );
132 }
133 
134 void QgsMapLayerLegendUtils::setLegendNodeUserLabel( QgsLayerTreeLayer *nodeLayer, int originalIndex, const QString &newLabel )
135 {
136  nodeLayer->setCustomProperty( "legend/label-" + QString::number( originalIndex ), newLabel );
137 }
138 
139 QString QgsMapLayerLegendUtils::legendNodeUserLabel( QgsLayerTreeLayer *nodeLayer, int originalIndex )
140 {
141  return nodeLayer->customProperty( "legend/label-" + QString::number( originalIndex ) ).toString();
142 }
143 
145 {
146  return nodeLayer->customProperties().contains( "legend/label-" + QString::number( originalIndex ) );
147 }
148 
149 
150 void QgsMapLayerLegendUtils::applyLayerNodeProperties( QgsLayerTreeLayer *nodeLayer, QList<QgsLayerTreeModelLegendNode *> &nodes )
151 {
152  // handle user labels
153  int i = 0;
154  const auto constNodes = nodes;
155  for ( QgsLayerTreeModelLegendNode *legendNode : constNodes )
156  {
157  QString userLabel = QgsMapLayerLegendUtils::legendNodeUserLabel( nodeLayer, i++ );
158  if ( !userLabel.isNull() )
159  legendNode->setUserLabel( userLabel );
160  }
161 
162  // handle user order of nodes
164  {
165  QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( nodeLayer );
166 
167  QList<QgsLayerTreeModelLegendNode *> newOrder;
168  QSet<int> usedIndices;
169  const auto constOrder = order;
170  for ( int idx : constOrder )
171  {
172  if ( usedIndices.contains( idx ) )
173  {
174  QgsDebugMsg( QStringLiteral( "invalid node order. ignoring." ) );
175  return;
176  }
177 
178  newOrder << nodes[idx];
179  usedIndices << idx;
180  }
181 
182  // delete unused nodes
183  for ( int i = 0; i < nodes.count(); ++i )
184  {
185  if ( !usedIndices.contains( i ) )
186  delete nodes[i];
187  }
188 
189  nodes = newOrder;
190  }
191 
192 }
193 
194 // -------------------------------------------------------------------------
195 
196 
198  : mLayer( vl )
199 {
201 }
202 
203 QList<QgsLayerTreeModelLegendNode *> QgsDefaultVectorLayerLegend::createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer )
204 {
205  QList<QgsLayerTreeModelLegendNode *> nodes;
206 
207  QgsFeatureRenderer *r = mLayer->renderer();
208  if ( !r )
209  return nodes;
210 
211  if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toBool() )
212  mLayer->countSymbolFeatures();
213 
214  QgsSettings settings;
215  if ( settings.value( QStringLiteral( "qgis/showLegendClassifiers" ), false ).toBool() && !r->legendClassificationAttribute().isEmpty() )
216  {
217  nodes.append( new QgsSimpleLegendNode( nodeLayer, r->legendClassificationAttribute() ) );
218  }
219 
220  const auto constLegendSymbolItems = r->legendSymbolItems();
221  for ( const QgsLegendSymbolItem &i : constLegendSymbolItems )
222  {
223  if ( i.dataDefinedSizeLegendSettings() )
224  nodes << new QgsDataDefinedSizeLegendNode( nodeLayer, *i.dataDefinedSizeLegendSettings() );
225  else
226  {
227  QgsSymbolLegendNode *legendNode = new QgsSymbolLegendNode( nodeLayer, i );
228  if ( mTextOnSymbolEnabled && mTextOnSymbolContent.contains( i.ruleKey() ) )
229  {
230  legendNode->setTextOnSymbolLabel( mTextOnSymbolContent.value( i.ruleKey() ) );
231  legendNode->setTextOnSymbolTextFormat( mTextOnSymbolTextFormat );
232  }
233  nodes << legendNode;
234  }
235  }
236 
237  if ( nodes.count() == 1 && nodes[0]->data( Qt::EditRole ).toString().isEmpty() )
238  nodes[0]->setEmbeddedInParent( true );
239 
240 
241  if ( mLayer->diagramsEnabled() )
242  {
243  const auto constLegendItems = mLayer->diagramRenderer()->legendItems( nodeLayer );
244  for ( QgsLayerTreeModelLegendNode *i : constLegendItems )
245  {
246  nodes.append( i );
247  }
248  }
249 
250 
251  return nodes;
252 }
253 
254 void QgsDefaultVectorLayerLegend::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
255 {
256  mTextOnSymbolEnabled = false;
257  mTextOnSymbolTextFormat = QgsTextFormat();
258  mTextOnSymbolContent.clear();
259 
260  QDomElement tosElem = elem.firstChildElement( QStringLiteral( "text-on-symbol" ) );
261  if ( !tosElem.isNull() )
262  {
263  mTextOnSymbolEnabled = true;
264  QDomElement tosFormatElem = tosElem.firstChildElement( QStringLiteral( "text-style" ) );
265  mTextOnSymbolTextFormat.readXml( tosFormatElem, context );
266  QDomElement tosContentElem = tosElem.firstChildElement( QStringLiteral( "content" ) );
267  QDomElement tosContentItemElem = tosContentElem.firstChildElement( QStringLiteral( "item" ) );
268  while ( !tosContentItemElem.isNull() )
269  {
270  mTextOnSymbolContent.insert( tosContentItemElem.attribute( QStringLiteral( "key" ) ), tosContentItemElem.attribute( QStringLiteral( "value" ) ) );
271  tosContentItemElem = tosContentItemElem.nextSiblingElement( QStringLiteral( "item" ) );
272  }
273  }
274 }
275 
276 QDomElement QgsDefaultVectorLayerLegend::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
277 {
278  QDomElement elem = doc.createElement( QStringLiteral( "legend" ) );
279  elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "default-vector" ) );
280 
281  if ( mTextOnSymbolEnabled )
282  {
283  QDomElement tosElem = doc.createElement( QStringLiteral( "text-on-symbol" ) );
284  QDomElement tosFormatElem = mTextOnSymbolTextFormat.writeXml( doc, context );
285  tosElem.appendChild( tosFormatElem );
286  QDomElement tosContentElem = doc.createElement( QStringLiteral( "content" ) );
287  for ( auto it = mTextOnSymbolContent.constBegin(); it != mTextOnSymbolContent.constEnd(); ++it )
288  {
289  QDomElement tosContentItemElem = doc.createElement( QStringLiteral( "item" ) );
290  tosContentItemElem.setAttribute( QStringLiteral( "key" ), it.key() );
291  tosContentItemElem.setAttribute( QStringLiteral( "value" ), it.value() );
292  tosContentElem.appendChild( tosContentItemElem );
293  }
294  tosElem.appendChild( tosContentElem );
295  elem.appendChild( tosElem );
296  }
297 
298  return elem;
299 }
300 
301 
302 // -------------------------------------------------------------------------
303 
304 
306  : mLayer( rl )
307 {
309 }
310 
311 QList<QgsLayerTreeModelLegendNode *> QgsDefaultRasterLayerLegend::createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer )
312 {
313  QList<QgsLayerTreeModelLegendNode *> nodes;
314 
315  // temporary solution for WMS. Ideally should be done with a delegate.
316  if ( mLayer->dataProvider() && mLayer->dataProvider()->supportsLegendGraphic() )
317  {
318  nodes << new QgsWmsLegendNode( nodeLayer );
319  }
320 
321  QgsLegendColorList rasterItemList = mLayer->legendSymbologyItems();
322  if ( rasterItemList.isEmpty() )
323  return nodes;
324 
325  // Paletted raster may have many colors, for example UInt16 may have 65536 colors
326  // and it is very slow, so we limit max count
327  int count = 0;
328  int max_count = 1000;
329 
330  for ( QgsLegendColorList::const_iterator itemIt = rasterItemList.constBegin();
331  itemIt != rasterItemList.constEnd(); ++itemIt, ++count )
332  {
333  nodes << new QgsRasterSymbolLegendNode( nodeLayer, itemIt->second, itemIt->first );
334 
335  if ( count == max_count )
336  {
337  QString label = tr( "following %1 items\nnot displayed" ).arg( rasterItemList.size() - max_count );
338  nodes << new QgsSimpleLegendNode( nodeLayer, label );
339  break;
340  }
341  }
342 
343  return nodes;
344 }
345 
346 // -------------------------------------------------------------------------
347 
349  : mLayer( ml )
350 {
352 }
353 
354 QList<QgsLayerTreeModelLegendNode *> QgsDefaultMeshLayerLegend::createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer )
355 {
356  QList<QgsLayerTreeModelLegendNode *> nodes;
357 
358  QgsMeshDataProvider *provider = mLayer->dataProvider();
359  if ( !provider )
360  return nodes;
361 
362  QgsMeshRendererSettings rendererSettings = mLayer->rendererSettings();
363 
364  QgsMeshDatasetIndex indexScalar = rendererSettings.activeScalarDataset();
365  QgsMeshDatasetIndex indexVector = rendererSettings.activeVectorDataset();
366 
367  QString name;
368  if ( indexScalar.isValid() && indexVector.isValid() && indexScalar.group() != indexVector.group() )
369  name = QString( "%1 / %2" ).arg( provider->datasetGroupMetadata( indexScalar.group() ).name(), provider->datasetGroupMetadata( indexVector.group() ).name() );
370  else if ( indexScalar.isValid() )
371  name = provider->datasetGroupMetadata( indexScalar.group() ).name();
372  else if ( indexVector.isValid() )
373  name = provider->datasetGroupMetadata( indexVector.group() ).name();
374  else
375  {
376  // neither contours nor vectors get rendered - no legend needed
377  return nodes;
378  }
379 
380  nodes << new QgsSimpleLegendNode( nodeLayer, name );
381 
382  if ( indexScalar.isValid() )
383  {
384  QgsMeshRendererScalarSettings settings = rendererSettings.scalarSettings( indexScalar.group() );
385  QgsLegendColorList items;
386  settings.colorRampShader().legendSymbologyItems( items );
387  for ( const QPair< QString, QColor > &item : qgis::as_const( items ) )
388  {
389  nodes << new QgsRasterSymbolLegendNode( nodeLayer, item.second, item.first );
390  }
391  }
392 
393  return nodes;
394 }
The class is used as a container of context for various read/write operations on other objects...
virtual QgsLegendSymbolList legendSymbolItems() const
Returns a list of symbology items for the legend.
static void setLegendNodeOrder(QgsLayerTreeLayer *nodeLayer, const QList< int > &order)
QgsVectorLayerFeatureCounter * countSymbolFeatures()
Count features for symbols.
virtual QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Writes configuration to a DOM element, to be used later with readXml()
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
virtual QString legendClassificationAttribute() const
If supported by the renderer, return classification attribute for the use in legend.
Definition: qgsrenderer.h:342
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QgsMapLayerLegend * legend() const
Can be nullptr.
static void setLegendNodeUserLabel(QgsLayerTreeLayer *nodeLayer, int originalIndex, const QString &newLabel)
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
virtual QList< QgsLayerTreeModelLegendNode *> legendItems(QgsLayerTreeLayer *nodeLayer) const
Returns list of legend nodes for the diagram.
Default legend implementation for mesh layers.
Represents a mesh renderer settings for scalar datasets.
Implementation of legend node interface for displaying raster legend entries.
Produces legend node with a marker symbol.
static QList< int > legendNodeOrder(QgsLayerTreeLayer *nodeLayer)
void setTextOnSymbolLabel(const QString &label)
Sets label of text to be shown on top of the symbol.
Default legend implementation for raster layers.
Implementation of legend node interface for displaying preview of vector symbols and their labels and...
virtual bool supportsLegendGraphic() const
Returns whether the provider supplies a legend graphic.
static bool hasLegendNodeOrder(QgsLayerTreeLayer *nodeLayer)
QgsDefaultVectorLayerLegend(QgsVectorLayer *vl)
Implementation of legend node interface for displaying WMS legend entries.
QgsMeshRendererScalarSettings scalarSettings(int groupIndex) const
Returns renderer settings.
int group() const
Returns a group index.
QStringList customProperties() const
Returns list of keys stored in custom properties.
static QgsMapLayerLegend * defaultMeshLegend(QgsMeshLayer *ml)
Create new legend implementation for mesh layer.
QList< QgsLayerTreeModelLegendNode * > createLayerTreeModelLegendNodes(QgsLayerTreeLayer *nodeLayer) override
Returns list of legend nodes to be used for a particular layer tree layer node.
QgsRasterDataProvider * dataProvider() override
Returns the layer&#39;s data provider, it may be nullptr.
QgsMeshDatasetIndex activeVectorDataset() const
Returns active vector dataset.
Represents all mesh renderer settings.
virtual QList< QgsLayerTreeModelLegendNode * > createLayerTreeModelLegendNodes(QgsLayerTreeLayer *nodeLayer)=0
Returns list of legend nodes to be used for a particular layer tree layer node.
static QString legendNodeUserLabel(QgsLayerTreeLayer *nodeLayer, int originalIndex)
QgsMapLayerLegend(QObject *parent=nullptr)
Constructor for QgsMapLayerLegend.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QgsMeshRendererSettings rendererSettings() const
Returns renderer settings.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file...
The QgsMapLayerLegend class is abstract interface for implementations of legends for one map layer...
void legendSymbologyItems(QList< QPair< QString, QColor > > &symbolItems) const override
Returns legend symbology items if provided by renderer.
QgsFeatureRenderer * renderer()
Returns renderer.
static bool hasLegendNodeUserLabel(QgsLayerTreeLayer *nodeLayer, int originalIndex)
Implementation of legend node interface for displaying arbitrary label with icon. ...
QgsMeshDataProvider * dataProvider() override
Returns the layer&#39;s data provider, it may be nullptr.
void rendererChanged()
Signal emitted when renderer is changed.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
QList< QgsLayerTreeModelLegendNode * > createLayerTreeModelLegendNodes(QgsLayerTreeLayer *nodeLayer) override
Returns list of legend nodes to be used for a particular layer tree layer node.
const QgsDiagramRenderer * diagramRenderer() const
QgsColorRampShader colorRampShader() const
Returns color ramp shader function.
void setTextOnSymbolTextFormat(const QgsTextFormat &format)
Sets format of text to be shown on top of the symbol.
static QgsMapLayerLegend * defaultVectorLegend(QgsVectorLayer *vl)
Create new legend implementation for vector layer.
Base class for providing data for QgsMeshLayer.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
QList< QPair< QString, QColor > > QgsLegendColorList
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
void itemsChanged()
Emitted when existing items/nodes got invalid and should be replaced by new ones. ...
static QgsMapLayerLegend * defaultRasterLegend(QgsRasterLayer *rl)
Create new legend implementation for raster layer.
static void applyLayerNodeProperties(QgsLayerTreeLayer *nodeLayer, QList< QgsLayerTreeModelLegendNode *> &nodes)
update according to layer node&#39;s custom properties (order of items, user labels for items) ...
QgsDefaultRasterLayerLegend(QgsRasterLayer *rl)
QgsMeshDatasetIndex is index that identifies the dataset group (e.g.
QList< QgsLayerTreeModelLegendNode * > createLayerTreeModelLegendNodes(QgsLayerTreeLayer *nodeLayer) override
Returns list of legend nodes to be used for a particular layer tree layer node.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
bool isValid() const
Returns whether index is valid, ie at least groups is set.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context) override
Reads configuration from a DOM element previously written by writeXml()
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:90
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const override
Writes configuration to a DOM element, to be used later with readXml()
Container for all settings relating to text rendering.
Default legend implementation for vector layers.
QgsLegendColorList legendSymbologyItems() const
Returns a list with classification items (Text and color)
Represents a vector layer which manages a vector based data sets.
QgsDefaultMeshLayerLegend(QgsMeshLayer *ml)
Creates an instance for the given mesh layer.
virtual void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Reads configuration from a DOM element previously written by writeXml()
bool diagramsEnabled() const
Returns whether the layer contains diagrams which are enabled and should be drawn.
virtual void setUserLabel(const QString &userLabel)
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file...
Layer tree node points to a map layer.
virtual QgsMeshDatasetGroupMetadata datasetGroupMetadata(int groupIndex) const =0
Returns dataset group metadata.
QgsMeshDatasetIndex activeScalarDataset() const
Returns active scalar dataset.