QGIS API Documentation 4.3.0-Master (c8cdfe19d7b)
Loading...
Searching...
No Matches
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 "qgsgui.h"
19#include "qgslayertree.h"
21#include "qgslayertreegroup.h"
22#include "qgslayertreemodel.h"
24#include "qgslayertreeutils.h"
26#include "qgsmaplayer.h"
27#include "qgsmessagebar.h"
28#include "qgsscreenhelper.h"
29
30#include <QApplication>
31#include <QContextMenuEvent>
32#include <QHeaderView>
33#include <QMenu>
34#include <QMimeData>
35#include <QScrollBar>
36#include <QString>
37
38#include "moc_qgslayertreeview.cpp"
39
40using namespace Qt::StringLiterals;
41
42#ifdef ENABLE_MODELTEST
43#include <QAbstractItemModelTester>
44#endif
45
48
49
51 : QTreeView( parent )
52 , mBlockDoubleClickTimer( new QTimer( this ) )
53{
54 mBlockDoubleClickTimer->setSingleShot( true );
55 mBlockDoubleClickTimer->setInterval( QApplication::doubleClickInterval() );
56
57 setHeaderHidden( true );
58
59 setDragEnabled( true );
60 setAcceptDrops( true );
61 setDropIndicatorShown( true );
62 setEditTriggers( EditKeyPressed );
63 setExpandsOnDoubleClick( false ); // normally used for other actions
64
65 // Ensure legend graphics are scrollable
66 header()->setStretchLastSection( false );
67 header()->setSectionResizeMode( QHeaderView::ResizeToContents );
68
69 // If vertically scrolling by item, legend graphics can get clipped
70 setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
71
72 setSelectionMode( ExtendedSelection );
73 setDefaultDropAction( Qt::MoveAction );
74
75 connect( this, &QTreeView::collapsed, this, &QgsLayerTreeViewBase::updateExpandedStateToNode );
76 connect( this, &QTreeView::expanded, this, &QgsLayerTreeViewBase::updateExpandedStateToNode );
77}
78
81
83{
84 if ( mBlockDoubleClickTimer->isActive() )
85 event->accept();
86 else
87 QTreeView::mouseDoubleClickEvent( event );
88}
89
91{
92 if ( mLayerTreeModel )
93 {
94 disconnect( mLayerTreeModel->rootGroup(), &QgsLayerTreeNode::expandedChanged, this, &QgsLayerTreeViewBase::onExpandedChanged );
95 disconnect( mLayerTreeModel, &QAbstractItemModel::modelReset, this, &QgsLayerTreeViewBase::onModelReset );
96 disconnect( mLayerTreeModel, &QAbstractItemModel::dataChanged, this, &QgsLayerTreeViewBase::onDataChanged );
97 }
98
99 mLayerTreeModel = model;
100
101 mLayerTreeModel->setTargetScreenProperties( { QgsScreenProperties( screen() ) } );
102 auto screenHelper = new QgsScreenHelper( this );
103 connect( screenHelper, &QgsScreenHelper::screenDpiChanged, this, [this] { mLayerTreeModel->setTargetScreenProperties( { QgsScreenProperties( screen() ) } ); } );
104
105 connect( mLayerTreeModel->rootGroup(), &QgsLayerTreeNode::expandedChanged, this, &QgsLayerTreeViewBase::onExpandedChanged );
106
107 connect( mLayerTreeModel, &QAbstractItemModel::modelReset, this, &QgsLayerTreeViewBase::onModelReset );
108 connect( mLayerTreeModel, &QAbstractItemModel::dataChanged, this, &QgsLayerTreeViewBase::onDataChanged );
109
110 updateExpandedStateFromNode( mLayerTreeModel->rootGroup() );
111}
112
114{
115 return mLayerTreeModel;
116}
117
119{
120 const QModelIndex idx = node2index( node );
121 setExpanded( idx, node->isExpanded() );
122
123 const auto constChildren = node->children();
124 for ( QgsLayerTreeNode *child : constChildren )
126}
127
128QModelIndex QgsLayerTreeViewBase::viewIndexToLayerTreeModelIndex( const QModelIndex &index ) const
129{
130 // if no proxy in use, we already have a layer tree model index
131 if ( model() == mLayerTreeModel )
132 return index;
133
134 if ( QAbstractProxyModel *proxy = qobject_cast< QAbstractProxyModel *>( model() ) )
135 {
136 return proxy->mapToSource( index );
137 }
138
139 return QModelIndex();
140}
141
142QModelIndex QgsLayerTreeViewBase::layerTreeModelIndexToViewIndex( const QModelIndex &index ) const
143{
144 // if no proxy in use, we already have a view index
145 if ( model() == mLayerTreeModel )
146 return index;
147
148 if ( QAbstractProxyModel *proxy = qobject_cast< QAbstractProxyModel *>( model() ) )
149 {
150 return proxy->mapFromSource( index );
151 }
152
153 return QModelIndex();
154}
155
157{
158 if ( QgsLayerTreeNode *node = index2node( index ) )
159 {
160 node->setExpanded( isExpanded( index ) );
161 }
162 else if ( QgsLayerTreeModelLegendNode *node = index2legendNode( index ) )
163 {
164 const QString ruleKey = node->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
165 QStringList lst = node->layerNode()->customProperty( u"expandedLegendNodes"_s ).toStringList();
166 const bool expanded = isExpanded( index );
167 const bool isInList = lst.contains( ruleKey );
168 if ( expanded && !isInList )
169 {
170 lst.append( ruleKey );
171 node->layerNode()->setCustomProperty( u"expandedLegendNodes"_s, lst );
172 }
173 else if ( !expanded && isInList )
174 {
175 lst.removeAll( ruleKey );
176 node->layerNode()->setCustomProperty( u"expandedLegendNodes"_s, lst );
177 }
178 }
179}
180
182{
183 const QModelIndex idx = node2index( node );
184 if ( isExpanded( idx ) != expanded )
185 setExpanded( idx, expanded );
186}
187
189{
190 if ( !mLayerTreeModel )
191 return;
192 updateExpandedStateFromNode( mLayerTreeModel->rootGroup() );
193 //checkModel();
194}
195
196void QgsLayerTreeViewBase::onDataChanged( const QModelIndex &, const QModelIndex &, const QVector<int> & )
197{
198 mBlockDoubleClickTimer->start();
199}
200
201QgsLayerTreeNode *QgsLayerTreeViewBase::index2node( const QModelIndex &index ) const
202{
203 if ( mLayerTreeModel )
204 return mLayerTreeModel->index2node( viewIndexToLayerTreeModelIndex( index ) );
205
206 return nullptr;
207}
208
213
215{
216 if ( mLayerTreeModel )
217 return mLayerTreeModel->node2index( node );
218
219 return QModelIndex();
220}
221
226
228{
229 if ( QItemSelectionModel *selectModel = selectionModel() )
230 return index2node( selectModel->currentIndex() );
231 else
232 return nullptr;
233}
234
235QList<QgsLayerTreeNode *> QgsLayerTreeViewBase::selectedNodes( bool skipInternal ) const
236{
237 if ( !mLayerTreeModel )
238 return {};
239
240 QModelIndexList mapped;
241 if ( QItemSelectionModel *selectModel = selectionModel() )
242 {
243 const QModelIndexList selected = selectModel->selectedIndexes();
244 mapped.reserve( selected.size() );
245 for ( const QModelIndex &index : selected )
246 mapped << viewIndexToLayerTreeModelIndex( index );
247 }
248
249 return mLayerTreeModel->indexes2nodes( mapped, skipInternal );
250}
251
252
254{
255 return layerForIndex( currentIndex() );
256}
257
258QgsMapLayer *QgsLayerTreeViewBase::layerForIndex( const QModelIndex &index ) const
259{
260 // Check if model has been set and index is valid
261 if ( mLayerTreeModel && index.isValid() )
262 {
263 if ( QgsLayerTreeNode *node = index2node( index ) )
264 {
265 if ( QgsLayerTree::isLayer( node ) )
266 return QgsLayerTree::toLayer( node )->layer();
267 }
268 else
269 {
270 // possibly a legend node
271 QgsLayerTreeModelLegendNode *legendNode = index2legendNode( index );
272 if ( legendNode )
273 return legendNode->layerNode()->layer();
274 }
275 }
276 return nullptr;
277}
278
280{
282 if ( QgsLayerTree::isGroup( node ) )
283 return QgsLayerTree::toGroup( node );
284 else if ( QgsLayerTree::isLayer( node ) )
285 {
286 QgsLayerTreeNode *parent = node->parent();
287 if ( QgsLayerTree::isGroup( parent ) )
288 return QgsLayerTree::toGroup( parent );
289 }
290
291 if ( QItemSelectionModel *selectModel = selectionModel() )
292 {
293 if ( QgsLayerTreeModelLegendNode *legendNode = index2legendNode( selectModel->currentIndex() ) )
294 {
295 QgsLayerTreeLayer *parent = legendNode->layerNode();
296 if ( QgsLayerTree::isGroup( parent->parent() ) )
297 return QgsLayerTree::toGroup( parent->parent() );
298 }
299 }
300
301 return nullptr;
302}
303
304QList<QgsLayerTreeLayer *> QgsLayerTreeViewBase::selectedLayerNodes() const
305{
306 QList<QgsLayerTreeLayer *> layerNodes;
307 const QList<QgsLayerTreeNode *> constSelectedNodes = selectedNodes();
308 layerNodes.reserve( constSelectedNodes.size() );
309 for ( QgsLayerTreeNode *node : constSelectedNodes )
310 {
311 if ( QgsLayerTree::isLayer( node ) )
312 layerNodes << QgsLayerTree::toLayer( node );
313 }
314 return layerNodes;
315}
316
317QList<QgsMapLayer *> QgsLayerTreeViewBase::selectedLayers() const
318{
319 QList<QgsMapLayer *> list;
320 const QList<QgsLayerTreeLayer *> constSelectedLayerNodes = selectedLayerNodes();
321 list.reserve( constSelectedLayerNodes.size() );
322 for ( QgsLayerTreeLayer *node : constSelectedLayerNodes )
323 {
324 if ( node->layer() )
325 list << node->layer();
326 }
327 return list;
328}
329
334
336{
337 if ( mLayerTreeModel )
338 return mLayerTreeModel->legendNode2index( legendNode );
339 return QModelIndex();
340}
341
343{
344 if ( !node )
345 {
346 setCurrentIndex( QModelIndex() );
347 return;
348 }
349
350 setCurrentIndex( node2index( node ) );
351}
352
354{
355 if ( !layer || !mLayerTreeModel )
356 {
357 setCurrentIndex( QModelIndex() );
358 return;
359 }
360
361 QgsLayerTreeLayer *nodeLayer = mLayerTreeModel->rootGroup()->findLayer( layer->id() );
362 if ( !nodeLayer )
363 return;
364
365 setCurrentNode( nodeLayer );
366}
367
369{
370 if ( QItemSelectionModel *selectModel = selectionModel() )
371 return index2legendNode( selectModel->currentIndex() );
372 else
373 return nullptr;
374}
375
376QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeViewBase::selectedLegendNodes() const
377{
378 QList<QgsLayerTreeModelLegendNode *> res;
379 QItemSelectionModel *selectModel = selectionModel();
380 if ( !mLayerTreeModel || !selectModel )
381 return res;
382
383 const QModelIndexList selected = selectModel->selectedIndexes();
384 res.reserve( selected.size() );
385 for ( const QModelIndex &index : selected )
386 {
387 const QModelIndex modelIndex = viewIndexToLayerTreeModelIndex( index );
388 if ( QgsLayerTreeModelLegendNode *node = mLayerTreeModel->index2legendNode( modelIndex ) )
389 {
390 res.push_back( node );
391 }
392 }
393
394 return res;
395}
396
398{
399 QModelIndexList mapped;
400 QItemSelectionModel *selectModel = selectionModel();
401 if ( !mLayerTreeModel || !selectModel )
402 return QList<QgsMapLayer *>();
403
404 const QModelIndexList selected = selectModel->selectedIndexes();
405 mapped.reserve( selected.size() );
406 for ( const QModelIndex &index : selected )
407 {
408 mapped << viewIndexToLayerTreeModelIndex( index );
409 }
410
411 const QList<QgsLayerTreeNode *> nodes = mLayerTreeModel->indexes2nodes( mapped, false );
412 const QSet<QgsMapLayer *> layersSet = QgsLayerTreeUtils::collectMapLayersRecursive( nodes );
413 return qgis::setToList( layersSet );
414}
415
416static void expandAllLegendNodes( QgsLayerTreeLayer *nodeLayer, bool expanded, QgsLayerTreeModel *model )
417{
418 // for layers we also need to find out with legend nodes contain some children and make them expanded/collapsed
419 // if we are collapsing, we just write out an empty list
420 QStringList lst;
421 if ( expanded )
422 {
423 const auto constLayerLegendNodes = model->layerLegendNodes( nodeLayer, true );
424 for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
425 {
426 const QString parentKey = legendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::ParentRuleKey ) ).toString();
427 if ( !parentKey.isEmpty() && !lst.contains( parentKey ) )
428 lst << parentKey;
429 }
430 }
431 nodeLayer->setCustomProperty( u"expandedLegendNodes"_s, lst );
432}
433
434static void expandAllNodes( QgsLayerTreeGroup *parent, bool expanded, QgsLayerTreeModel *model )
435{
436 const auto constChildren = parent->children();
437 for ( QgsLayerTreeNode *node : constChildren )
438 {
439 node->setExpanded( expanded );
440 if ( QgsLayerTree::isGroup( node ) )
441 expandAllNodes( QgsLayerTree::toGroup( node ), expanded, model );
442 else if ( QgsLayerTree::isLayer( node ) )
443 expandAllLegendNodes( QgsLayerTree::toLayer( node ), expanded, model );
444 }
445}
446
448{
449 // unfortunately expandAll() does not emit expanded() signals
450 QgsLayerTreeModel *layerModel = layerTreeModel();
451 if ( !layerModel )
452 return;
453 ::expandAllNodes( layerModel->rootGroup(), true, layerModel );
454 expandAll();
455}
456
458{
459 // unfortunately collapseAll() does not emit collapsed() signals
460 QgsLayerTreeModel *layerModel = layerTreeModel();
461 if ( !layerModel )
462 return;
463 ::expandAllNodes( layerModel->rootGroup(), false, layerModel );
464 collapseAll();
465}
466
473
474//
475// QgsLayerTreeView
476//
477
479 : QgsLayerTreeViewBase( parent )
480{
481 // we need a custom item delegate in order to draw indicators
482 setItemDelegate( new QgsLayerTreeViewItemDelegate( this ) );
483 setStyle( new QgsLayerTreeViewProxyStyle( this ) );
484
485 setLayerMarkWidth( static_cast<int>( QFontMetricsF( font() ).horizontalAdvance( 'l' ) * Qgis::UI_SCALE_FACTOR ) );
486
487 connect( horizontalScrollBar(), &QScrollBar::valueChanged, this, &QgsLayerTreeView::onHorizontalScroll );
488}
489
492
493void QgsLayerTreeView::setModel( QAbstractItemModel *model )
494{
495 QgsLayerTreeModel *treeModel = qobject_cast<QgsLayerTreeModel *>( model );
496 if ( !treeModel )
497 return;
498
499 auto proxyModel = new QgsLayerTreeProxyModel( treeModel, this );
500 proxyModel->setShowPrivateLayers( mShowPrivateLayers );
501 proxyModel->setHideValidLayers( mHideValidLayers );
502
503 setModel( treeModel, proxyModel );
504
505 connect( this, &QTreeView::doubleClicked, this, &QgsLayerTreeView::onDoubleClicked );
506}
507
509{
510 if ( mMessageBar )
511 connect( treeModel, &QgsLayerTreeModel::messageEmitted, this, [this]( const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info, int duration = 5 ) {
512 Q_UNUSED( duration )
513 mMessageBar->pushMessage( message, level );
514 } );
515
516 mProxyModel = proxyModel;
517 connect( mProxyModel, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted );
518 connect( mProxyModel, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved );
519
520#ifdef ENABLE_MODELTEST
521 new QAbstractItemModelTester( mProxyModel, QAbstractItemModelTester::FailureReportingMode::Fatal, this );
522#endif
523
524 QTreeView::setModel( mProxyModel );
526
527 connect( treeModel->rootGroup(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayerTreeView::onCustomPropertyChanged );
528
529 connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayerTreeView::onCurrentChanged );
530
531 connect( treeModel, &QAbstractItemModel::dataChanged, this, &QgsLayerTreeView::onDataChanged );
532
533 //checkModel();
534}
535
540
542{
543 QgsLayerTreeModel *layerModel = layerTreeModel();
544 if ( !layer || !layerModel )
545 return;
546 QgsLayerTreeLayer *nodeLayer = layerModel->rootGroup()->findLayer( layer->id() );
547 if ( !nodeLayer )
548 return;
549 nodeLayer->setItemVisibilityChecked( visible );
550}
551
552void QgsLayerTreeView::contextMenuEvent( QContextMenuEvent *event )
553{
554 if ( !mMenuProvider )
555 return;
556
557 const QModelIndex idx = indexAt( event->pos() );
558 if ( !idx.isValid() )
559 setCurrentIndex( QModelIndex() );
560
561 QMenu *menu = mMenuProvider->createContextMenu();
562 if ( menu )
563 {
564 emit contextMenuAboutToShow( menu );
565
566 if ( menu->actions().count() != 0 )
567 menu->exec( mapToGlobal( event->pos() ) );
568 delete menu;
569 }
570}
571
572
573void QgsLayerTreeView::modelRowsInserted( const QModelIndex &index, int start, int end )
574{
575 QgsLayerTreeNode *parentNode = index2node( index );
576 QgsLayerTreeModel *layerModel = layerTreeModel();
577 if ( !parentNode || !layerModel )
578 return;
579
580 // Embedded widgets - replace placeholders in the model by actual widgets
581 if ( layerModel->testFlag( QgsLayerTreeModel::UseEmbeddedWidgets ) && QgsLayerTree::isLayer( parentNode ) )
582 {
583 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( parentNode );
584 if ( QgsMapLayer *layer = nodeLayer->layer() )
585 {
586 const int widgetsCount = layer->customProperty( u"embeddedWidgets/count"_s, 0 ).toInt();
587 QList<QgsLayerTreeModelLegendNode *> legendNodes = layerModel->layerLegendNodes( nodeLayer, true );
588 for ( int i = 0; i < widgetsCount; ++i )
589 {
590 const QString providerId = layer->customProperty( u"embeddedWidgets/%1/id"_s.arg( i ) ).toString();
591 if ( QgsLayerTreeEmbeddedWidgetProvider *provider = QgsGui::layerTreeEmbeddedWidgetRegistry()->provider( providerId ) )
592 {
593 const QModelIndex index = legendNode2index( legendNodes[i] );
594 QWidget *wdgt = provider->createWidget( layer, i );
595 // Since column is resized to contents, limit the expanded width of embedded
596 // widgets, if they are not already limited, e.g. have the default MAX value.
597 // Else, embedded widget may grow very wide due to large legend graphics.
598 // NOTE: This approach DOES NOT work right. It causes horizontal scroll
599 // bar to disappear if the embedded widget is expanded and part
600 // of the last layer in the panel, even if much wider legend items
601 // are expanded above it. The correct width-limiting method should
602 // be setting fixed-width, hidpi-aware embedded widget items in a
603 // layout and appending an expanding QSpacerItem to end. This ensures
604 // full width is always created in the column by the embedded widget.
605 // See QgsLayerTreeOpacityWidget
606 //if ( wdgt->maximumWidth() == QWIDGETSIZE_MAX )
607 //{
608 // wdgt->setMaximumWidth( 250 );
609 //}
610
611 setIndexWidget( index, wdgt );
612 }
613 }
614 }
615 }
616
617
618 if ( QgsLayerTree::isLayer( parentNode ) )
619 {
620 // if ShowLegendAsTree flag is enabled in model, we may need to expand some legend nodes
621 const QStringList expandedNodeKeys = parentNode->customProperty( u"expandedLegendNodes"_s ).toStringList();
622 if ( expandedNodeKeys.isEmpty() )
623 return;
624
625 const auto constLayerLegendNodes = layerModel->layerLegendNodes( QgsLayerTree::toLayer( parentNode ), true );
626 for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
627 {
628 const QString ruleKey = legendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
629 if ( expandedNodeKeys.contains( ruleKey ) )
630 setExpanded( legendNode2index( legendNode ), true );
631 }
632 return;
633 }
634
635 QList<QgsLayerTreeNode *> children = parentNode->children();
636 for ( int i = start; i <= end && i < children.count(); ++i )
637 {
638 updateExpandedStateFromNode( children[i] );
639 }
640
641 // make sure we still have correct current layer
643}
644
646{
647 // make sure we still have correct current layer
649}
650
652{
653 QgsLayerTreeModel *layerModel = layerTreeModel();
654 if ( !layerModel )
655 return;
656
657 QgsMapLayer *layerCurrent = layerForIndex( currentIndex() );
658 const QString layerCurrentID = layerCurrent ? layerCurrent->id() : QString();
659 if ( mCurrentLayerID == layerCurrentID )
660 return;
661
662 // update the current index in model (the item will be underlined)
663 QModelIndex proxyModelNodeLayerIndex;
664 if ( layerCurrent )
665 {
666 QgsLayerTreeLayer *nodeLayer = layerModel->rootGroup()->findLayer( layerCurrentID );
667 if ( nodeLayer )
668 proxyModelNodeLayerIndex = node2index( nodeLayer );
669 }
670
671 if ( !proxyModelNodeLayerIndex.isValid() )
672 {
673 mCurrentLayerID = QString();
674 layerModel->setCurrentIndex( QModelIndex() );
675 }
676 else
677 {
678 mCurrentLayerID = layerCurrentID;
679 layerModel->setCurrentIndex( mProxyModel->mapToSource( proxyModelNodeLayerIndex ) );
680 }
681
682 //checkModel();
683
684 emit currentLayerChanged( layerCurrent );
685}
686
687void QgsLayerTreeView::onDoubleClicked( const QModelIndex &index )
688{
689 QgsLayerTreeNode *node = index2node( index );
690
691 if ( !node || node->nodeType() != QgsLayerTreeNode::NodeGroup )
692 return;
693
694 node->setExpanded( !node->isExpanded() );
695}
696
697
698void QgsLayerTreeView::onCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key )
699{
700 QgsLayerTreeModel *layerModel = layerTreeModel();
701 if ( key != "expandedLegendNodes"_L1 || !QgsLayerTree::isLayer( node ) || !layerModel )
702 return;
703
704 const QSet<QString> expandedLegendNodes = qgis::listToSet( node->customProperty( u"expandedLegendNodes"_s ).toStringList() );
705
706 const QList<QgsLayerTreeModelLegendNode *> legendNodes = layerModel->layerLegendNodes( QgsLayerTree::toLayer( node ), true );
707 for ( QgsLayerTreeModelLegendNode *legendNode : legendNodes )
708 {
709 const QString key = legendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
710 if ( !key.isEmpty() )
711 setExpanded( legendNode2index( legendNode ), expandedLegendNodes.contains( key ) );
712 }
713}
714
716{
717 if ( !mIndicators[node].contains( indicator ) )
718 {
719 mIndicators[node].append( indicator );
720 connect( indicator, &QgsLayerTreeViewIndicator::changed, this, [this] {
721 update();
722 viewport()->repaint();
723 } );
724 update();
725 viewport()->repaint(); //update() does not automatically trigger a repaint()
726 }
727}
728
730{
731 mIndicators[node].removeOne( indicator );
732 update();
733}
734
735QList<QgsLayerTreeViewIndicator *> QgsLayerTreeView::indicators( QgsLayerTreeNode *node ) const
736{
737 return mIndicators.value( node );
738}
739
741QStringList QgsLayerTreeView::viewOnlyCustomProperties()
742{
743 return QStringList() << u"expandedLegendNodes"_s;
744}
746
747void QgsLayerTreeView::refreshLayerSymbology( const QString &layerId )
748{
749 QgsLayerTreeModel *layerModel = layerTreeModel();
750 if ( !layerModel )
751 return;
752 QgsLayerTreeLayer *nodeLayer = layerModel->rootGroup()->findLayer( layerId );
753 if ( nodeLayer )
754 layerModel->refreshLayerLegend( nodeLayer );
755}
756
757
759{
760 if ( mMessageBar == messageBar )
761 return;
762
763 mMessageBar = messageBar;
764 QgsLayerTreeModel *layerModel = layerTreeModel();
765 if ( !layerModel )
766 return;
767
768 if ( mMessageBar )
769 connect( layerModel, &QgsLayerTreeModel::messageEmitted, this, [this]( const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info, int duration = 5 ) {
770 Q_UNUSED( duration )
771 mMessageBar->pushMessage( message, level );
772 } );
773}
774
776{
777 if ( !mProxyModel )
778 return;
779
780 mShowPrivateLayers = showPrivate;
781 mProxyModel->setShowPrivateLayers( showPrivate );
782}
783
785{
786 if ( !mProxyModel )
787 return;
788
789 mHideValidLayers = hideValid;
790 mProxyModel->setHideValidLayers( mHideValidLayers );
791}
792
794{
795 return mShowPrivateLayers;
796}
797
799{
800 return mHideValidLayers;
801}
802
803void QgsLayerTreeView::mouseReleaseEvent( QMouseEvent *event )
804{
805 QgsLayerTreeModel *layerModel = layerTreeModel();
806 if ( !layerModel )
807 return;
808
809 // we need to keep last mouse position in order to know whether to emit an indicator's clicked() signal
810 // (the item delegate needs to know which indicator has been clicked)
811 mLastReleaseMousePos = event->pos();
812
813 const QgsLayerTreeModel::Flags oldFlags = layerModel->flags();
814 if ( event->modifiers() & Qt::ControlModifier )
815 layerModel->setFlags( oldFlags | QgsLayerTreeModel::ActionHierarchical );
816 else
817 layerModel->setFlags( oldFlags & ~QgsLayerTreeModel::ActionHierarchical );
818 QTreeView::mouseReleaseEvent( event );
819 layerModel->setFlags( oldFlags );
820}
821
822void QgsLayerTreeView::keyPressEvent( QKeyEvent *event )
823{
824 if ( event->key() == Qt::Key_Space )
825 {
826 const QList<QgsLayerTreeNode *> constSelectedNodes = selectedNodes();
827
828 if ( !constSelectedNodes.isEmpty() )
829 {
830 const bool isFirstNodeChecked = constSelectedNodes[0]->itemVisibilityChecked();
831 for ( QgsLayerTreeNode *node : constSelectedNodes )
832 {
833 node->setItemVisibilityChecked( !isFirstNodeChecked );
834 }
835
836 // if we call the original keyPress handler, the current item will be checked to the original state yet again
837 return;
838 }
839 }
840
841 QgsLayerTreeModel *layerModel = layerTreeModel();
842 if ( !layerModel )
843 return;
844 const QgsLayerTreeModel::Flags oldFlags = layerModel->flags();
845 if ( event->modifiers() & Qt::ControlModifier )
846 layerModel->setFlags( oldFlags | QgsLayerTreeModel::ActionHierarchical );
847 else
848 layerModel->setFlags( oldFlags & ~QgsLayerTreeModel::ActionHierarchical );
849 QTreeView::keyPressEvent( event );
850 layerModel->setFlags( oldFlags );
851}
852
853void QgsLayerTreeView::dragEnterEvent( QDragEnterEvent *event )
854{
855 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.uri"_s ) )
856 {
857 // the mime data are coming from layer tree, so ignore that, do not import those layers again
858 if ( !event->mimeData()->hasFormat( u"application/qgis.layertreemodeldata"_s ) )
859 {
860 event->accept();
861 return;
862 }
863 }
864 QTreeView::dragEnterEvent( event );
865}
866
867void QgsLayerTreeView::dragMoveEvent( QDragMoveEvent *event )
868{
869 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.uri"_s ) )
870 {
871 // the mime data are coming from layer tree, so ignore that, do not import those layers again
872 if ( !event->mimeData()->hasFormat( u"application/qgis.layertreemodeldata"_s ) )
873 {
874 event->accept();
875 return;
876 }
877 }
878 QTreeView::dragMoveEvent( event );
879}
880
881void QgsLayerTreeView::dropEvent( QDropEvent *event )
882{
883 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.uri"_s ) )
884 {
885 // the mime data are coming from layer tree, so ignore that, do not import those layers again
886 if ( !event->mimeData()->hasFormat( u"application/qgis.layertreemodeldata"_s ) )
887 {
888 event->accept();
889
890 QModelIndex index = indexAt( event->pos() );
891 if ( index.isValid() )
892 {
893 setCurrentIndex( index );
894 }
895
896 emit datasetsDropped( event );
897 return;
898 }
899 }
900 if ( event->keyboardModifiers() & Qt::AltModifier || event->keyboardModifiers() & Qt::ControlModifier )
901 {
902 event->accept();
903 }
904 QTreeView::dropEvent( event );
905}
906
907void QgsLayerTreeView::resizeEvent( QResizeEvent *event )
908{
909 // Since last column is resized to content (instead of stretched), the active
910 // selection rectangle ends at width of widest visible item in tree,
911 // regardless of which item is selected. This causes layer indicators to
912 // become 'inactive' (not clickable and no tool tip) unless their rectangle
913 // enters the view item's selection (active) rectangle.
914 // Always resetting the minimum section size relative to the viewport ensures
915 // the view item's selection rectangle extends to the right edge of the
916 // viewport, which allows indicators to become active again.
917 header()->setMinimumSectionSize( viewport()->width() );
918 QTreeView::resizeEvent( event );
919}
920
921void QgsLayerTreeView::onHorizontalScroll( int value )
922{
923 Q_UNUSED( value )
924 viewport()->update();
925}
926
927void QgsLayerTreeView::onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
928{
929 Q_UNUSED( topLeft )
930 Q_UNUSED( bottomRight )
931
932 // If an item is resized asynchronously (e.g. wms legend)
933 // The items below will need to be shifted vertically.
934 // This doesn't happen automatically, unless the viewport update is triggered.
935
936 if ( roles.contains( Qt::SizeHintRole ) )
937 viewport()->update();
938
939 //checkModel();
940}
941
942#if 0
943// for model debugging
944void QgsLayerTreeView::checkModel()
945{
946 std::function<void( QgsLayerTreeNode *, int )> debug;
947 debug = [ & ]( QgsLayerTreeNode * node, int depth )
948 {
949 if ( depth == 1 )
950 qDebug() << "----------------------------------------------";
951
952 qDebug() << depth << node->name() << node2index( node ) << layerTreeModel()->rowCount( node2sourceIndex( node ) ) << mProxyModel->rowCount( node2index( node ) );
953 Q_ASSERT( node == index2node( node2index( node ) ) );
954 Q_ASSERT( node == layerTreeModel()->index2node( node2sourceIndex( node ) ) );
955 Q_ASSERT( layerTreeModel()->rowCount( node2sourceIndex( node ) ) == mProxyModel->rowCount( node2index( node ) ) );
956
957 for ( int i = 0; i < mProxyModel->rowCount( node2index( node ) ); i++ )
958 {
959 QgsLayerTreeNode *childNode { index2node( mProxyModel->index( i, 0, node2index( node ) ) ) };
960 if ( childNode )
961 debug( childNode, depth + 1 );
962 else
963 qDebug() << "Warning no child node!";
964 }
965 };
966 debug( layerTreeModel()->rootGroup(), 1 );
967}
968#endif
969
971{
972 return mProxyModel;
973}
974
976 : QSortFilterProxyModel( parent )
977 , mLayerTreeModel( treeModel )
978{
979 setSourceModel( treeModel );
980}
981
982void QgsLayerTreeProxyModel::setFilterText( const QString &filterText )
983{
984 if ( filterText == mFilterText )
985 return;
986
987 mFilterText = filterText;
988 invalidateFilter();
989}
990
991bool QgsLayerTreeProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
992{
993 QgsLayerTreeNode *node = mLayerTreeModel->index2node( mLayerTreeModel->index( sourceRow, 0, sourceParent ) );
994 return nodeShown( node );
995}
996
998{
999 if ( !node )
1000 return true;
1001
1002 switch ( node->nodeType() )
1003 {
1005 {
1006 QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer();
1007 if ( !layer )
1008 return true;
1009 if ( !mFilterText.isEmpty() && !layer->name().contains( mFilterText, Qt::CaseInsensitive ) )
1010 return false;
1011 if ( !mShowPrivateLayers && layer->flags().testFlag( QgsMapLayer::LayerFlag::Private ) )
1012 {
1013 return false;
1014 }
1015 if ( mHideValidLayers && layer->isValid() )
1016 return false;
1017
1018 return true;
1019 }
1022 return true;
1023 }
1024 return true;
1025}
1026
1028{
1029 return mShowPrivateLayers;
1030}
1031
1033{
1034 if ( showPrivate == mShowPrivateLayers )
1035 return;
1036
1037 mShowPrivateLayers = showPrivate;
1038 invalidateFilter();
1039}
1040
1042{
1043 return mHideValidLayers;
1044}
1045
1047{
1048 if ( hideValid == mHideValidLayers )
1049 return;
1050
1051 mHideValidLayers = hideValid;
1052 invalidateFilter();
1053}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:160
@ Info
Information message.
Definition qgis.h:161
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:7390
static QgsLayerTreeEmbeddedWidgetRegistry * layerTreeEmbeddedWidgetRegistry()
Returns the global layer tree embedded widget registry, used for registering widgets that may be embe...
Definition qgsgui.cpp:145
Provider interface to be implemented in order to introduce new kinds of embedded widgets for use in l...
Layer tree group node serves as a container for layers and further groups.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
Layer tree node points to a map layer.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
An abstract interface for legend items returned from QgsMapLayerLegend implementation.
virtual QVariant data(int role) const =0
Returns data associated with the item. Must be implemented in derived class.
@ ParentRuleKey
Rule key of the parent legend node - for legends with tree hierarchy (QString). Added in 2....
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
A model representing the layer tree, including layers and groups of layers.
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...
Qt::ItemFlags flags(const QModelIndex &index) const override
void setCurrentIndex(const QModelIndex &currentIndex)
Sets index of the current item. May be used by view. Item marked as current is underlined.
void setFlags(QgsLayerTreeModel::Flags f)
Sets OR-ed combination of model flags.
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non nullptr value.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void messageEmitted(const QString &message, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=5)
Emits a message than can be displayed to the user in a GUI class.
void setTargetScreenProperties(const QSet< QgsScreenProperties > &properties)
Sets all target screen properties to use when generating icons for Qt::DecorationRole data.
static QgsLayerTreeModelLegendNode * index2legendNode(const QModelIndex &index)
Returns legend node for given index.
void refreshLayerLegend(QgsLayerTreeLayer *nodeLayer)
Force a refresh of legend nodes of a layer node.
bool testFlag(Flag f) const
Check whether a flag is enabled.
@ ActionHierarchical
Check/uncheck action has consequences on children (or parents for leaf node).
@ UseEmbeddedWidgets
Layer nodes may optionally include extra embedded widgets (if used in QgsLayerTreeView)....
Base class for nodes in a layer tree.
@ NodeCustom
Leaf node pointing to a custom object.
@ NodeGroup
Container of other groups and layers.
@ NodeLayer
Leaf node pointing to a layer.
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.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
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 setExpanded(bool expanded)
Sets whether the node should be shown as expanded or collapsed in GUI.
virtual QString name() const =0
Returns name of the node.
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is nullptr, the node is a root node.
NodeType nodeType() const
Find out about type of the node. It is usually shorter to use convenience functions from QgsLayerTree...
void customPropertyChanged(QgsLayerTreeNode *node, const QString &key)
Emitted when a custom property of a node within the tree has been changed or removed.
bool isExpanded() const
Returns whether the node should be shown as expanded or collapsed in GUI.
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children).
void expandedChanged(QgsLayerTreeNode *node, bool expanded)
Emitted when the collapsed/expanded state of a node within the tree has been changed.
A proxy model for QgsLayerTreeModel, supporting private layers and text filtering.
bool showPrivateLayers() const
Returns if private layers are shown.
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
void setFilterText(const QString &filterText=QString())
Sets filter to filterText.
bool hideValidLayers() const
Returns if valid layers should be hidden (i.e.
void setHideValidLayers(bool hideValid)
Sets whether valid layers should be hidden (i.e.
virtual bool nodeShown(QgsLayerTreeNode *node) const
Returns true if the specified node should be shown.
void setShowPrivateLayers(bool showPrivate)
Determines if private layers are shown.
QgsLayerTreeProxyModel(QgsLayerTreeModel *treeModel, QObject *parent)
Constructs QgsLayerTreeProxyModel with source model treeModel and a parent.
static QSet< QgsMapLayer * > collectMapLayersRecursive(const QList< QgsLayerTreeNode * > &nodes)
Returns map layers from the given list of layer tree nodes.
void updateExpandedStateToNode(const QModelIndex &index)
Stores the expanded state to a node with matching index.
QList< QgsLayerTreeNode * > selectedNodes(bool skipInternal=false) const
Returns the list of selected layer tree nodes.
QgsLayerTreeNode * currentNode() const
Returns the current node.
QgsLayerTreeGroup * currentGroupNode() const
Returns the current group node.
QList< QgsMapLayer * > selectedLayers() const
Returns the list of selected layers.
QModelIndex node2sourceIndex(QgsLayerTreeNode *node) const
Returns the layer tree source model index for a given node.
QgsLayerTreeModelLegendNode * index2legendNode(const QModelIndex &index) const
Returns legend node for given view index.
void mouseDoubleClickEvent(QMouseEvent *event) override
void setLayerTreeModel(QgsLayerTreeModel *model)
Associates a layer tree model with the view.
QgsMapLayer * layerForIndex(const QModelIndex &index) const
Returns the map layer corresponding to a view index.
QModelIndex legendNode2index(QgsLayerTreeModelLegendNode *legendNode)
Returns the view index for a given legend node.
void setCurrentNode(QgsLayerTreeNode *node)
Sets the currently selected node.
void onExpandedChanged(QgsLayerTreeNode *node, bool expanded)
Called when the expanded state changes for a node.
QModelIndex layerTreeModelIndexToViewIndex(const QModelIndex &index) const
Returns the layer tree model index corresponding with a view index.
QModelIndex legendNode2sourceIndex(QgsLayerTreeModelLegendNode *legendNode)
Returns the layer tree source model index for a given legend node.
QList< QgsLayerTreeModelLegendNode * > selectedLegendNodes() const
Returns the list of selected legend nodes.
QModelIndex node2index(QgsLayerTreeNode *node) const
Returns the view model index for a given node.
void onModelReset()
Called when the model is reset.
void updateExpandedStateFromNode(QgsLayerTreeNode *node)
Updates the expanded state from a node.
QList< QgsLayerTreeLayer * > selectedLayerNodes() const
Returns the list of selected nodes filtered to just layer nodes (QgsLayerTreeLayer).
QModelIndex viewIndexToLayerTreeModelIndex(const QModelIndex &index) const
Returns the view index corresponding with a layer tree model index.
QgsLayerTreeViewBase(QWidget *parent=nullptr)
Constructor for QgsLayerTreeViewBase.
QgsLayerTreeViewDefaultActions * defaultActions()
Gets access to the default actions that may be used with the tree view.
void setCurrentLayer(QgsMapLayer *layer)
Sets the currently selected layer.
void collapseAllNodes()
Enhancement of QTreeView::collapseAll() that also records expanded state in layer tree nodes.
QList< QgsMapLayer * > selectedLayersRecursive() const
Gets list of selected layers, including those that are not directly selected, but their ancestor grou...
QgsLayerTreeNode * index2node(const QModelIndex &index) const
Returns the layer tree node for given view index.
QgsLayerTreeViewDefaultActions * mDefaultActions
helper class with default actions. Lazily initialized.
QgsLayerTreeModelLegendNode * currentLegendNode() const
Gets current legend node.
void expandAllNodes()
Enhancement of QTreeView::expandAll() that also records expanded state in layer tree nodes.
QgsMapLayer * currentLayer() const
Returns the currently selected layer, or nullptr if no layers is selected.
QgsLayerTreeModel * layerTreeModel() const
Returns the associated layer tree model.
Serves as a factory of actions that can be used together with a layer tree view.
Indicator that can be used in a layer tree view to display icons next to items of the layer tree.
void changed()
Emitted when the indicator changes state (e.g.
Implementation of this interface can be implemented to allow QgsLayerTreeView instance to provide cus...
QList< QgsLayerTreeViewIndicator * > indicators(QgsLayerTreeNode *node) const
Returns list of indicators associated with a particular layer tree node.
void currentLayerChanged(QgsMapLayer *layer)
Emitted when a current layer is changed.
void onDoubleClicked(const QModelIndex &index)
Expands layer groups only.
void contextMenuAboutToShow(QMenu *menu)
Emitted when the context menu is about to show.
QString mCurrentLayerID
Keeps track of current layer ID (to check when to emit signal about change of current layer).
void datasetsDropped(QDropEvent *event)
Emitted when datasets are dropped onto the layer tree view.
void setModel(QAbstractItemModel *model) override
Overridden setModel() from base class.
void removeIndicator(QgsLayerTreeNode *node, QgsLayerTreeViewIndicator *indicator)
Removes a previously added indicator to a layer tree node.
QHash< QgsLayerTreeNode *, QList< QgsLayerTreeViewIndicator * > > mIndicators
Storage of indicators used with the tree view.
void refreshLayerSymbology(const QString &layerId)
Force refresh of layer symbology. Normally not needed as the changes of layer's renderer are monitore...
void dropEvent(QDropEvent *event) override
QgsLayerTreeView(QWidget *parent=nullptr)
Constructor for QgsLayerTreeView.
QgsLayerTreeViewMenuProvider * menuProvider() const
Returns pointer to the context menu provider. May be nullptr.
void resizeEvent(QResizeEvent *event) override
void mouseReleaseEvent(QMouseEvent *event) override
bool hideValidLayers() const
Returns if valid layers should be hidden (i.e.
void setMenuProvider(QgsLayerTreeViewMenuProvider *menuProvider)
Sets provider for context menu. Takes ownership of the instance.
void setLayerVisible(QgsMapLayer *layer, bool visible)
Convenience methods which sets the visible state of the specified map layer.
void dragMoveEvent(QDragMoveEvent *event) override
QPoint mLastReleaseMousePos
Used by the item delegate for identification of which indicator has been clicked.
friend class QgsLayerTreeViewItemDelegate
QgsLayerTreeProxyModel * proxyModel() const
Returns the proxy model used by the view.
void contextMenuEvent(QContextMenuEvent *event) override
void keyPressEvent(QKeyEvent *event) override
void setMessageBar(QgsMessageBar *messageBar)
Set the message bar to display messages from the layer tree.
void addIndicator(QgsLayerTreeNode *node, QgsLayerTreeViewIndicator *indicator)
Adds an indicator to the given layer tree node.
void setHideValidLayers(bool hideValid)
Sets whether valid layers should be hidden (i.e.
std::unique_ptr< QgsLayerTreeViewMenuProvider > mMenuProvider
Context menu provider. Owned by the view.
void modelRowsInserted(const QModelIndex &index, int start, int end)
void setLayerMarkWidth(int width)
Set width of contextual menu mark, at right of layer node items.
void setShowPrivateLayers(bool showPrivate)
Set the show private layers to showPrivate.
void dragEnterEvent(QDragEnterEvent *event) override
bool showPrivateLayers() const
Returns the show private layers status.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QString id
Definition qgsmaplayer.h:86
QgsMapLayer::LayerFlags flags
Definition qgsmaplayer.h:99
@ Private
Determines if the layer is meant to be exposed to the GUI, i.e. visible in the layer legend tree.
A bar for displaying non-blocking messages to the user.
A utility class for dynamic handling of changes to screen properties.
void screenDpiChanged(double dpi)
Emitted whenever the screen dpi associated with the widget is changed.
Stores properties relating to a screen.