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