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