QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgslayertree.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayertree
3  ---------------------
4  begin : 22.3.2017
5  copyright : (C) 2017 by Matthias Kuhn
6  email : [email protected]
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 "qgslayertree.h"
17 #include "qgsmaplayerlistutils.h"
18 #include "qgsvectorlayer.h"
19 
21 {
22  connect( this, &QgsLayerTree::addedChildren, this, &QgsLayerTree::nodeAddedChildren );
23  connect( this, &QgsLayerTree::removedChildren, this, &QgsLayerTree::nodeRemovedChildren );
24 }
25 
27  : QgsLayerTreeGroup( other )
28  , mCustomLayerOrder( other.mCustomLayerOrder )
29  , mHasCustomLayerOrder( other.mHasCustomLayerOrder )
30 {
31  connect( this, &QgsLayerTree::addedChildren, this, &QgsLayerTree::nodeAddedChildren );
32  connect( this, &QgsLayerTree::removedChildren, this, &QgsLayerTree::nodeRemovedChildren );
33 }
34 
35 QList<QgsMapLayer *> QgsLayerTree::customLayerOrder() const
36 {
37  return _qgis_listQPointerToRaw( mCustomLayerOrder );
38 }
39 
40 void QgsLayerTree::setCustomLayerOrder( const QList<QgsMapLayer *> &customLayerOrder )
41 {
42  QgsWeakMapLayerPointerList newOrder = _qgis_listRawToQPointer( customLayerOrder );
43 
44  if ( newOrder == mCustomLayerOrder )
45  return;
46 
47  mCustomLayerOrder = newOrder;
49 
50  if ( mHasCustomLayerOrder )
51  emit layerOrderChanged();
52 }
53 
55 {
56  QList<QgsMapLayer *> layers;
57 
58  for ( const auto &layerId : customLayerOrder )
59  {
60  QgsLayerTreeLayer *nodeLayer = findLayer( layerId );
61  if ( nodeLayer )
62  {
63  // configuration from 2.x projects might have non spatial layers
64  QgsMapLayer *layer = nodeLayer->layer();
65  if ( !layer || !layer->isSpatial() )
66  {
67  continue;
68  }
69  layers.append( layer );
70  }
71  }
72  setCustomLayerOrder( layers );
73 }
74 
75 QList<QgsMapLayer *> QgsLayerTree::layerOrder() const
76 {
77  if ( mHasCustomLayerOrder )
78  {
79  return customLayerOrder();
80  }
81  else
82  {
83  QList<QgsMapLayer *> layers;
84  const QList< QgsLayerTreeLayer * > foundLayers = findLayers();
85  for ( const auto &treeLayer : foundLayers )
86  {
87  QgsMapLayer *layer = treeLayer->layer();
88  if ( !layer || !layer->isSpatial() )
89  {
90  continue;
91  }
92  layers.append( layer );
93  }
94  return layers;
95  }
96 }
97 
99 {
100  return mHasCustomLayerOrder;
101 }
102 
104 {
105  if ( hasCustomLayerOrder == mHasCustomLayerOrder )
106  return;
107 
108  mHasCustomLayerOrder = hasCustomLayerOrder;
109 
110  emit hasCustomLayerOrderChanged( hasCustomLayerOrder );
111  emit layerOrderChanged();
112 }
113 
114 QgsLayerTree *QgsLayerTree::readXml( QDomElement &element, const QgsReadWriteContext &context )
115 {
116  QgsLayerTree *tree = new QgsLayerTree();
117 
118  tree->readCommonXml( element );
119 
120  tree->readChildrenFromXml( element, context );
121 
122  return tree;
123 }
124 
125 void QgsLayerTree::writeXml( QDomElement &parentElement, const QgsReadWriteContext &context )
126 {
127  QDomDocument doc = parentElement.ownerDocument();
128  QDomElement elem = doc.createElement( QStringLiteral( "layer-tree-group" ) );
129 
130  writeCommonXml( elem );
131 
132  for ( QgsLayerTreeNode *node : qgis::as_const( mChildren ) )
133  node->writeXml( elem, context );
134 
135  QDomElement customOrderElem = doc.createElement( QStringLiteral( "custom-order" ) );
136  customOrderElem.setAttribute( QStringLiteral( "enabled" ), mHasCustomLayerOrder ? 1 : 0 );
137  elem.appendChild( customOrderElem );
138 
139  for ( QgsMapLayer *layer : qgis::as_const( mCustomLayerOrder ) )
140  {
141  // Safety belt, see https://github.com/qgis/QGIS/issues/26975
142  // Crash when deleting an item from the layout legend
143  if ( ! layer )
144  continue;
145  QDomElement layerElem = doc.createElement( QStringLiteral( "item" ) );
146  layerElem.appendChild( doc.createTextNode( layer->id() ) );
147  customOrderElem.appendChild( layerElem );
148  }
149 
150  elem.appendChild( customOrderElem );
151 
152  parentElement.appendChild( elem );
153 }
154 
156 {
157  return new QgsLayerTree( *this );
158 }
159 
161 {
163  setHasCustomLayerOrder( false );
164  setCustomLayerOrder( QStringList() );
165 }
166 
167 void QgsLayerTree::nodeAddedChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo )
168 {
169  Q_ASSERT( node );
170 
171  // collect layer IDs that have been added in order to put them into custom layer order
172  QList<QgsMapLayer *> layers;
173 
174  QList<QgsLayerTreeNode *> children = node->children();
175  for ( int i = indexFrom; i <= indexTo; ++i )
176  {
177  QgsLayerTreeNode *child = children.at( i );
178  if ( QgsLayerTree::isLayer( child ) )
179  {
180  layers << QgsLayerTree::toLayer( child )->layer();
181  }
182  else if ( QgsLayerTree::isGroup( child ) )
183  {
184  const auto nodeLayers = QgsLayerTree::toGroup( child )->findLayers();
185  for ( QgsLayerTreeLayer *nodeL : nodeLayers )
186  layers << nodeL->layer();
187  }
188  }
189 
190  for ( QgsMapLayer *layer : qgis::as_const( layers ) )
191  {
192  if ( !mCustomLayerOrder.contains( layer ) && layer )
193  mCustomLayerOrder.append( layer );
194  }
195 
197  emit layerOrderChanged();
198 }
199 
200 void QgsLayerTree::nodeRemovedChildren()
201 {
202  QList<QgsMapLayer *> layers = customLayerOrder();
203  auto layer = layers.begin();
204 
205  while ( layer != layers.end() )
206  {
207  if ( !findLayer( *layer ) )
208  layer = layers.erase( layer );
209  else
210  ++layer;
211  }
212 
213  // we need to ensure that the customLayerOrderChanged signal is ALWAYS raised
214  // here, since that order HAS changed due to removal of the child!
215  // setCustomLayerOrder will only emit this signal when the layers list
216  // at this stage is different to the stored customer layer order. If this
217  // isn't the case (i.e. the lists ARE the same) then manually emit the
218  // signal
219  const bool emitSignal = _qgis_listRawToQPointer( layers ) == mCustomLayerOrder;
220 
221  setCustomLayerOrder( layers );
222  if ( emitSignal )
224 
225  emit layerOrderChanged();
226 }
227 
228 void QgsLayerTree::addMissingLayers()
229 {
230  bool changed = false;
231 
232  const QList< QgsLayerTreeLayer * > layers = findLayers();
233  for ( const auto layer : layers )
234  {
235  if ( !mCustomLayerOrder.contains( layer->layer() ) &&
236  layer->layer() && layer->layer()->isSpatial() )
237  {
238  mCustomLayerOrder.append( layer->layer() );
239  changed = true;
240  }
241  }
242 
243  if ( changed )
244  {
246  if ( mHasCustomLayerOrder )
247  emit layerOrderChanged();
248  }
249 }
250 
251 void QgsLayerTree::readLayerOrderFromXml( const QDomElement &elem )
252 {
253  QStringList order;
254 
255  QDomElement customOrderElem = elem.firstChildElement( QStringLiteral( "custom-order" ) );
256  if ( !customOrderElem.isNull() )
257  {
258  setHasCustomLayerOrder( customOrderElem.attribute( QStringLiteral( "enabled" ) ).toInt() );
259 
260  QDomElement itemElem = customOrderElem.firstChildElement( QStringLiteral( "item" ) );
261  while ( !itemElem.isNull() )
262  {
263  order.append( itemElem.text() );
264  itemElem = itemElem.nextSiblingElement( QStringLiteral( "item" ) );
265  }
266  }
267 
268  setCustomLayerOrder( order );
269  addMissingLayers();
270 }
The class is used as a container of context for various read/write operations on other objects...
void readChildrenFromXml(QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
Base class for all map layer types.
Definition: qgsmaplayer.h:78
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:43
void removeAllChildren()
Remove all child nodes.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:64
void layerOrderChanged()
Emitted when the layer order has changed.
void customLayerOrderChanged()
Emitted when the custom layer order has changed.
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
void removedChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)
Emitted when one or more nodes has been removed from a node within the tree.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:32
void addedChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)
Emitted when one or more nodes have been added to a node within the tree.
void writeCommonXml(QDomElement &element)
Write common XML elements.
void setHasCustomLayerOrder(bool hasCustomLayerOrder)
Determines if the layer order should be derived from the layer tree or if a custom override order sha...
void clear()
Clear any information from this layer tree.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
This class is a base class for nodes in a layer tree.
bool hasCustomLayerOrder() const
Determines if the layer order should be derived from the layer tree or if a custom override order sha...
void readLayerOrderFromXml(const QDomElement &doc)
Load the layer order from an XML element.
void hasCustomLayerOrderChanged(bool hasCustomLayerOrder)
Emitted when the hasCustomLayerOrder flag changes.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
QgsLayerTreeGroup(const QString &name=QString(), bool checked=true)
Constructor.
void setCustomLayerOrder(const QList< QgsMapLayer *> &customLayerOrder)
The order in which layers will be rendered on the canvas.
void writeXml(QDomElement &parentElement, const QgsReadWriteContext &context) override
Write layer tree to XML.
QList< QgsWeakMapLayerPointer > QgsWeakMapLayerPointerList
A list of weak pointers to QgsMapLayers.
Definition: qgsmaplayer.h:1583
static QgsLayerTree * readXml(QDomElement &element, const QgsReadWriteContext &context)
Load the layer tree from an XML element.
void readCommonXml(QDomElement &element)
Read common XML elements.
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
QgsLayerTree()
Create a new empty layer tree.
QList< QgsLayerTreeNode * > mChildren
list of children - node is responsible for their deletion
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
QgsLayerTree * clone() const override
Create a copy of the node. Returns new instance.
Layer tree node points to a map layer.