QGIS API Documentation  3.0.2-Girona (307d082)
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 
33 {
34  setHeaderHidden( true );
35 
36  setDragEnabled( true );
37  setAcceptDrops( true );
38  setDropIndicatorShown( true );
39  setEditTriggers( EditKeyPressed );
40  setExpandsOnDoubleClick( false ); // normally used for other actions
41 
42  setSelectionMode( ExtendedSelection );
43  setDefaultDropAction( Qt::MoveAction );
44 
45  connect( this, &QTreeView::collapsed, this, &QgsLayerTreeView::updateExpandedStateToNode );
46  connect( this, &QTreeView::expanded, this, &QgsLayerTreeView::updateExpandedStateToNode );
47 }
48 
50 {
51  delete mMenuProvider;
52 }
53 
54 void QgsLayerTreeView::setModel( QAbstractItemModel *model )
55 {
56  if ( !qobject_cast<QgsLayerTreeModel *>( model ) )
57  return;
58 
59  connect( model, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted );
60  connect( model, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved );
61 
62  QTreeView::setModel( model );
63 
65 
66  connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayerTreeView::onCurrentChanged );
67 
68  connect( layerTreeModel(), &QAbstractItemModel::modelReset, this, &QgsLayerTreeView::onModelReset );
69 
71 }
72 
74 {
75  return qobject_cast<QgsLayerTreeModel *>( model() );
76 }
77 
79 {
80  if ( !mDefaultActions )
82  return mDefaultActions;
83 }
84 
86 {
87  delete mMenuProvider;
89 }
90 
92 {
93  return layerForIndex( currentIndex() );
94 }
95 
97 {
98  if ( !layer )
99  {
100  setCurrentIndex( QModelIndex() );
101  return;
102  }
103 
104  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layer->id() );
105  if ( !nodeLayer )
106  return;
107 
108  setCurrentIndex( layerTreeModel()->node2index( nodeLayer ) );
109 }
110 
111 
112 void QgsLayerTreeView::contextMenuEvent( QContextMenuEvent *event )
113 {
114  if ( !mMenuProvider )
115  return;
116 
117  QModelIndex idx = indexAt( event->pos() );
118  if ( !idx.isValid() )
119  setCurrentIndex( QModelIndex() );
120 
121  QMenu *menu = mMenuProvider->createContextMenu();
122  if ( menu && menu->actions().count() != 0 )
123  menu->exec( mapToGlobal( event->pos() ) );
124  delete menu;
125 }
126 
127 
128 void QgsLayerTreeView::modelRowsInserted( const QModelIndex &index, int start, int end )
129 {
130  QgsLayerTreeNode *parentNode = layerTreeModel()->index2node( index );
131  if ( !parentNode )
132  return;
133 
134  // Embedded widgets - replace placeholders in the model by actual widgets
135  if ( layerTreeModel()->testFlag( QgsLayerTreeModel::UseEmbeddedWidgets ) && QgsLayerTree::isLayer( parentNode ) )
136  {
137  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( parentNode );
138  if ( QgsMapLayer *layer = nodeLayer->layer() )
139  {
140  int widgetsCount = layer->customProperty( QStringLiteral( "embeddedWidgets/count" ), 0 ).toInt();
141  QList<QgsLayerTreeModelLegendNode *> legendNodes = layerTreeModel()->layerLegendNodes( nodeLayer, true );
142  for ( int i = 0; i < widgetsCount; ++i )
143  {
144  QString providerId = layer->customProperty( QStringLiteral( "embeddedWidgets/%1/id" ).arg( i ) ).toString();
145  if ( QgsLayerTreeEmbeddedWidgetProvider *provider = QgsGui::layerTreeEmbeddedWidgetRegistry()->provider( providerId ) )
146  {
147  QModelIndex index = layerTreeModel()->legendNode2index( legendNodes[i] );
148  setIndexWidget( index, provider->createWidget( layer, i ) );
149  }
150  }
151  }
152  }
153 
154 
155  if ( QgsLayerTree::isLayer( parentNode ) )
156  {
157  // if ShowLegendAsTree flag is enabled in model, we may need to expand some legend nodes
158  QStringList expandedNodeKeys = parentNode->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
159  if ( expandedNodeKeys.isEmpty() )
160  return;
161 
162  Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, layerTreeModel()->layerLegendNodes( QgsLayerTree::toLayer( parentNode ), true ) )
163  {
164  QString ruleKey = legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
165  if ( expandedNodeKeys.contains( ruleKey ) )
166  setExpanded( layerTreeModel()->legendNode2index( legendNode ), true );
167  }
168  return;
169  }
170 
171  QList<QgsLayerTreeNode *> children = parentNode->children();
172  for ( int i = start; i <= end; ++i )
173  {
174  updateExpandedStateFromNode( children[i] );
175  }
176 
177  // make sure we still have correct current layer
179 }
180 
182 {
183  // make sure we still have correct current layer
185 }
186 
187 void QgsLayerTreeView::updateExpandedStateToNode( const QModelIndex &index )
188 {
189  if ( QgsLayerTreeNode *node = layerTreeModel()->index2node( index ) )
190  {
191  node->setExpanded( isExpanded( index ) );
192  }
193  else if ( QgsLayerTreeModelLegendNode *node = layerTreeModel()->index2legendNode( index ) )
194  {
195  QString ruleKey = node->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
196  QStringList lst = node->layerNode()->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
197  bool expanded = isExpanded( index );
198  bool isInList = lst.contains( ruleKey );
199  if ( expanded && !isInList )
200  {
201  lst.append( ruleKey );
202  node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
203  }
204  else if ( !expanded && isInList )
205  {
206  lst.removeAll( ruleKey );
207  node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
208  }
209  }
210 }
211 
213 {
214  QgsMapLayer *layerCurrent = layerForIndex( currentIndex() );
215  QString layerCurrentID = layerCurrent ? layerCurrent->id() : QString();
216  if ( mCurrentLayerID == layerCurrentID )
217  return;
218 
219  // update the current index in model (the item will be underlined)
220  QModelIndex nodeLayerIndex;
221  if ( layerCurrent )
222  {
223  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerCurrentID );
224  if ( nodeLayer )
225  nodeLayerIndex = layerTreeModel()->node2index( nodeLayer );
226  }
227  layerTreeModel()->setCurrentIndex( nodeLayerIndex );
228 
229  mCurrentLayerID = layerCurrentID;
230  emit currentLayerChanged( layerCurrent );
231 }
232 
234 {
235  QModelIndex idx = layerTreeModel()->node2index( node );
236  if ( isExpanded( idx ) != expanded )
237  setExpanded( idx, expanded );
238 }
239 
241 {
243 }
244 
246 {
247  QModelIndex idx = layerTreeModel()->node2index( node );
248  setExpanded( idx, node->isExpanded() );
249 
250  Q_FOREACH ( QgsLayerTreeNode *child, node->children() )
252 }
253 
254 QgsMapLayer *QgsLayerTreeView::layerForIndex( const QModelIndex &index ) const
255 {
256  // Check if model has been set and index is valid
257  if ( layerTreeModel() && index.isValid() )
258  {
259  QgsLayerTreeNode *node = layerTreeModel()->index2node( index );
260  if ( node )
261  {
262  if ( QgsLayerTree::isLayer( node ) )
263  return QgsLayerTree::toLayer( node )->layer();
264  }
265  else
266  {
267  // possibly a legend node
269  if ( legendNode )
270  return legendNode->layerNode()->layer();
271  }
272  }
273  return nullptr;
274 }
275 
277 {
278  return layerTreeModel()->index2node( selectionModel()->currentIndex() );
279 }
280 
282 {
283  QgsLayerTreeNode *node = currentNode();
284  if ( QgsLayerTree::isGroup( node ) )
285  return QgsLayerTree::toGroup( node );
286  else if ( QgsLayerTree::isLayer( node ) )
287  {
288  QgsLayerTreeNode *parent = node->parent();
289  if ( QgsLayerTree::isGroup( parent ) )
290  return QgsLayerTree::toGroup( parent );
291  }
292 
293  if ( QgsLayerTreeModelLegendNode *legendNode = layerTreeModel()->index2legendNode( selectionModel()->currentIndex() ) )
294  {
295  QgsLayerTreeLayer *parent = legendNode->layerNode();
296  if ( QgsLayerTree::isGroup( parent->parent() ) )
297  return QgsLayerTree::toGroup( parent->parent() );
298  }
299 
300  return nullptr;
301 }
302 
304 {
305  return layerTreeModel()->index2legendNode( selectionModel()->currentIndex() );
306 }
307 
308 QList<QgsLayerTreeNode *> QgsLayerTreeView::selectedNodes( bool skipInternal ) const
309 {
310  return layerTreeModel()->indexes2nodes( selectionModel()->selectedIndexes(), skipInternal );
311 }
312 
313 QList<QgsLayerTreeLayer *> QgsLayerTreeView::selectedLayerNodes() const
314 {
315  QList<QgsLayerTreeLayer *> layerNodes;
316  Q_FOREACH ( QgsLayerTreeNode *node, selectedNodes() )
317  {
318  if ( QgsLayerTree::isLayer( node ) )
319  layerNodes << QgsLayerTree::toLayer( node );
320  }
321  return layerNodes;
322 }
323 
324 QList<QgsMapLayer *> QgsLayerTreeView::selectedLayers() const
325 {
326  QList<QgsMapLayer *> list;
327  Q_FOREACH ( QgsLayerTreeLayer *node, selectedLayerNodes() )
328  {
329  if ( node->layer() )
330  list << node->layer();
331  }
332  return list;
333 }
334 
335 
336 void QgsLayerTreeView::refreshLayerSymbology( const QString &layerId )
337 {
338  QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerId );
339  if ( nodeLayer )
340  layerTreeModel()->refreshLayerLegend( nodeLayer );
341 }
342 
343 
344 static void _expandAllLegendNodes( QgsLayerTreeLayer *nodeLayer, bool expanded, QgsLayerTreeModel *model )
345 {
346  // for layers we also need to find out with legend nodes contain some children and make them expanded/collapsed
347  // if we are collapsing, we just write out an empty list
348  QStringList lst;
349  if ( expanded )
350  {
351  Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, model->layerLegendNodes( nodeLayer, true ) )
352  {
353  QString parentKey = legendNode->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString();
354  if ( !parentKey.isEmpty() && !lst.contains( parentKey ) )
355  lst << parentKey;
356  }
357  }
358  nodeLayer->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
359 }
360 
361 
362 static void _expandAllNodes( QgsLayerTreeGroup *parent, bool expanded, QgsLayerTreeModel *model )
363 {
364  Q_FOREACH ( QgsLayerTreeNode *node, parent->children() )
365  {
366  node->setExpanded( expanded );
367  if ( QgsLayerTree::isGroup( node ) )
368  _expandAllNodes( QgsLayerTree::toGroup( node ), expanded, model );
369  else if ( QgsLayerTree::isLayer( node ) )
370  _expandAllLegendNodes( QgsLayerTree::toLayer( node ), expanded, model );
371  }
372 }
373 
374 
376 {
377  // unfortunately expandAll() does not emit expanded() signals
378  _expandAllNodes( layerTreeModel()->rootGroup(), true, layerTreeModel() );
379  expandAll();
380 }
381 
383 {
384  // unfortunately collapseAll() does not emit collapsed() signals
385  _expandAllNodes( layerTreeModel()->rootGroup(), false, layerTreeModel() );
386  collapseAll();
387 }
388 
389 void QgsLayerTreeView::mouseReleaseEvent( QMouseEvent *event )
390 {
391  const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
392  if ( event->modifiers() & Qt::ControlModifier )
394  else
396  QTreeView::mouseReleaseEvent( event );
397  layerTreeModel()->setFlags( oldFlags );
398 }
399 
400 void QgsLayerTreeView::keyPressEvent( QKeyEvent *event )
401 {
402  const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
403  if ( event->modifiers() & Qt::ControlModifier )
405  else
407  QTreeView::keyPressEvent( event );
408  layerTreeModel()->setFlags( oldFlags );
409 }
410 
411 void QgsLayerTreeView::dropEvent( QDropEvent *event )
412 {
413  if ( event->keyboardModifiers() & Qt::AltModifier )
414  {
415  event->accept();
416  }
417  QTreeView::dropEvent( event );
418 }
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
Get list of selected layers.
Base class for all map layer types.
Definition: qgsmaplayer.h:56
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)
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:64
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:61
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:53
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.
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)
Return filtered list of active legend nodes attached to a particular layer node (by default it return...
void setCurrentLayer(QgsMapLayer *layer)
Set currently selected layer. Null pointer will deselect any layer.
QgsLayerTreeView(QWidget *parent=nullptr)
Constructor for QgsLayerTreeView.
void setMenuProvider(QgsLayerTreeViewMenuProvider *menuProvider)
Set provider for context menu. Takes ownership of the instance.
~QgsLayerTreeView() override
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.