QGIS API Documentation  3.2.0-Bonn (bc43194)
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 
31 
32 
34  : QTreeView( parent )
35 
36 {
37  setHeaderHidden( true );
38 
39  setDragEnabled( true );
40  setAcceptDrops( true );
41  setDropIndicatorShown( true );
42  setEditTriggers( EditKeyPressed );
43  setExpandsOnDoubleClick( false ); // normally used for other actions
44 
45  setSelectionMode( ExtendedSelection );
46  setDefaultDropAction( Qt::MoveAction );
47 
48  // we need a custom item delegate in order to draw indicators
49  setItemDelegate( new QgsLayerTreeViewItemDelegate( this ) );
50  setStyle( new QgsLayerTreeViewProxyStyle( this ) );
51 
52  connect( this, &QTreeView::collapsed, this, &QgsLayerTreeView::updateExpandedStateToNode );
53  connect( this, &QTreeView::expanded, this, &QgsLayerTreeView::updateExpandedStateToNode );
54 }
55 
57 {
58  delete mMenuProvider;
59 }
60 
61 void QgsLayerTreeView::setModel( QAbstractItemModel *model )
62 {
63  if ( !qobject_cast<QgsLayerTreeModel *>( model ) )
64  return;
65 
66  connect( model, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted );
67  connect( model, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved );
68 
69  QTreeView::setModel( model );
70 
72  connect( layerTreeModel()->rootGroup(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayerTreeView::onCustomPropertyChanged );
73 
74  connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayerTreeView::onCurrentChanged );
75 
76  connect( layerTreeModel(), &QAbstractItemModel::modelReset, this, &QgsLayerTreeView::onModelReset );
77 
79 }
80 
82 {
83  return qobject_cast<QgsLayerTreeModel *>( model() );
84 }
85 
87 {
88  if ( !mDefaultActions )
90  return mDefaultActions;
91 }
92 
94 {
95  delete mMenuProvider;
97 }
98 
100 {
101  return layerForIndex( currentIndex() );
102 }
103 
105 {
106  if ( !layer )
107  {
108  setCurrentIndex( QModelIndex() );
109  return;
110  }
111 
112  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layer->id() );
113  if ( !nodeLayer )
114  return;
115 
116  setCurrentIndex( layerTreeModel()->node2index( nodeLayer ) );
117 }
118 
119 
120 void QgsLayerTreeView::contextMenuEvent( QContextMenuEvent *event )
121 {
122  if ( !mMenuProvider )
123  return;
124 
125  QModelIndex idx = indexAt( event->pos() );
126  if ( !idx.isValid() )
127  setCurrentIndex( QModelIndex() );
128 
129  QMenu *menu = mMenuProvider->createContextMenu();
130  if ( menu && menu->actions().count() != 0 )
131  menu->exec( mapToGlobal( event->pos() ) );
132  delete menu;
133 }
134 
135 
136 void QgsLayerTreeView::modelRowsInserted( const QModelIndex &index, int start, int end )
137 {
138  QgsLayerTreeNode *parentNode = layerTreeModel()->index2node( index );
139  if ( !parentNode )
140  return;
141 
142  // Embedded widgets - replace placeholders in the model by actual widgets
143  if ( layerTreeModel()->testFlag( QgsLayerTreeModel::UseEmbeddedWidgets ) && QgsLayerTree::isLayer( parentNode ) )
144  {
145  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( parentNode );
146  if ( QgsMapLayer *layer = nodeLayer->layer() )
147  {
148  int widgetsCount = layer->customProperty( QStringLiteral( "embeddedWidgets/count" ), 0 ).toInt();
149  QList<QgsLayerTreeModelLegendNode *> legendNodes = layerTreeModel()->layerLegendNodes( nodeLayer, true );
150  for ( int i = 0; i < widgetsCount; ++i )
151  {
152  QString providerId = layer->customProperty( QStringLiteral( "embeddedWidgets/%1/id" ).arg( i ) ).toString();
153  if ( QgsLayerTreeEmbeddedWidgetProvider *provider = QgsGui::layerTreeEmbeddedWidgetRegistry()->provider( providerId ) )
154  {
155  QModelIndex index = layerTreeModel()->legendNode2index( legendNodes[i] );
156  setIndexWidget( index, provider->createWidget( layer, i ) );
157  }
158  }
159  }
160  }
161 
162 
163  if ( QgsLayerTree::isLayer( parentNode ) )
164  {
165  // if ShowLegendAsTree flag is enabled in model, we may need to expand some legend nodes
166  QStringList expandedNodeKeys = parentNode->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
167  if ( expandedNodeKeys.isEmpty() )
168  return;
169 
170  Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, layerTreeModel()->layerLegendNodes( QgsLayerTree::toLayer( parentNode ), true ) )
171  {
172  QString ruleKey = legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
173  if ( expandedNodeKeys.contains( ruleKey ) )
174  setExpanded( layerTreeModel()->legendNode2index( legendNode ), true );
175  }
176  return;
177  }
178 
179  QList<QgsLayerTreeNode *> children = parentNode->children();
180  for ( int i = start; i <= end; ++i )
181  {
182  updateExpandedStateFromNode( children[i] );
183  }
184 
185  // make sure we still have correct current layer
187 }
188 
190 {
191  // make sure we still have correct current layer
193 }
194 
195 void QgsLayerTreeView::updateExpandedStateToNode( const QModelIndex &index )
196 {
197  if ( QgsLayerTreeNode *node = layerTreeModel()->index2node( index ) )
198  {
199  node->setExpanded( isExpanded( index ) );
200  }
201  else if ( QgsLayerTreeModelLegendNode *node = layerTreeModel()->index2legendNode( index ) )
202  {
203  QString ruleKey = node->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
204  QStringList lst = node->layerNode()->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
205  bool expanded = isExpanded( index );
206  bool isInList = lst.contains( ruleKey );
207  if ( expanded && !isInList )
208  {
209  lst.append( ruleKey );
210  node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
211  }
212  else if ( !expanded && isInList )
213  {
214  lst.removeAll( ruleKey );
215  node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
216  }
217  }
218 }
219 
221 {
222  QgsMapLayer *layerCurrent = layerForIndex( currentIndex() );
223  QString layerCurrentID = layerCurrent ? layerCurrent->id() : QString();
224  if ( mCurrentLayerID == layerCurrentID )
225  return;
226 
227  // update the current index in model (the item will be underlined)
228  QModelIndex nodeLayerIndex;
229  if ( layerCurrent )
230  {
231  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerCurrentID );
232  if ( nodeLayer )
233  nodeLayerIndex = layerTreeModel()->node2index( nodeLayer );
234  }
235  layerTreeModel()->setCurrentIndex( nodeLayerIndex );
236 
237  mCurrentLayerID = layerCurrentID;
238  emit currentLayerChanged( layerCurrent );
239 }
240 
242 {
243  QModelIndex idx = layerTreeModel()->node2index( node );
244  if ( isExpanded( idx ) != expanded )
245  setExpanded( idx, expanded );
246 }
247 
248 void QgsLayerTreeView::onCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key )
249 {
250  if ( key != QStringLiteral( "expandedLegendNodes" ) || !QgsLayerTree::isLayer( node ) )
251  return;
252 
253  QSet<QString> expandedLegendNodes = node->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList().toSet();
254 
255  const QList<QgsLayerTreeModelLegendNode *> legendNodes = layerTreeModel()->layerLegendNodes( QgsLayerTree::toLayer( node ), true );
256  for ( QgsLayerTreeModelLegendNode *legendNode : legendNodes )
257  {
258  QString key = legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
259  if ( !key.isEmpty() )
260  setExpanded( layerTreeModel()->legendNode2index( legendNode ), expandedLegendNodes.contains( key ) );
261  }
262 }
263 
265 {
267 }
268 
270 {
271  QModelIndex idx = layerTreeModel()->node2index( node );
272  setExpanded( idx, node->isExpanded() );
273 
274  Q_FOREACH ( QgsLayerTreeNode *child, node->children() )
276 }
277 
278 QgsMapLayer *QgsLayerTreeView::layerForIndex( const QModelIndex &index ) const
279 {
280  // Check if model has been set and index is valid
281  if ( layerTreeModel() && index.isValid() )
282  {
283  QgsLayerTreeNode *node = layerTreeModel()->index2node( index );
284  if ( node )
285  {
286  if ( QgsLayerTree::isLayer( node ) )
287  return QgsLayerTree::toLayer( node )->layer();
288  }
289  else
290  {
291  // possibly a legend node
293  if ( legendNode )
294  return legendNode->layerNode()->layer();
295  }
296  }
297  return nullptr;
298 }
299 
301 {
302  return layerTreeModel()->index2node( selectionModel()->currentIndex() );
303 }
304 
306 {
307  QgsLayerTreeNode *node = currentNode();
308  if ( QgsLayerTree::isGroup( node ) )
309  return QgsLayerTree::toGroup( node );
310  else if ( QgsLayerTree::isLayer( node ) )
311  {
312  QgsLayerTreeNode *parent = node->parent();
313  if ( QgsLayerTree::isGroup( parent ) )
314  return QgsLayerTree::toGroup( parent );
315  }
316 
317  if ( QgsLayerTreeModelLegendNode *legendNode = layerTreeModel()->index2legendNode( selectionModel()->currentIndex() ) )
318  {
319  QgsLayerTreeLayer *parent = legendNode->layerNode();
320  if ( QgsLayerTree::isGroup( parent->parent() ) )
321  return QgsLayerTree::toGroup( parent->parent() );
322  }
323 
324  return nullptr;
325 }
326 
328 {
329  return layerTreeModel()->index2legendNode( selectionModel()->currentIndex() );
330 }
331 
332 QList<QgsLayerTreeNode *> QgsLayerTreeView::selectedNodes( bool skipInternal ) const
333 {
334  return layerTreeModel()->indexes2nodes( selectionModel()->selectedIndexes(), skipInternal );
335 }
336 
337 QList<QgsLayerTreeLayer *> QgsLayerTreeView::selectedLayerNodes() const
338 {
339  QList<QgsLayerTreeLayer *> layerNodes;
340  Q_FOREACH ( QgsLayerTreeNode *node, selectedNodes() )
341  {
342  if ( QgsLayerTree::isLayer( node ) )
343  layerNodes << QgsLayerTree::toLayer( node );
344  }
345  return layerNodes;
346 }
347 
348 QList<QgsMapLayer *> QgsLayerTreeView::selectedLayers() const
349 {
350  QList<QgsMapLayer *> list;
351  Q_FOREACH ( QgsLayerTreeLayer *node, selectedLayerNodes() )
352  {
353  if ( node->layer() )
354  list << node->layer();
355  }
356  return list;
357 }
358 
360 {
361  if ( !mIndicators[node].contains( indicator ) )
362  mIndicators[node].append( indicator );
363 }
364 
366 {
367  mIndicators[node].removeOne( indicator );
368 }
369 
370 QList<QgsLayerTreeViewIndicator *> QgsLayerTreeView::indicators( QgsLayerTreeNode *node ) const
371 {
372  return mIndicators.value( node );
373 }
374 
376 QStringList QgsLayerTreeView::viewOnlyCustomProperties()
377 {
378  return QStringList() << QStringLiteral( "expandedLegendNodes" );
379 }
381 
382 void QgsLayerTreeView::refreshLayerSymbology( const QString &layerId )
383 {
384  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerId );
385  if ( nodeLayer )
386  layerTreeModel()->refreshLayerLegend( nodeLayer );
387 }
388 
389 
390 static void _expandAllLegendNodes( QgsLayerTreeLayer *nodeLayer, bool expanded, QgsLayerTreeModel *model )
391 {
392  // for layers we also need to find out with legend nodes contain some children and make them expanded/collapsed
393  // if we are collapsing, we just write out an empty list
394  QStringList lst;
395  if ( expanded )
396  {
397  Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, model->layerLegendNodes( nodeLayer, true ) )
398  {
399  QString parentKey = legendNode->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString();
400  if ( !parentKey.isEmpty() && !lst.contains( parentKey ) )
401  lst << parentKey;
402  }
403  }
404  nodeLayer->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
405 }
406 
407 
408 static void _expandAllNodes( QgsLayerTreeGroup *parent, bool expanded, QgsLayerTreeModel *model )
409 {
410  Q_FOREACH ( QgsLayerTreeNode *node, parent->children() )
411  {
412  node->setExpanded( expanded );
413  if ( QgsLayerTree::isGroup( node ) )
414  _expandAllNodes( QgsLayerTree::toGroup( node ), expanded, model );
415  else if ( QgsLayerTree::isLayer( node ) )
416  _expandAllLegendNodes( QgsLayerTree::toLayer( node ), expanded, model );
417  }
418 }
419 
420 
422 {
423  // unfortunately expandAll() does not emit expanded() signals
424  _expandAllNodes( layerTreeModel()->rootGroup(), true, layerTreeModel() );
425  expandAll();
426 }
427 
429 {
430  // unfortunately collapseAll() does not emit collapsed() signals
431  _expandAllNodes( layerTreeModel()->rootGroup(), false, layerTreeModel() );
432  collapseAll();
433 }
434 
435 void QgsLayerTreeView::mouseReleaseEvent( QMouseEvent *event )
436 {
437  // we need to keep last mouse position in order to know whether to emit an indicator's clicked() signal
438  // (the item delegate needs to know which indicator has been clicked)
439  mLastReleaseMousePos = event->pos();
440 
441  const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
442  if ( event->modifiers() & Qt::ControlModifier )
444  else
446  QTreeView::mouseReleaseEvent( event );
447  layerTreeModel()->setFlags( oldFlags );
448 }
449 
450 void QgsLayerTreeView::keyPressEvent( QKeyEvent *event )
451 {
452  const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
453  if ( event->modifiers() & Qt::ControlModifier )
455  else
457  QTreeView::keyPressEvent( event );
458  layerTreeModel()->setFlags( oldFlags );
459 }
460 
461 void QgsLayerTreeView::dropEvent( QDropEvent *event )
462 {
463  if ( event->keyboardModifiers() & Qt::AltModifier )
464  {
465  event->accept();
466  }
467  QTreeView::dropEvent( event );
468 }
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:75
QList< QgsMapLayer * > selectedLayers() const
Gets list of selected layers.
Base class for all map layer types.
Definition: qgsmaplayer.h:61
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:43
void setCurrentIndex(const QModelIndex &currentIndex)
Sets 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 removeIndicator(QgsLayerTreeNode *node, QgsLayerTreeViewIndicator *indicator)
Removes a previously added indicator to a layer tree node.
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:64
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
void modelRowsInserted(const QModelIndex &index, int start, int end)
QgsMapLayer * currentLayer() const
Gets currently selected layer. May be null.
QgsLayerTreeViewDefaultActions * defaultActions()
Gets access to the default actions that may be used with the tree view.
bool isExpanded() const
Returns whether the node should be shown as expanded or collapsed in GUI.
QModelIndex node2index(QgsLayerTreeNode *node) const
Returns index for a given node. If the node does not belong to the layer tree, the result is undefine...
virtual QMenu * createContextMenu()=0
Returns a newly created menu instance (or null pointer on error)
void setFlags(QgsLayerTreeModel::Flags f)
Sets OR-ed combination of model flags.
QList< QgsLayerTreeLayer * > selectedLayerNodes() const
Returns 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()
Gets 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)
Returns 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:62
Provider interface to be implemented in order to introduce new kinds of embedded widgets for use in l...
void addIndicator(QgsLayerTreeNode *node, QgsLayerTreeViewIndicator *indicator)
Adds an indicator to the given layer tree node.
static QgsLayerTreeModelLegendNode * index2legendNode(const QModelIndex &index)
Returns legend node for given index.
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is a null pointer, the node is a root node. ...
QgsLayerTreeGroup * currentGroupNode() const
Gets current group node. If a layer is current node, the function will return parent group...
QPoint mLastReleaseMousePos
Used by the item delegate for identification of which indicator has been clicked. ...
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...
QHash< QgsLayerTreeNode *, QList< QgsLayerTreeViewIndicator * > > mIndicators
Storage of indicators used with the tree view.
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.
friend class QgsLayerTreeViewItemDelegate
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
Gets 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)
Sets 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
Returns list of selected nodes.
QgsLayerTreeNode * index2node(const QModelIndex &index) const
Returns layer tree node for given index.
QgsLayerTree * rootGroup() const
Returns 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
Gets current legend node.
QgsLayerTreeViewMenuProvider * menuProvider() const
Returns pointer to the context menu provider. May be null.
virtual QVariant data(int role) const =0
Returns data associated with the item. Must be implemented in derived class.
void dropEvent(QDropEvent *event) override
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)
void setModel(QAbstractItemModel *model) override
Overridden setModel() from base class. Only QgsLayerTreeModel is an acceptable model.
QList< QgsLayerTreeModelLegendNode * > layerLegendNodes(QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent=false)
Returns filtered list of active legend nodes attached to a particular layer node (by default it retur...
void setCurrentLayer(QgsMapLayer *layer)
Sets currently selected layer. Null pointer will deselect any layer.
QgsLayerTreeView(QWidget *parent=nullptr)
Constructor for QgsLayerTreeView.
void setMenuProvider(QgsLayerTreeViewMenuProvider *menuProvider)
Sets provider for context menu. Takes ownership of the instance.
~QgsLayerTreeView() override
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
void customPropertyChanged(QgsLayerTreeNode *node, const QString &key)
Emitted when a custom property of a node within the tree has been changed or removed.
void expandAllNodes()
Enhancement of QTreeView::expandAll() that also records expanded state in layer tree nodes...
Indicator that can be used in a layer tree view to display icons next to items of the layer tree...
QList< QgsLayerTreeViewIndicator * > indicators(QgsLayerTreeNode *node) const
Returns list of indicators associated with a particular layer tree node.
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.
QgsLayerTreeNode * currentNode() const
Gets current node. May be null.