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