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