QGIS API Documentation  2.99.0-Master (d55fa22)
qgslayertreeview.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayertreeview.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 
16 #include "qgslayertreeview.h"
17 
18 #include "qgslayertree.h"
20 #include "qgslayertreemodel.h"
23 #include "qgsmaplayer.h"
24 #include "qgsgui.h"
25 
26 #include <QMenu>
27 #include <QContextMenuEvent>
28 
29 
31  : QTreeView( parent )
32  , mDefaultActions( nullptr )
33  , mMenuProvider( nullptr )
34 {
35  setHeaderHidden( true );
36 
37  setDragEnabled( true );
38  setAcceptDrops( true );
39  setDropIndicatorShown( true );
40  setEditTriggers( EditKeyPressed );
41  setExpandsOnDoubleClick( false ); // normally used for other actions
42 
43  setSelectionMode( ExtendedSelection );
44  setDefaultDropAction( Qt::MoveAction );
45 
46  connect( this, &QTreeView::collapsed, this, &QgsLayerTreeView::updateExpandedStateToNode );
47  connect( this, &QTreeView::expanded, this, &QgsLayerTreeView::updateExpandedStateToNode );
48 }
49 
51 {
52  delete mMenuProvider;
53 }
54 
55 void QgsLayerTreeView::setModel( QAbstractItemModel *model )
56 {
57  if ( !qobject_cast<QgsLayerTreeModel *>( model ) )
58  return;
59 
60  connect( model, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted );
61  connect( model, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved );
62 
63  QTreeView::setModel( model );
64 
66 
67  connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayerTreeView::onCurrentChanged );
68 
69  connect( layerTreeModel(), &QAbstractItemModel::modelReset, this, &QgsLayerTreeView::onModelReset );
70 
72 }
73 
75 {
76  return qobject_cast<QgsLayerTreeModel *>( model() );
77 }
78 
80 {
81  if ( !mDefaultActions )
83  return mDefaultActions;
84 }
85 
87 {
88  delete mMenuProvider;
90 }
91 
93 {
94  return layerForIndex( currentIndex() );
95 }
96 
98 {
99  if ( !layer )
100  {
101  setCurrentIndex( QModelIndex() );
102  return;
103  }
104 
105  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layer->id() );
106  if ( !nodeLayer )
107  return;
108 
109  setCurrentIndex( layerTreeModel()->node2index( nodeLayer ) );
110 }
111 
112 
113 void QgsLayerTreeView::contextMenuEvent( QContextMenuEvent *event )
114 {
115  if ( !mMenuProvider )
116  return;
117 
118  QModelIndex idx = indexAt( event->pos() );
119  if ( !idx.isValid() )
120  setCurrentIndex( QModelIndex() );
121 
122  QMenu *menu = mMenuProvider->createContextMenu();
123  if ( menu && menu->actions().count() != 0 )
124  menu->exec( mapToGlobal( event->pos() ) );
125  delete menu;
126 }
127 
128 
129 void QgsLayerTreeView::modelRowsInserted( const QModelIndex &index, int start, int end )
130 {
131  QgsLayerTreeNode *parentNode = layerTreeModel()->index2node( index );
132  if ( !parentNode )
133  return;
134 
135  // Embedded widgets - replace placeholders in the model by actual widgets
136  if ( layerTreeModel()->testFlag( QgsLayerTreeModel::UseEmbeddedWidgets ) && QgsLayerTree::isLayer( parentNode ) )
137  {
138  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( parentNode );
139  if ( QgsMapLayer *layer = nodeLayer->layer() )
140  {
141  int widgetsCount = layer->customProperty( QStringLiteral( "embeddedWidgets/count" ), 0 ).toInt();
142  QList<QgsLayerTreeModelLegendNode *> legendNodes = layerTreeModel()->layerLegendNodes( nodeLayer, true );
143  for ( int i = 0; i < widgetsCount; ++i )
144  {
145  QString providerId = layer->customProperty( QStringLiteral( "embeddedWidgets/%1/id" ).arg( i ) ).toString();
146  if ( QgsLayerTreeEmbeddedWidgetProvider *provider = QgsGui::layerTreeEmbeddedWidgetRegistry()->provider( providerId ) )
147  {
148  QModelIndex index = layerTreeModel()->legendNode2index( legendNodes[i] );
149  setIndexWidget( index, provider->createWidget( layer, i ) );
150  }
151  }
152  }
153  }
154 
155 
156  if ( QgsLayerTree::isLayer( parentNode ) )
157  {
158  // if ShowLegendAsTree flag is enabled in model, we may need to expand some legend nodes
159  QStringList expandedNodeKeys = parentNode->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
160  if ( expandedNodeKeys.isEmpty() )
161  return;
162 
163  Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, layerTreeModel()->layerLegendNodes( QgsLayerTree::toLayer( parentNode ), true ) )
164  {
165  QString ruleKey = legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
166  if ( expandedNodeKeys.contains( ruleKey ) )
167  setExpanded( layerTreeModel()->legendNode2index( legendNode ), true );
168  }
169  return;
170  }
171 
172  QList<QgsLayerTreeNode *> children = parentNode->children();
173  for ( int i = start; i <= end; ++i )
174  {
175  updateExpandedStateFromNode( children[i] );
176  }
177 
178  // make sure we still have correct current layer
180 }
181 
183 {
184  // make sure we still have correct current layer
186 }
187 
188 void QgsLayerTreeView::updateExpandedStateToNode( const QModelIndex &index )
189 {
190  if ( QgsLayerTreeNode *node = layerTreeModel()->index2node( index ) )
191  {
192  node->setExpanded( isExpanded( index ) );
193  }
194  else if ( QgsLayerTreeModelLegendNode *node = layerTreeModel()->index2legendNode( index ) )
195  {
196  QString ruleKey = node->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
197  QStringList lst = node->layerNode()->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
198  bool expanded = isExpanded( index );
199  bool isInList = lst.contains( ruleKey );
200  if ( expanded && !isInList )
201  {
202  lst.append( ruleKey );
203  node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
204  }
205  else if ( !expanded && isInList )
206  {
207  lst.removeAll( ruleKey );
208  node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
209  }
210  }
211 }
212 
214 {
215  QgsMapLayer *layerCurrent = layerForIndex( currentIndex() );
216  QString layerCurrentID = layerCurrent ? layerCurrent->id() : QString();
217  if ( mCurrentLayerID == layerCurrentID )
218  return;
219 
220  // update the current index in model (the item will be underlined)
221  QModelIndex nodeLayerIndex;
222  if ( layerCurrent )
223  {
224  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerCurrentID );
225  if ( nodeLayer )
226  nodeLayerIndex = layerTreeModel()->node2index( nodeLayer );
227  }
228  layerTreeModel()->setCurrentIndex( nodeLayerIndex );
229 
230  mCurrentLayerID = layerCurrentID;
231  emit currentLayerChanged( layerCurrent );
232 }
233 
235 {
236  QModelIndex idx = layerTreeModel()->node2index( node );
237  if ( isExpanded( idx ) != expanded )
238  setExpanded( idx, expanded );
239 }
240 
242 {
244 }
245 
247 {
248  QModelIndex idx = layerTreeModel()->node2index( node );
249  setExpanded( idx, node->isExpanded() );
250 
251  Q_FOREACH ( QgsLayerTreeNode *child, node->children() )
253 }
254 
255 QgsMapLayer *QgsLayerTreeView::layerForIndex( const QModelIndex &index ) const
256 {
257  // Check if model has been set and index is valid
258  if ( layerTreeModel() && index.isValid( ) )
259  {
260  QgsLayerTreeNode *node = layerTreeModel()->index2node( index );
261  if ( node )
262  {
263  if ( QgsLayerTree::isLayer( node ) )
264  return QgsLayerTree::toLayer( node )->layer();
265  }
266  else
267  {
268  // possibly a legend node
270  if ( legendNode )
271  return legendNode->layerNode()->layer();
272  }
273  }
274  return nullptr;
275 }
276 
278 {
279  return layerTreeModel()->index2node( selectionModel()->currentIndex() );
280 }
281 
283 {
284  QgsLayerTreeNode *node = currentNode();
285  if ( QgsLayerTree::isGroup( node ) )
286  return QgsLayerTree::toGroup( node );
287  else if ( QgsLayerTree::isLayer( node ) )
288  {
289  QgsLayerTreeNode *parent = node->parent();
290  if ( QgsLayerTree::isGroup( parent ) )
291  return QgsLayerTree::toGroup( parent );
292  }
293 
294  if ( QgsLayerTreeModelLegendNode *legendNode = layerTreeModel()->index2legendNode( selectionModel()->currentIndex() ) )
295  {
296  QgsLayerTreeLayer *parent = legendNode->layerNode();
297  if ( QgsLayerTree::isGroup( parent->parent() ) )
298  return QgsLayerTree::toGroup( parent->parent() );
299  }
300 
301  return nullptr;
302 }
303 
305 {
306  return layerTreeModel()->index2legendNode( selectionModel()->currentIndex() );
307 }
308 
309 QList<QgsLayerTreeNode *> QgsLayerTreeView::selectedNodes( bool skipInternal ) const
310 {
311  return layerTreeModel()->indexes2nodes( selectionModel()->selectedIndexes(), skipInternal );
312 }
313 
314 QList<QgsLayerTreeLayer *> QgsLayerTreeView::selectedLayerNodes() const
315 {
316  QList<QgsLayerTreeLayer *> layerNodes;
317  Q_FOREACH ( QgsLayerTreeNode *node, selectedNodes() )
318  {
319  if ( QgsLayerTree::isLayer( node ) )
320  layerNodes << QgsLayerTree::toLayer( node );
321  }
322  return layerNodes;
323 }
324 
325 QList<QgsMapLayer *> QgsLayerTreeView::selectedLayers() const
326 {
327  QList<QgsMapLayer *> list;
328  Q_FOREACH ( QgsLayerTreeLayer *node, selectedLayerNodes() )
329  {
330  if ( node->layer() )
331  list << node->layer();
332  }
333  return list;
334 }
335 
336 
337 void QgsLayerTreeView::refreshLayerSymbology( const QString &layerId )
338 {
339  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerId );
340  if ( nodeLayer )
341  layerTreeModel()->refreshLayerLegend( nodeLayer );
342 }
343 
344 
345 static void _expandAllLegendNodes( QgsLayerTreeLayer *nodeLayer, bool expanded, QgsLayerTreeModel *model )
346 {
347  // for layers we also need to find out with legend nodes contain some children and make them expanded/collapsed
348  // if we are collapsing, we just write out an empty list
349  QStringList lst;
350  if ( expanded )
351  {
352  Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, model->layerLegendNodes( nodeLayer, true ) )
353  {
354  QString parentKey = legendNode->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString();
355  if ( !parentKey.isEmpty() && !lst.contains( parentKey ) )
356  lst << parentKey;
357  }
358  }
359  nodeLayer->setCustomProperty( "expandedLegendNodes", lst );
360 }
361 
362 
363 static void _expandAllNodes( QgsLayerTreeGroup *parent, bool expanded, QgsLayerTreeModel *model )
364 {
365  Q_FOREACH ( QgsLayerTreeNode *node, parent->children() )
366  {
367  node->setExpanded( expanded );
368  if ( QgsLayerTree::isGroup( node ) )
369  _expandAllNodes( QgsLayerTree::toGroup( node ), expanded, model );
370  else if ( QgsLayerTree::isLayer( node ) )
371  _expandAllLegendNodes( QgsLayerTree::toLayer( node ), expanded, model );
372  }
373 }
374 
375 
377 {
378  // unfortunately expandAll() does not emit expanded() signals
379  _expandAllNodes( layerTreeModel()->rootGroup(), true, layerTreeModel() );
380  expandAll();
381 }
382 
384 {
385  // unfortunately collapseAll() does not emit collapsed() signals
386  _expandAllNodes( layerTreeModel()->rootGroup(), false, layerTreeModel() );
387  collapseAll();
388 }
389 
390 void QgsLayerTreeView::mouseReleaseEvent( QMouseEvent *event )
391 {
392  const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
393  if ( event->modifiers() & Qt::ControlModifier )
395  else
397  QTreeView::mouseReleaseEvent( event );
398  layerTreeModel()->setFlags( oldFlags );
399 }
400 
401 void QgsLayerTreeView::keyPressEvent( QKeyEvent *event )
402 {
403  const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
404  if ( event->modifiers() & Qt::ControlModifier )
406  else
408  QTreeView::keyPressEvent( event );
409  layerTreeModel()->setFlags( oldFlags );
410 }
Layer tree group node serves as a container for layers and further groups.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:74
QList< QgsMapLayer * > selectedLayers() const
Get list of selected layers.
Base class for all map layer types.
Definition: qgsmaplayer.h:54
Implementation of this interface can be implemented to allow QgsLayerTreeView instance to provide cus...
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:42
void setCurrentIndex(const QModelIndex &currentIndex)
Set index of the current item. May be used by view. Item marked as current is underlined.
QList< QgsLayerTreeNode * > indexes2nodes(const QModelIndexList &list, bool skipInternal=false) const
Convert a list of indexes to a list of layer tree nodes.
QgsLayerTreeViewMenuProvider * mMenuProvider
Context menu provider. Owned by the view.
void refreshLayerSymbology(const QString &layerId)
Force refresh of layer symbology. Normally not needed as the changes of layer&#39;s renderer are monitore...
void contextMenuEvent(QContextMenuEvent *event) override
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:63
QgsLayerTreeLayer * layerNode() const
Return pointer to the parent layer node.
void modelRowsInserted(const QModelIndex &index, int start, int end)
QgsMapLayer * currentLayer() const
Get currently selected layer. May be null.
QgsLayerTreeViewDefaultActions * defaultActions()
Get access to the default actions that may be used with the tree view.
bool isExpanded() const
Return whether the node should be shown as expanded or collapsed in GUI.
QModelIndex node2index(QgsLayerTreeNode *node) const
Return index for a given node. If the node does not belong to the layer tree, the result is undefined...
virtual QMenu * createContextMenu()=0
Return a newly created menu instance (or null pointer on error)
void setFlags(QgsLayerTreeModel::Flags f)
Set OR-ed combination of model flags.
QList< QgsLayerTreeLayer * > selectedLayerNodes() const
Return list of selected nodes filtered to just layer nodes.
void mouseReleaseEvent(QMouseEvent *event) override
QString mCurrentLayerID
Keeps track of current layer ID (to check when to emit signal about change of current layer) ...
The QgsLayerTreeViewDefaultActions class serves as a factory of actions that can be used together wit...
The QgsLayerTreeModel class is model implementation for Qt item views framework.
Rule key of the parent legend node - for legends with tree hierarchy (QString). Added in 2...
void updateExpandedStateToNode(const QModelIndex &index)
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
void expandedChanged(QgsLayerTreeNode *node, bool expanded)
Emitted when the collapsed/expanded state of a node within the tree has been changed.
QModelIndex legendNode2index(QgsLayerTreeModelLegendNode *legendNode)
Return index for a given legend node.
static QgsLayerTreeEmbeddedWidgetRegistry * layerTreeEmbeddedWidgetRegistry()
Returns the global layer tree embedded widget registry, used for registering widgets that may be embe...
Definition: qgsgui.cpp:50
Provider interface to be implemented in order to introduce new kinds of embedded widgets for use in l...
static QgsLayerTreeModelLegendNode * index2legendNode(const QModelIndex &index)
Return legend node for given index.
QgsLayerTreeNode * parent()
Get pointer to the parent. If parent is a null pointer, the node is a root node.
QgsLayerTreeGroup * currentGroupNode() const
Get current group node. If a layer is current node, the function will return parent group...
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...
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:52
This class is a base class for nodes in a layer tree.
void collapseAllNodes()
Enhancement of QTreeView::collapseAll() that also records expanded state in layer tree nodes...
void keyPressEvent(QKeyEvent *event) override
void currentLayerChanged(QgsMapLayer *layer)
Emitted when a current layer is changed.
QgsLayerTreeModel * layerTreeModel() const
Get access to the model casted to QgsLayerTreeModel.
void refreshLayerLegend(QgsLayerTreeLayer *nodeLayer)
Force a refresh of legend nodes of a layer node.
QgsLayerTreeViewDefaultActions * mDefaultActions
helper class with default actions. Lazily initialized.
void setExpanded(bool expanded)
Set whether the node should be shown as expanded or collapsed in GUI.
QgsMapLayer * layer() const
Qt::ItemFlags flags(const QModelIndex &index) const override
void updateExpandedStateFromNode(QgsLayerTreeNode *node)
QList< QgsLayerTreeNode * > selectedNodes(bool skipInternal=false) const
Return list of selected nodes.
QgsLayerTreeNode * index2node(const QModelIndex &index) const
Return layer tree node for given index.
QgsLayerTree * rootGroup() const
Return pointer to the root node of the layer tree. Always a non-null pointer.
void onExpandedChanged(QgsLayerTreeNode *node, bool expanded)
Layer nodes may optionally include extra embedded widgets (if used in QgsLayerTreeView). Added in 2.16.
QgsLayerTreeModelLegendNode * currentLegendNode() const
Get current legend node.
QgsLayerTreeViewMenuProvider * menuProvider() const
Return pointer to the context menu provider. May be null.
virtual QVariant data(int role) const =0
Return data associated with the item. Must be implemented in derived class.
QgsMapLayer * layerForIndex(const QModelIndex &index) const
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
Check/uncheck action has consequences on children (or parents for leaf node)
virtual void setModel(QAbstractItemModel *model) override
Overridden setModel() from base class. Only QgsLayerTreeModel is an acceptable model.
QList< QgsLayerTreeModelLegendNode * > layerLegendNodes(QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent=false)
Return filtered list of active legend nodes attached to a particular layer node (by default it return...
QgsLayerTreeView(QWidget *parent=0)
void setCurrentLayer(QgsMapLayer *layer)
Set currently selected layer. Null pointer will deselect any layer.
void setMenuProvider(QgsLayerTreeViewMenuProvider *menuProvider)
Set provider for context menu. Takes ownership of the instance.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
void expandAllNodes()
Enhancement of QTreeView::expandAll() that also records expanded state in layer tree nodes...
void setCustomProperty(const QString &key, const QVariant &value)
Set 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.
QgsLayerTreeNode * currentNode() const
Get current node. May be null.