QGIS API Documentation  2.99.0-Master (0a63d1f)
qgslayertreemapcanvasbridge.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayertreemapcanvasbridge.cpp
3  --------------------------------------
4  Date : May 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 
17 
18 #include "qgslayertree.h"
19 #include "qgslayertreeutils.h"
20 #include "qgsmaplayer.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsmapcanvas.h"
23 #include "qgsmapoverviewcanvas.h"
24 #include "qgsproject.h"
25 
27  : QObject( parent )
28  , mRoot( root )
29  , mCanvas( canvas )
30  , mOverviewCanvas( nullptr )
31  , mPendingCanvasUpdate( false )
32  , mHasCustomLayerOrder( false )
33  , mAutoSetupOnFirstLayer( true )
34  , mAutoEnableCrsTransform( true )
35  , mLastLayerCount( !root->findLayers().isEmpty() )
36 {
37  connect( root, SIGNAL( addedChildren( QgsLayerTreeNode*, int, int ) ), this, SLOT( nodeAddedChildren( QgsLayerTreeNode*, int, int ) ) );
38  connect( root, SIGNAL( customPropertyChanged( QgsLayerTreeNode*, QString ) ), this, SLOT( nodeCustomPropertyChanged( QgsLayerTreeNode*, QString ) ) );
39  connect( root, SIGNAL( removedChildren( QgsLayerTreeNode*, int, int ) ), this, SLOT( nodeRemovedChildren() ) );
41 
43 }
44 
46 {
47  setHasCustomLayerOrder( false );
49 }
50 
52 {
53  QStringList order;
54  defaultLayerOrder( mRoot, order );
55  return order;
56 }
57 
59 {
60  if ( QgsLayerTree::isLayer( node ) )
61  {
62  QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
63  order << nodeLayer->layerId();
64  }
65 
66  Q_FOREACH ( QgsLayerTreeNode* child, node->children() )
67  defaultLayerOrder( child, order );
68 }
69 
70 
72 {
73  if ( mHasCustomLayerOrder == state )
74  return;
75 
76  mHasCustomLayerOrder = state;
78 
80 }
81 
82 void QgsLayerTreeMapCanvasBridge::setCustomLayerOrder( const QStringList& order )
83 {
84  if ( mCustomLayerOrder == order )
85  return;
86 
87  // verify that the new order is correct
88  QStringList defOrder( defaultLayerOrder() );
89  QStringList newOrder( order );
90  QStringList sortedNewOrder( order );
91  qSort( defOrder );
92  qSort( sortedNewOrder );
93 
94  if ( defOrder.size() < sortedNewOrder.size() )
95  {
96  // might contain bad layers, but also duplicates
97  QSet<QString> ids( defOrder.toSet() );
98 
99  for ( int i = 0; i < sortedNewOrder.size(); i++ )
100  {
101  if ( !ids.contains( sortedNewOrder[i] ) )
102  {
103  newOrder.removeAll( sortedNewOrder[i] );
104  sortedNewOrder.removeAt( i-- );
105  }
106  }
107  }
108 
109  if ( defOrder != sortedNewOrder )
110  return; // must be permutation of the default order
111 
112  mCustomLayerOrder = newOrder;
114 
115  if ( mHasCustomLayerOrder )
117 }
118 
120 {
121  QList<QgsMapLayer*> canvasLayers, overviewLayers;
122 
123  if ( mHasCustomLayerOrder )
124  {
125  Q_FOREACH ( const QString& layerId, mCustomLayerOrder )
126  {
127  QgsLayerTreeLayer* nodeLayer = mRoot->findLayer( layerId );
128  if ( nodeLayer )
129  {
130  if ( nodeLayer->isVisible() )
131  canvasLayers << nodeLayer->layer();
132  if ( nodeLayer->customProperty( QStringLiteral( "overview" ), 0 ).toInt() )
133  overviewLayers << nodeLayer->layer();
134  }
135  }
136  }
137  else
138  setCanvasLayers( mRoot, canvasLayers, overviewLayers );
139 
140  QList<QgsLayerTreeLayer*> layerNodes = mRoot->findLayers();
141  int currentLayerCount = layerNodes.count();
142  bool firstLayers = mAutoSetupOnFirstLayer && mLastLayerCount == 0 && currentLayerCount != 0;
143 
144  if ( firstLayers )
145  {
146  // also setup destination CRS and map units if the OTF projections are not yet enabled
148  {
149  Q_FOREACH ( QgsLayerTreeLayer* layerNode, layerNodes )
150  {
151  if ( !layerNode->layer() )
152  continue;
153 
154  if ( layerNode->layer()->isSpatial() )
155  {
156  mCanvas->setDestinationCrs( layerNode->layer()->crs() );
157  QgsProject::instance()->setCrs( layerNode->layer()->crs() );
158  mCanvas->setMapUnits( layerNode->layer()->crs().mapUnits() );
159  break;
160  }
161  }
162  }
163  }
164 
165  mCanvas->setLayers( canvasLayers );
166  if ( mOverviewCanvas )
167  mOverviewCanvas->setLayers( overviewLayers );
168 
169  if ( firstLayers )
170  {
171  // if we are moving from zero to non-zero layers, let's zoom to those data
173  }
174 
175  if ( !mFirstCRS.isValid() )
176  {
177  // find out what is the first used CRS in case we may need to turn on OTF projections later
178  Q_FOREACH ( QgsLayerTreeLayer* layerNode, layerNodes )
179  {
180  if ( layerNode->layer() && layerNode->layer()->crs().isValid() )
181  {
182  mFirstCRS = layerNode->layer()->crs();
183  break;
184  }
185  }
186  }
187 
189  {
190  // check whether all layers still have the same CRS
191  Q_FOREACH ( QgsLayerTreeLayer* layerNode, layerNodes )
192  {
193  if ( layerNode->layer() && layerNode->layer()->crs().isValid() && layerNode->layer()->crs() != mFirstCRS )
194  {
198  break;
199  }
200  }
201  }
202 
203  mLastLayerCount = currentLayerCount;
204  if ( currentLayerCount == 0 )
206 
207  mPendingCanvasUpdate = false;
208 }
209 
210 void QgsLayerTreeMapCanvasBridge::readProject( const QDomDocument& doc )
211 {
212  mFirstCRS = QgsCoordinateReferenceSystem(); // invalidate on project load
213 
214  QDomElement elem = doc.documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
215  if ( elem.isNull() )
216  {
217  bool oldEnabled;
218  QStringList oldOrder;
219  if ( QgsLayerTreeUtils::readOldLegendLayerOrder( doc.documentElement().firstChildElement( QStringLiteral( "legend" ) ), oldEnabled, oldOrder ) )
220  {
221  setHasCustomLayerOrder( oldEnabled );
222  setCustomLayerOrder( oldOrder );
223  }
224  return;
225  }
226 
227  QDomElement customOrderElem = elem.firstChildElement( QStringLiteral( "custom-order" ) );
228  if ( !customOrderElem.isNull() )
229  {
230  QStringList order;
231  QDomElement itemElem = customOrderElem.firstChildElement( QStringLiteral( "item" ) );
232  while ( !itemElem.isNull() )
233  {
234  order.append( itemElem.text() );
235  itemElem = itemElem.nextSiblingElement( QStringLiteral( "item" ) );
236  }
237 
238  setHasCustomLayerOrder( customOrderElem.attribute( QStringLiteral( "enabled" ), QString() ).toInt() );
239  setCustomLayerOrder( order );
240  }
241 }
242 
244 {
245  QDomElement elem = doc.createElement( QStringLiteral( "layer-tree-canvas" ) );
246  QDomElement customOrderElem = doc.createElement( QStringLiteral( "custom-order" ) );
247  customOrderElem.setAttribute( QStringLiteral( "enabled" ), mHasCustomLayerOrder ? 1 : 0 );
248 
249  Q_FOREACH ( const QString& layerId, mCustomLayerOrder )
250  {
251  QDomElement itemElem = doc.createElement( QStringLiteral( "item" ) );
252  itemElem.appendChild( doc.createTextNode( layerId ) );
253  customOrderElem.appendChild( itemElem );
254  }
255  elem.appendChild( customOrderElem );
256 
257  doc.documentElement().appendChild( elem );
258 }
259 
260 void QgsLayerTreeMapCanvasBridge::setCanvasLayers( QgsLayerTreeNode *node, QList<QgsMapLayer*> &canvasLayers, QList<QgsMapLayer*>& overviewLayers )
261 {
262  if ( QgsLayerTree::isLayer( node ) )
263  {
264  QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
265  if ( nodeLayer->isVisible() )
266  canvasLayers << nodeLayer->layer();
267  if ( nodeLayer->customProperty( QStringLiteral( "overview" ), 0 ).toInt() )
268  overviewLayers << nodeLayer->layer();
269  }
270 
271  Q_FOREACH ( QgsLayerTreeNode* child, node->children() )
272  setCanvasLayers( child, canvasLayers, overviewLayers );
273 }
274 
276 {
277  if ( mPendingCanvasUpdate )
278  return;
279 
280  mPendingCanvasUpdate = true;
281  QMetaObject::invokeMethod( this, "setCanvasLayers", Qt::QueuedConnection );
282 }
283 
284 void QgsLayerTreeMapCanvasBridge::nodeAddedChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
285 {
286  Q_ASSERT( node );
287 
288  // collect layer IDs that have been added in order to put them into custom layer order
289  QStringList layerIds;
290  QList<QgsLayerTreeNode*> children = node->children();
291  for ( int i = indexFrom; i <= indexTo; ++i )
292  {
293  QgsLayerTreeNode* child = children.at( i );
294  if ( QgsLayerTree::isLayer( child ) )
295  {
296  layerIds << QgsLayerTree::toLayer( child )->layerId();
297  }
298  else if ( QgsLayerTree::isGroup( child ) )
299  {
300  Q_FOREACH ( QgsLayerTreeLayer* nodeL, QgsLayerTree::toGroup( child )->findLayers() )
301  layerIds << nodeL->layerId();
302  }
303  }
304 
305  Q_FOREACH ( const QString& layerId, layerIds )
306  {
307  if ( !mCustomLayerOrder.contains( layerId ) )
308  mCustomLayerOrder.append( layerId );
309  }
310 
312 
314 }
315 
317 {
318  // no need to disconnect from removed nodes as they are deleted
319 
320  // check whether the layers are still there, if not, remove them from the layer order!
321  QList<int> toRemove;
322  for ( int i = 0; i < mCustomLayerOrder.count(); ++i )
323  {
325  if ( !node )
326  toRemove << i;
327  }
328  for ( int i = toRemove.count() - 1; i >= 0; --i )
329  mCustomLayerOrder.removeAt( toRemove[i] );
331 
333 }
334 
336 {
338 }
339 
341 {
342  Q_UNUSED( node );
343  if ( key == QLatin1String( "overview" ) )
345 }
346 
static bool readOldLegendLayerOrder(const QDomElement &legendElem, bool &hasCustomOrder, QStringList &order)
Try to load custom layer order from.
Layer tree group node serves as a container for layers and further groups.
void hasCustomLayerOrderChanged(bool)
void setCustomLayerOrder(const QStringList &order)
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
Definition: qgsmaplayer.h:349
void nodeCustomPropertyChanged(QgsLayerTreeNode *node, const QString &key)
QgsLayerTreeMapCanvasBridge(QgsLayerTreeGroup *root, QgsMapCanvas *canvas, QObject *parent=nullptr)
Constructor: does not take ownership of the layer tree nor canvas.
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well) ...
void setCanvasLayers()
force update of canvas layers from the layer tree. Normally this should not be needed to be called...
QString layerId() const
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:74
QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group. No type checking is done - use isGroup() to find out whether this operation is ...
Definition: qgslayertree.h:46
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
void setCrsTransformEnabled(bool enabled)
sets whether to use projections for this layer set
void setLayers(const QList< QgsMapLayer *> &layers)
updates layer set for overview
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set map units (needed by project properties dialog)
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...
void nodeAddedChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the project&#39;s native coordinate reference system.
Definition: qgsproject.cpp:424
QgsCoordinateReferenceSystem crs() const
Returns the layer&#39;s spatial reference system.
This class is a base class for nodes in a layer tree.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
bool isLayer(QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:40
void readProject(const QDomDocument &doc)
QgsMapLayer * layer() const
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is ...
Definition: qgslayertree.h:52
void visibilityChanged(QgsLayerTreeNode *node)
Emitted when check state of a node within the tree has been changed.
void setLayers(const QList< QgsMapLayer *> &layers)
Set list of layers that should be shown in the canvas.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:350
void zoomToFullExtent()
Zoom to the full extent of all layers.
This class represents a coordinate reference system (CRS).
void customLayerOrderChanged(const QStringList &order)
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes. Searches recursively the whole sub-tree.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
QgsCoordinateReferenceSystem mFirstCRS
bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:34
Layer tree node points to a map layer.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.