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