QGIS API Documentation 3.99.0-Master (d270888f95f)
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 "qgslayertreemodel.h"
23#include "qgslayertreeutils.h"
25#include "qgsmaplayer.h"
26#include "qgsmessagebar.h"
27
28#include <QApplication>
29#include <QContextMenuEvent>
30#include <QHeaderView>
31#include <QMenu>
32#include <QMimeData>
33#include <QScrollBar>
34#include <QString>
35
36#include "moc_qgslayertreeview.cpp"
37
38using namespace Qt::StringLiterals;
39
40#ifdef ENABLE_MODELTEST
41#include "modeltest.h"
42#endif
43
46
47
49 : QTreeView( parent )
50 , mBlockDoubleClickTimer( new QTimer( this ) )
51{
52 mBlockDoubleClickTimer->setSingleShot( true );
53 mBlockDoubleClickTimer->setInterval( QApplication::doubleClickInterval() );
54
55 setHeaderHidden( true );
56
57 setDragEnabled( true );
58 setAcceptDrops( true );
59 setDropIndicatorShown( true );
60 setEditTriggers( EditKeyPressed );
61 setExpandsOnDoubleClick( false ); // normally used for other actions
62
63 // Ensure legend graphics are scrollable
64 header()->setStretchLastSection( false );
65 header()->setSectionResizeMode( QHeaderView::ResizeToContents );
66
67 // If vertically scrolling by item, legend graphics can get clipped
68 setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
69
70 setSelectionMode( ExtendedSelection );
71 setDefaultDropAction( Qt::MoveAction );
72
73 connect( this, &QTreeView::collapsed, this, &QgsLayerTreeViewBase::updateExpandedStateToNode );
74 connect( this, &QTreeView::expanded, this, &QgsLayerTreeViewBase::updateExpandedStateToNode );
75}
76
78{
79 delete mBlockDoubleClickTimer;
80}
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->addTargetScreenProperties( QgsScreenProperties( screen() ) );
102
103 connect( mLayerTreeModel->rootGroup(), &QgsLayerTreeNode::expandedChanged, this, &QgsLayerTreeViewBase::onExpandedChanged );
104
105 connect( mLayerTreeModel, &QAbstractItemModel::modelReset, this, &QgsLayerTreeViewBase::onModelReset );
106 connect( mLayerTreeModel, &QAbstractItemModel::dataChanged, this, &QgsLayerTreeViewBase::onDataChanged );
107
108 updateExpandedStateFromNode( mLayerTreeModel->rootGroup() );
109}
110
112{
113 return mLayerTreeModel;
114}
115
117{
118 const QModelIndex idx = node2index( node );
119 setExpanded( idx, node->isExpanded() );
120
121 const auto constChildren = node->children();
122 for ( QgsLayerTreeNode *child : constChildren )
124}
125
126QModelIndex QgsLayerTreeViewBase::viewIndexToLayerTreeModelIndex( const QModelIndex &index ) const
127{
128 // if no proxy in use, we already have a layer tree model index
129 if ( model() == mLayerTreeModel )
130 return index;
131
132 if ( QAbstractProxyModel *proxy = qobject_cast< QAbstractProxyModel *>( model() ) )
133 {
134 return proxy->mapToSource( index );
135 }
136
137 return QModelIndex();
138}
139
140QModelIndex QgsLayerTreeViewBase::layerTreeModelIndexToViewIndex( const QModelIndex &index ) const
141{
142 // if no proxy in use, we already have a view index
143 if ( model() == mLayerTreeModel )
144 return index;
145
146 if ( QAbstractProxyModel *proxy = qobject_cast< QAbstractProxyModel *>( model() ) )
147 {
148 return proxy->mapFromSource( index );
149 }
150
151 return QModelIndex();
152}
153
155{
156 if ( QgsLayerTreeNode *node = index2node( index ) )
157 {
158 node->setExpanded( isExpanded( index ) );
159 }
160 else if ( QgsLayerTreeModelLegendNode *node = index2legendNode( index ) )
161 {
162 const QString ruleKey = node->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
163 QStringList lst = node->layerNode()->customProperty( u"expandedLegendNodes"_s ).toStringList();
164 const bool expanded = isExpanded( index );
165 const bool isInList = lst.contains( ruleKey );
166 if ( expanded && !isInList )
167 {
168 lst.append( ruleKey );
169 node->layerNode()->setCustomProperty( u"expandedLegendNodes"_s, lst );
170 }
171 else if ( !expanded && isInList )
172 {
173 lst.removeAll( ruleKey );
174 node->layerNode()->setCustomProperty( u"expandedLegendNodes"_s, lst );
175 }
176 }
177}
178
180{
181 const QModelIndex idx = node2index( node );
182 if ( isExpanded( idx ) != expanded )
183 setExpanded( idx, expanded );
184}
185
187{
188 if ( !mLayerTreeModel )
189 return;
190 updateExpandedStateFromNode( mLayerTreeModel->rootGroup() );
191 //checkModel();
192}
193
194void QgsLayerTreeViewBase::onDataChanged( const QModelIndex &, const QModelIndex &, const QVector<int> & )
195{
196 mBlockDoubleClickTimer->start();
197}
198
199QgsLayerTreeNode *QgsLayerTreeViewBase::index2node( const QModelIndex &index ) const
200{
201 if ( mLayerTreeModel )
202 return mLayerTreeModel->index2node( viewIndexToLayerTreeModelIndex( index ) );
203
204 return nullptr;
205}
206
211
213{
214 if ( mLayerTreeModel )
215 return mLayerTreeModel->node2index( node );
216
217 return QModelIndex();
218}
219
224
226{
227 if ( QItemSelectionModel *selectModel = selectionModel() )
228 return index2node( selectModel->currentIndex() );
229 else
230 return nullptr;
231}
232
233QList<QgsLayerTreeNode *> QgsLayerTreeViewBase::selectedNodes( bool skipInternal ) const
234{
235 if ( !mLayerTreeModel )
236 return {};
237
238 QModelIndexList mapped;
239 if ( QItemSelectionModel *selectModel = selectionModel() )
240 {
241 const QModelIndexList selected = selectModel->selectedIndexes();
242 mapped.reserve( selected.size() );
243 for ( const QModelIndex &index : selected )
244 mapped << viewIndexToLayerTreeModelIndex( index );
245 }
246
247 return mLayerTreeModel->indexes2nodes( mapped, skipInternal );
248}
249
250
252{
253 return layerForIndex( currentIndex() );
254}
255
256QgsMapLayer *QgsLayerTreeViewBase::layerForIndex( const QModelIndex &index ) const
257{
258 // Check if model has been set and index is valid
259 if ( mLayerTreeModel && index.isValid() )
260 {
261 if ( QgsLayerTreeNode *node = index2node( index ) )
262 {
263 if ( QgsLayerTree::isLayer( node ) )
264 return QgsLayerTree::toLayer( node )->layer();
265 }
266 else
267 {
268 // possibly a legend node
269 QgsLayerTreeModelLegendNode *legendNode = index2legendNode( index );
270 if ( legendNode )
271 return legendNode->layerNode()->layer();
272 }
273 }
274 return nullptr;
275}
276
278{
280 if ( QgsLayerTree::isGroup( node ) )
281 return QgsLayerTree::toGroup( node );
282 else if ( QgsLayerTree::isLayer( node ) )
283 {
284 QgsLayerTreeNode *parent = node->parent();
285 if ( QgsLayerTree::isGroup( parent ) )
286 return QgsLayerTree::toGroup( parent );
287 }
288
289 if ( QItemSelectionModel *selectModel = selectionModel() )
290 {
291 if ( QgsLayerTreeModelLegendNode *legendNode = index2legendNode( selectModel->currentIndex() ) )
292 {
293 QgsLayerTreeLayer *parent = legendNode->layerNode();
294 if ( QgsLayerTree::isGroup( parent->parent() ) )
295 return QgsLayerTree::toGroup( parent->parent() );
296 }
297 }
298
299 return nullptr;
300}
301
302QList<QgsLayerTreeLayer *> QgsLayerTreeViewBase::selectedLayerNodes() const
303{
304 QList<QgsLayerTreeLayer *> layerNodes;
305 const QList<QgsLayerTreeNode *> constSelectedNodes = selectedNodes();
306 layerNodes.reserve( constSelectedNodes.size() );
307 for ( QgsLayerTreeNode *node : constSelectedNodes )
308 {
309 if ( QgsLayerTree::isLayer( node ) )
310 layerNodes << QgsLayerTree::toLayer( node );
311 }
312 return layerNodes;
313}
314
315QList<QgsMapLayer *> QgsLayerTreeViewBase::selectedLayers() const
316{
317 QList<QgsMapLayer *> list;
318 const QList<QgsLayerTreeLayer *> constSelectedLayerNodes = selectedLayerNodes();
319 list.reserve( constSelectedLayerNodes.size() );
320 for ( QgsLayerTreeLayer *node : constSelectedLayerNodes )
321 {
322 if ( node->layer() )
323 list << node->layer();
324 }
325 return list;
326}
327
332
334{
335 if ( mLayerTreeModel )
336 return mLayerTreeModel->legendNode2index( legendNode );
337 return QModelIndex();
338}
339
341{
342 if ( !node )
343 {
344 setCurrentIndex( QModelIndex() );
345 return;
346 }
347
348 setCurrentIndex( node2index( node ) );
349}
350
352{
353 if ( !layer || !mLayerTreeModel )
354 {
355 setCurrentIndex( QModelIndex() );
356 return;
357 }
358
359 QgsLayerTreeLayer *nodeLayer = mLayerTreeModel->rootGroup()->findLayer( layer->id() );
360 if ( !nodeLayer )
361 return;
362
363 setCurrentNode( nodeLayer );
364}
365
367{
368 if ( QItemSelectionModel *selectModel = selectionModel() )
369 return index2legendNode( selectModel->currentIndex() );
370 else
371 return nullptr;
372}
373
374QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeViewBase::selectedLegendNodes() const
375{
376 QList<QgsLayerTreeModelLegendNode *> res;
377 QItemSelectionModel *selectModel = selectionModel();
378 if ( !mLayerTreeModel || !selectModel )
379 return res;
380
381 const QModelIndexList selected = selectModel->selectedIndexes();
382 res.reserve( selected.size() );
383 for ( const QModelIndex &index : selected )
384 {
385 const QModelIndex modelIndex = viewIndexToLayerTreeModelIndex( index );
386 if ( QgsLayerTreeModelLegendNode *node = mLayerTreeModel->index2legendNode( modelIndex ) )
387 {
388 res.push_back( node );
389 }
390 }
391
392 return res;
393}
394
396{
397 QModelIndexList mapped;
398 QItemSelectionModel *selectModel = selectionModel();
399 if ( !mLayerTreeModel || !selectModel )
400 return QList<QgsMapLayer *>();
401
402 const QModelIndexList selected = selectModel->selectedIndexes();
403 mapped.reserve( selected.size() );
404 for ( const QModelIndex &index : selected )
405 {
406 mapped << viewIndexToLayerTreeModelIndex( index );
407 }
408
409 const QList<QgsLayerTreeNode *> nodes = mLayerTreeModel->indexes2nodes( mapped, false );
410 const QSet<QgsMapLayer *> layersSet = QgsLayerTreeUtils::collectMapLayersRecursive( nodes );
411 return qgis::setToList( layersSet );
412}
413
414static void expandAllLegendNodes( QgsLayerTreeLayer *nodeLayer, bool expanded, QgsLayerTreeModel *model )
415{
416 // for layers we also need to find out with legend nodes contain some children and make them expanded/collapsed
417 // if we are collapsing, we just write out an empty list
418 QStringList lst;
419 if ( expanded )
420 {
421 const auto constLayerLegendNodes = model->layerLegendNodes( nodeLayer, true );
422 for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
423 {
424 const QString parentKey = legendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::ParentRuleKey ) ).toString();
425 if ( !parentKey.isEmpty() && !lst.contains( parentKey ) )
426 lst << parentKey;
427 }
428 }
429 nodeLayer->setCustomProperty( u"expandedLegendNodes"_s, lst );
430}
431
432static void expandAllNodes( QgsLayerTreeGroup *parent, bool expanded, QgsLayerTreeModel *model )
433{
434 const auto constChildren = parent->children();
435 for ( QgsLayerTreeNode *node : constChildren )
436 {
437 node->setExpanded( expanded );
438 if ( QgsLayerTree::isGroup( node ) )
439 expandAllNodes( QgsLayerTree::toGroup( node ), expanded, model );
440 else if ( QgsLayerTree::isLayer( node ) )
441 expandAllLegendNodes( QgsLayerTree::toLayer( node ), expanded, model );
442 }
443}
444
446{
447 // unfortunately expandAll() does not emit expanded() signals
448 QgsLayerTreeModel *layerModel = layerTreeModel();
449 if ( !layerModel )
450 return;
451 ::expandAllNodes( layerModel->rootGroup(), true, layerModel );
452 expandAll();
453}
454
456{
457 // unfortunately collapseAll() does not emit collapsed() signals
458 QgsLayerTreeModel *layerModel = layerTreeModel();
459 if ( !layerModel )
460 return;
461 ::expandAllNodes( layerModel->rootGroup(), false, layerModel );
462 collapseAll();
463}
464
471
472//
473// QgsLayerTreeView
474//
475
477 : QgsLayerTreeViewBase( parent )
478{
479 // we need a custom item delegate in order to draw indicators
480 setItemDelegate( new QgsLayerTreeViewItemDelegate( this ) );
481 setStyle( new QgsLayerTreeViewProxyStyle( this ) );
482
483 setLayerMarkWidth( static_cast<int>( QFontMetricsF( font() ).horizontalAdvance( 'l' ) * Qgis::UI_SCALE_FACTOR ) );
484
485 connect( horizontalScrollBar(), &QScrollBar::valueChanged, this, &QgsLayerTreeView::onHorizontalScroll );
486}
487
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
507{
508 if ( mMessageBar )
509 connect( treeModel, &QgsLayerTreeModel::messageEmitted, this, [this]( const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info, int duration = 5 ) {
510 Q_UNUSED( duration )
511 mMessageBar->pushMessage( message, level );
512 } );
513
514 mProxyModel = proxyModel;
515 connect( mProxyModel, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted );
516 connect( mProxyModel, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved );
517
518#ifdef ENABLE_MODELTEST
519 new ModelTest( mProxyModel, this );
520#endif
521
522 QTreeView::setModel( mProxyModel );
524
525 connect( treeModel->rootGroup(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayerTreeView::onCustomPropertyChanged );
526
527 connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayerTreeView::onCurrentChanged );
528
529 connect( treeModel, &QAbstractItemModel::dataChanged, this, &QgsLayerTreeView::onDataChanged );
530
531 //checkModel();
532}
533
539
541{
542 QgsLayerTreeModel *layerModel = layerTreeModel();
543 if ( !layer || !layerModel )
544 return;
545 QgsLayerTreeLayer *nodeLayer = layerModel->rootGroup()->findLayer( layer->id() );
546 if ( !nodeLayer )
547 return;
548 nodeLayer->setItemVisibilityChecked( visible );
549}
550
551void QgsLayerTreeView::contextMenuEvent( QContextMenuEvent *event )
552{
553 if ( !mMenuProvider )
554 return;
555
556 const QModelIndex idx = indexAt( event->pos() );
557 if ( !idx.isValid() )
558 setCurrentIndex( QModelIndex() );
559
560 QMenu *menu = mMenuProvider->createContextMenu();
561 if ( menu )
562 {
563 emit contextMenuAboutToShow( menu );
564
565 if ( menu->actions().count() != 0 )
566 menu->exec( mapToGlobal( event->pos() ) );
567 delete menu;
568 }
569}
570
571
572void QgsLayerTreeView::modelRowsInserted( const QModelIndex &index, int start, int end )
573{
574 QgsLayerTreeNode *parentNode = index2node( index );
575 QgsLayerTreeModel *layerModel = layerTreeModel();
576 if ( !parentNode || !layerModel )
577 return;
578
579 // Embedded widgets - replace placeholders in the model by actual widgets
580 if ( layerModel->testFlag( QgsLayerTreeModel::UseEmbeddedWidgets ) && QgsLayerTree::isLayer( parentNode ) )
581 {
582 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( parentNode );
583 if ( QgsMapLayer *layer = nodeLayer->layer() )
584 {
585 const int widgetsCount = layer->customProperty( u"embeddedWidgets/count"_s, 0 ).toInt();
586 QList<QgsLayerTreeModelLegendNode *> legendNodes = layerModel->layerLegendNodes( nodeLayer, true );
587 for ( int i = 0; i < widgetsCount; ++i )
588 {
589 const QString providerId = layer->customProperty( u"embeddedWidgets/%1/id"_s.arg( i ) ).toString();
590 if ( QgsLayerTreeEmbeddedWidgetProvider *provider = QgsGui::layerTreeEmbeddedWidgetRegistry()->provider( providerId ) )
591 {
592 const QModelIndex index = legendNode2index( legendNodes[i] );
593 QWidget *wdgt = provider->createWidget( layer, i );
594 // Since column is resized to contents, limit the expanded width of embedded
595 // widgets, if they are not already limited, e.g. have the default MAX value.
596 // Else, embedded widget may grow very wide due to large legend graphics.
597 // NOTE: This approach DOES NOT work right. It causes horizontal scroll
598 // bar to disappear if the embedded widget is expanded and part
599 // of the last layer in the panel, even if much wider legend items
600 // are expanded above it. The correct width-limiting method should
601 // be setting fixed-width, hidpi-aware embedded widget items in a
602 // layout and appending an expanding QSpacerItem to end. This ensures
603 // full width is always created in the column by the embedded widget.
604 // See QgsLayerTreeOpacityWidget
605 //if ( wdgt->maximumWidth() == QWIDGETSIZE_MAX )
606 //{
607 // wdgt->setMaximumWidth( 250 );
608 //}
609
610 setIndexWidget( index, wdgt );
611 }
612 }
613 }
614 }
615
616
617 if ( QgsLayerTree::isLayer( parentNode ) )
618 {
619 // if ShowLegendAsTree flag is enabled in model, we may need to expand some legend nodes
620 const QStringList expandedNodeKeys = parentNode->customProperty( u"expandedLegendNodes"_s ).toStringList();
621 if ( expandedNodeKeys.isEmpty() )
622 return;
623
624 const auto constLayerLegendNodes = layerModel->layerLegendNodes( QgsLayerTree::toLayer( parentNode ), true );
625 for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
626 {
627 const QString ruleKey = legendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
628 if ( expandedNodeKeys.contains( ruleKey ) )
629 setExpanded( legendNode2index( legendNode ), true );
630 }
631 return;
632 }
633
634 QList<QgsLayerTreeNode *> children = parentNode->children();
635 for ( int i = start; i <= end && i < children.count(); ++i )
636 {
637 updateExpandedStateFromNode( children[i] );
638 }
639
640 // make sure we still have correct current layer
642}
643
645{
646 // make sure we still have correct current layer
648}
649
651{
652 QgsLayerTreeModel *layerModel = layerTreeModel();
653 if ( !layerModel )
654 return;
655
656 QgsMapLayer *layerCurrent = layerForIndex( currentIndex() );
657 const QString layerCurrentID = layerCurrent ? layerCurrent->id() : QString();
658 if ( mCurrentLayerID == layerCurrentID )
659 return;
660
661 // update the current index in model (the item will be underlined)
662 QModelIndex proxyModelNodeLayerIndex;
663 if ( layerCurrent )
664 {
665 QgsLayerTreeLayer *nodeLayer = layerModel->rootGroup()->findLayer( layerCurrentID );
666 if ( nodeLayer )
667 proxyModelNodeLayerIndex = node2index( nodeLayer );
668 }
669
670 if ( !proxyModelNodeLayerIndex.isValid() )
671 {
672 mCurrentLayerID = QString();
673 layerModel->setCurrentIndex( QModelIndex() );
674 }
675 else
676 {
677 mCurrentLayerID = layerCurrentID;
678 layerModel->setCurrentIndex( mProxyModel->mapToSource( proxyModelNodeLayerIndex ) );
679 }
680
681 //checkModel();
682
683 emit currentLayerChanged( layerCurrent );
684}
685
686void QgsLayerTreeView::onCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key )
687{
688 QgsLayerTreeModel *layerModel = layerTreeModel();
689 if ( key != "expandedLegendNodes"_L1 || !QgsLayerTree::isLayer( node ) || !layerModel )
690 return;
691
692 const QSet<QString> expandedLegendNodes = qgis::listToSet( node->customProperty( u"expandedLegendNodes"_s ).toStringList() );
693
694 const QList<QgsLayerTreeModelLegendNode *> legendNodes = layerModel->layerLegendNodes( QgsLayerTree::toLayer( node ), true );
695 for ( QgsLayerTreeModelLegendNode *legendNode : legendNodes )
696 {
697 const QString key = legendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
698 if ( !key.isEmpty() )
699 setExpanded( legendNode2index( legendNode ), expandedLegendNodes.contains( key ) );
700 }
701}
702
704{
705 if ( !mIndicators[node].contains( indicator ) )
706 {
707 mIndicators[node].append( indicator );
708 connect( indicator, &QgsLayerTreeViewIndicator::changed, this, [this] {
709 update();
710 viewport()->repaint();
711 } );
712 update();
713 viewport()->repaint(); //update() does not automatically trigger a repaint()
714 }
715}
716
718{
719 mIndicators[node].removeOne( indicator );
720 update();
721}
722
723QList<QgsLayerTreeViewIndicator *> QgsLayerTreeView::indicators( QgsLayerTreeNode *node ) const
724{
725 return mIndicators.value( node );
726}
727
729QStringList QgsLayerTreeView::viewOnlyCustomProperties()
730{
731 return QStringList() << u"expandedLegendNodes"_s;
732}
734
735void QgsLayerTreeView::refreshLayerSymbology( const QString &layerId )
736{
737 QgsLayerTreeModel *layerModel = layerTreeModel();
738 if ( !layerModel )
739 return;
740 QgsLayerTreeLayer *nodeLayer = layerModel->rootGroup()->findLayer( layerId );
741 if ( nodeLayer )
742 layerModel->refreshLayerLegend( nodeLayer );
743}
744
745
747{
748 if ( mMessageBar == messageBar )
749 return;
750
751 mMessageBar = messageBar;
752 QgsLayerTreeModel *layerModel = layerTreeModel();
753 if ( !layerModel )
754 return;
755
756 if ( mMessageBar )
757 connect( layerModel, &QgsLayerTreeModel::messageEmitted, this, [this]( const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info, int duration = 5 ) {
758 Q_UNUSED( duration )
759 mMessageBar->pushMessage( message, level );
760 } );
761}
762
764{
765 if ( !mProxyModel )
766 return;
767
768 mShowPrivateLayers = showPrivate;
769 mProxyModel->setShowPrivateLayers( showPrivate );
770}
771
773{
774 if ( !mProxyModel )
775 return;
776
777 mHideValidLayers = hideValid;
778 mProxyModel->setHideValidLayers( mHideValidLayers );
779}
780
782{
783 return mShowPrivateLayers;
784}
785
787{
788 return mHideValidLayers;
789}
790
791void QgsLayerTreeView::mouseReleaseEvent( QMouseEvent *event )
792{
793 QgsLayerTreeModel *layerModel = layerTreeModel();
794 if ( !layerModel )
795 return;
796
797 // we need to keep last mouse position in order to know whether to emit an indicator's clicked() signal
798 // (the item delegate needs to know which indicator has been clicked)
799 mLastReleaseMousePos = event->pos();
800
801 const QgsLayerTreeModel::Flags oldFlags = layerModel->flags();
802 if ( event->modifiers() & Qt::ControlModifier )
803 layerModel->setFlags( oldFlags | QgsLayerTreeModel::ActionHierarchical );
804 else
805 layerModel->setFlags( oldFlags & ~QgsLayerTreeModel::ActionHierarchical );
806 QTreeView::mouseReleaseEvent( event );
807 layerModel->setFlags( oldFlags );
808}
809
810void QgsLayerTreeView::keyPressEvent( QKeyEvent *event )
811{
812 if ( event->key() == Qt::Key_Space )
813 {
814 const QList<QgsLayerTreeNode *> constSelectedNodes = selectedNodes();
815
816 if ( !constSelectedNodes.isEmpty() )
817 {
818 const bool isFirstNodeChecked = constSelectedNodes[0]->itemVisibilityChecked();
819 for ( QgsLayerTreeNode *node : constSelectedNodes )
820 {
821 node->setItemVisibilityChecked( !isFirstNodeChecked );
822 }
823
824 // if we call the original keyPress handler, the current item will be checked to the original state yet again
825 return;
826 }
827 }
828
829 QgsLayerTreeModel *layerModel = layerTreeModel();
830 if ( !layerModel )
831 return;
832 const QgsLayerTreeModel::Flags oldFlags = layerModel->flags();
833 if ( event->modifiers() & Qt::ControlModifier )
834 layerModel->setFlags( oldFlags | QgsLayerTreeModel::ActionHierarchical );
835 else
836 layerModel->setFlags( oldFlags & ~QgsLayerTreeModel::ActionHierarchical );
837 QTreeView::keyPressEvent( event );
838 layerModel->setFlags( oldFlags );
839}
840
841void QgsLayerTreeView::dragEnterEvent( QDragEnterEvent *event )
842{
843 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.uri"_s ) )
844 {
845 // the mime data are coming from layer tree, so ignore that, do not import those layers again
846 if ( !event->mimeData()->hasFormat( u"application/qgis.layertreemodeldata"_s ) )
847 {
848 event->accept();
849 return;
850 }
851 }
852 QTreeView::dragEnterEvent( event );
853}
854
855void QgsLayerTreeView::dragMoveEvent( QDragMoveEvent *event )
856{
857 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.uri"_s ) )
858 {
859 // the mime data are coming from layer tree, so ignore that, do not import those layers again
860 if ( !event->mimeData()->hasFormat( u"application/qgis.layertreemodeldata"_s ) )
861 {
862 event->accept();
863 return;
864 }
865 }
866 QTreeView::dragMoveEvent( event );
867}
868
869void QgsLayerTreeView::dropEvent( QDropEvent *event )
870{
871 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( u"application/x-vnd.qgis.qgis.uri"_s ) )
872 {
873 // the mime data are coming from layer tree, so ignore that, do not import those layers again
874 if ( !event->mimeData()->hasFormat( u"application/qgis.layertreemodeldata"_s ) )
875 {
876 event->accept();
877
878 QModelIndex index = indexAt( event->pos() );
879 if ( index.isValid() )
880 {
881 setCurrentIndex( index );
882 }
883
884 emit datasetsDropped( event );
885 return;
886 }
887 }
888 if ( event->keyboardModifiers() & Qt::AltModifier || event->keyboardModifiers() & Qt::ControlModifier )
889 {
890 event->accept();
891 }
892 QTreeView::dropEvent( event );
893}
894
895void QgsLayerTreeView::resizeEvent( QResizeEvent *event )
896{
897 // Since last column is resized to content (instead of stretched), the active
898 // selection rectangle ends at width of widest visible item in tree,
899 // regardless of which item is selected. This causes layer indicators to
900 // become 'inactive' (not clickable and no tool tip) unless their rectangle
901 // enters the view item's selection (active) rectangle.
902 // Always resetting the minimum section size relative to the viewport ensures
903 // the view item's selection rectangle extends to the right edge of the
904 // viewport, which allows indicators to become active again.
905 header()->setMinimumSectionSize( viewport()->width() );
906 QTreeView::resizeEvent( event );
907}
908
909void QgsLayerTreeView::onHorizontalScroll( int value )
910{
911 Q_UNUSED( value )
912 viewport()->update();
913}
914
915void QgsLayerTreeView::onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
916{
917 Q_UNUSED( topLeft )
918 Q_UNUSED( bottomRight )
919
920 // If an item is resized asynchronously (e.g. wms legend)
921 // The items below will need to be shifted vertically.
922 // This doesn't happen automatically, unless the viewport update is triggered.
923
924 if ( roles.contains( Qt::SizeHintRole ) )
925 viewport()->update();
926
927 //checkModel();
928}
929
930#if 0
931// for model debugging
932void QgsLayerTreeView::checkModel()
933{
934 std::function<void( QgsLayerTreeNode *, int )> debug;
935 debug = [ & ]( QgsLayerTreeNode * node, int depth )
936 {
937 if ( depth == 1 )
938 qDebug() << "----------------------------------------------";
939
940 qDebug() << depth << node->name() << node2index( node ) << layerTreeModel()->rowCount( node2sourceIndex( node ) ) << mProxyModel->rowCount( node2index( node ) );
941 Q_ASSERT( node == index2node( node2index( node ) ) );
942 Q_ASSERT( node == layerTreeModel()->index2node( node2sourceIndex( node ) ) );
943 Q_ASSERT( layerTreeModel()->rowCount( node2sourceIndex( node ) ) == mProxyModel->rowCount( node2index( node ) ) );
944
945 for ( int i = 0; i < mProxyModel->rowCount( node2index( node ) ); i++ )
946 {
947 QgsLayerTreeNode *childNode { index2node( mProxyModel->index( i, 0, node2index( node ) ) ) };
948 if ( childNode )
949 debug( childNode, depth + 1 );
950 else
951 qDebug() << "Warning no child node!";
952 }
953 };
954 debug( layerTreeModel()->rootGroup(), 1 );
955}
956#endif
957
959{
960 return mProxyModel;
961}
962
964 : QSortFilterProxyModel( parent )
965 , mLayerTreeModel( treeModel )
966{
967 setSourceModel( treeModel );
968}
969
970void QgsLayerTreeProxyModel::setFilterText( const QString &filterText )
971{
972 if ( filterText == mFilterText )
973 return;
974
975 mFilterText = filterText;
976 invalidateFilter();
977}
978
979bool QgsLayerTreeProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
980{
981 QgsLayerTreeNode *node = mLayerTreeModel->index2node( mLayerTreeModel->index( sourceRow, 0, sourceParent ) );
982 return nodeShown( node );
983}
984
986{
987 if ( !node )
988 return true;
989
990 if ( node->nodeType() == QgsLayerTreeNode::NodeGroup )
991 {
992 return true;
993 }
994 else
995 {
996 QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer();
997 if ( !layer )
998 return true;
999 if ( !mFilterText.isEmpty() && !layer->name().contains( mFilterText, Qt::CaseInsensitive ) )
1000 return false;
1001 if ( !mShowPrivateLayers && layer->flags().testFlag( QgsMapLayer::LayerFlag::Private ) )
1002 {
1003 return false;
1004 }
1005 if ( mHideValidLayers && layer->isValid() )
1006 return false;
1007
1008 return true;
1009 }
1010}
1011
1013{
1014 return mShowPrivateLayers;
1015}
1016
1018{
1019 if ( showPrivate == mShowPrivateLayers )
1020 return;
1021
1022 mShowPrivateLayers = showPrivate;
1023 invalidateFilter();
1024}
1025
1027{
1028 return mHideValidLayers;
1029}
1030
1032{
1033 if ( hideValid == mHideValidLayers )
1034 return;
1035
1036 mHideValidLayers = hideValid;
1037 invalidateFilter();
1038}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:159
@ Info
Information message.
Definition qgis.h:160
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6499
static QgsLayerTreeEmbeddedWidgetRegistry * layerTreeEmbeddedWidgetRegistry()
Returns the global layer tree embedded widget registry, used for registering widgets that may be embe...
Definition qgsgui.cpp:144
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.
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)....
void addTargetScreenProperties(const QgsScreenProperties &properties)
Adds additional target screen properties to use when generating icons for Qt::DecorationRole data.
Base class for nodes in a layer tree.
@ NodeGroup
Container of other groups and layers.
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.
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 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.
QgsLayerTreeViewMenuProvider * mMenuProvider
Context menu provider. Owned 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.
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.
Stores properties relating to a screen.