QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 "qgslayertree.h"
20#include "qgslayertreemodel.h"
22#include "qgslayertreeutils.h"
24#include "qgsmaplayer.h"
25#include "qgsmessagebar.h"
26
27#include "qgsgui.h"
28
29#include <QApplication>
30#include <QMenu>
31#include <QContextMenuEvent>
32#include <QHeaderView>
33#include <QMimeData>
34#include <QScrollBar>
35
36#ifdef ENABLE_MODELTEST
37#include "modeltest.h"
38#endif
39
42
43
45 : QTreeView( parent )
46 , mBlockDoubleClickTimer( new QTimer( this ) )
47
48{
49 mBlockDoubleClickTimer->setSingleShot( true );
50 mBlockDoubleClickTimer->setInterval( QApplication::doubleClickInterval() );
51 setHeaderHidden( true );
52
53 setDragEnabled( true );
54 setAcceptDrops( true );
55 setDropIndicatorShown( true );
56 setEditTriggers( EditKeyPressed );
57 setExpandsOnDoubleClick( false ); // normally used for other actions
58
59 // Ensure legend graphics are scrollable
60 header()->setStretchLastSection( false );
61 header()->setSectionResizeMode( QHeaderView::ResizeToContents );
62
63 // If vertically scrolling by item, legend graphics can get clipped
64 setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
65
66 setSelectionMode( ExtendedSelection );
67 setDefaultDropAction( Qt::MoveAction );
68
69 // we need a custom item delegate in order to draw indicators
70 setItemDelegate( new QgsLayerTreeViewItemDelegate( this ) );
71 setStyle( new QgsLayerTreeViewProxyStyle( this ) );
72
73 setLayerMarkWidth( static_cast< int >( QFontMetricsF( font() ).horizontalAdvance( 'l' ) * Qgis::UI_SCALE_FACTOR ) );
74
75 connect( this, &QTreeView::collapsed, this, &QgsLayerTreeView::updateExpandedStateToNode );
76 connect( this, &QTreeView::expanded, this, &QgsLayerTreeView::updateExpandedStateToNode );
77
78 connect( horizontalScrollBar(), &QScrollBar::valueChanged, this, &QgsLayerTreeView::onHorizontalScroll );
79}
80
82{
83 delete mMenuProvider;
84 delete mBlockDoubleClickTimer;
85}
86
87void QgsLayerTreeView::setModel( QAbstractItemModel *model )
88{
89 QgsLayerTreeModel *treeModel = qobject_cast<QgsLayerTreeModel *>( model );
90 if ( !treeModel )
91 return;
92
93 if ( mMessageBar )
94 connect( treeModel, &QgsLayerTreeModel::messageEmitted, this,
95 [ = ]( const QString & message, Qgis::MessageLevel level = Qgis::MessageLevel::Info, int duration = 5 )
96 {
97 Q_UNUSED( duration )
98 mMessageBar->pushMessage( message, level );
99 }
100 );
101
102 treeModel->addTargetScreenProperties( QgsScreenProperties( screen() ) );
103
104 mProxyModel = new QgsLayerTreeProxyModel( treeModel, this );
105
106 connect( mProxyModel, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted );
107 connect( mProxyModel, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved );
108
109#ifdef ENABLE_MODELTEST
110 new ModelTest( mProxyModel, this );
111#endif
112
113 mProxyModel->setShowPrivateLayers( mShowPrivateLayers );
114 QTreeView::setModel( mProxyModel );
115
117 connect( treeModel->rootGroup(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayerTreeView::onCustomPropertyChanged );
118
119 connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayerTreeView::onCurrentChanged );
120
121 connect( treeModel, &QAbstractItemModel::modelReset, this, &QgsLayerTreeView::onModelReset );
122
123 connect( treeModel, &QAbstractItemModel::dataChanged, this, &QgsLayerTreeView::onDataChanged );
124
126
127 //checkModel();
128}
129
131{
132 return mProxyModel ? qobject_cast<QgsLayerTreeModel *>( mProxyModel->sourceModel() ) : nullptr;
133}
134
141
147
149{
150 return layerForIndex( currentIndex() );
151}
152
154{
155 if ( !layer )
156 {
157 setCurrentIndex( QModelIndex() );
158 return;
159 }
160
161 QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layer->id() );
162 if ( !nodeLayer )
163 return;
164
165 setCurrentIndex( node2index( nodeLayer ) );
166}
167
169{
170 if ( !layer )
171 return;
172 QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layer->id() );
173 if ( !nodeLayer )
174 return;
175 nodeLayer->setItemVisibilityChecked( visible );
176}
177
178void QgsLayerTreeView::contextMenuEvent( QContextMenuEvent *event )
179{
180 if ( !mMenuProvider )
181 return;
182
183 const QModelIndex idx = indexAt( event->pos() );
184 if ( !idx.isValid() )
185 setCurrentIndex( QModelIndex() );
186
187 QMenu *menu = mMenuProvider->createContextMenu();
188 if ( menu )
189 {
190 emit contextMenuAboutToShow( menu );
191
192 if ( menu->actions().count() != 0 )
193 menu->exec( mapToGlobal( event->pos() ) );
194 delete menu;
195 }
196}
197
198
199void QgsLayerTreeView::modelRowsInserted( const QModelIndex &index, int start, int end )
200{
201 QgsLayerTreeNode *parentNode = index2node( index );
202 if ( !parentNode )
203 return;
204
205 // Embedded widgets - replace placeholders in the model by actual widgets
207 {
208 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( parentNode );
209 if ( QgsMapLayer *layer = nodeLayer->layer() )
210 {
211 const int widgetsCount = layer->customProperty( QStringLiteral( "embeddedWidgets/count" ), 0 ).toInt();
212 QList<QgsLayerTreeModelLegendNode *> legendNodes = layerTreeModel()->layerLegendNodes( nodeLayer, true );
213 for ( int i = 0; i < widgetsCount; ++i )
214 {
215 const QString providerId = layer->customProperty( QStringLiteral( "embeddedWidgets/%1/id" ).arg( i ) ).toString();
216 if ( QgsLayerTreeEmbeddedWidgetProvider *provider = QgsGui::layerTreeEmbeddedWidgetRegistry()->provider( providerId ) )
217 {
218 const QModelIndex index = legendNode2index( legendNodes[i] );
219 QWidget *wdgt = provider->createWidget( layer, i );
220 // Since column is resized to contents, limit the expanded width of embedded
221 // widgets, if they are not already limited, e.g. have the default MAX value.
222 // Else, embedded widget may grow very wide due to large legend graphics.
223 // NOTE: This approach DOES NOT work right. It causes horizontal scroll
224 // bar to disappear if the embedded widget is expanded and part
225 // of the last layer in the panel, even if much wider legend items
226 // are expanded above it. The correct width-limiting method should
227 // be setting fixed-width, hidpi-aware embedded widget items in a
228 // layout and appending an expanding QSpacerItem to end. This ensures
229 // full width is always created in the column by the embedded widget.
230 // See QgsLayerTreeOpacityWidget
231 //if ( wdgt->maximumWidth() == QWIDGETSIZE_MAX )
232 //{
233 // wdgt->setMaximumWidth( 250 );
234 //}
235
236 setIndexWidget( index, wdgt );
237 }
238 }
239 }
240 }
241
242
243 if ( QgsLayerTree::isLayer( parentNode ) )
244 {
245 // if ShowLegendAsTree flag is enabled in model, we may need to expand some legend nodes
246 const QStringList expandedNodeKeys = parentNode->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
247 if ( expandedNodeKeys.isEmpty() )
248 return;
249
250 const auto constLayerLegendNodes = layerTreeModel()->layerLegendNodes( QgsLayerTree::toLayer( parentNode ), true );
251 for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
252 {
253 const QString ruleKey = legendNode->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
254 if ( expandedNodeKeys.contains( ruleKey ) )
255 setExpanded( legendNode2index( legendNode ), true );
256 }
257 return;
258 }
259
260 QList<QgsLayerTreeNode *> children = parentNode->children();
261 for ( int i = start; i <= end && i < children.count(); ++i )
262 {
263 updateExpandedStateFromNode( children[i] );
264 }
265
266 // make sure we still have correct current layer
268}
269
271{
272 // make sure we still have correct current layer
274}
275
276void QgsLayerTreeView::updateExpandedStateToNode( const QModelIndex &index )
277{
278 if ( QgsLayerTreeNode *node = index2node( index ) )
279 {
280 node->setExpanded( isExpanded( index ) );
281 }
282 else if ( QgsLayerTreeModelLegendNode *node = index2legendNode( index ) )
283 {
284 const QString ruleKey = node->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
285 QStringList lst = node->layerNode()->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList();
286 const bool expanded = isExpanded( index );
287 const bool isInList = lst.contains( ruleKey );
288 if ( expanded && !isInList )
289 {
290 lst.append( ruleKey );
291 node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
292 }
293 else if ( !expanded && isInList )
294 {
295 lst.removeAll( ruleKey );
296 node->layerNode()->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
297 }
298 }
299}
300
302{
303 QgsMapLayer *layerCurrent = layerForIndex( currentIndex() );
304 const QString layerCurrentID = layerCurrent ? layerCurrent->id() : QString();
305 if ( mCurrentLayerID == layerCurrentID )
306 return;
307
308 // update the current index in model (the item will be underlined)
309 QModelIndex proxyModelNodeLayerIndex;
310 if ( layerCurrent )
311 {
312 QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerCurrentID );
313 if ( nodeLayer )
314 proxyModelNodeLayerIndex = node2index( nodeLayer );
315 }
316
317 if ( ! proxyModelNodeLayerIndex.isValid() )
318 {
319 mCurrentLayerID = QString();
320 layerTreeModel()->setCurrentIndex( QModelIndex() );
321 }
322 else
323 {
324 mCurrentLayerID = layerCurrentID;
325 layerTreeModel()->setCurrentIndex( mProxyModel->mapToSource( proxyModelNodeLayerIndex ) );
326 }
327
328 //checkModel();
329
330 emit currentLayerChanged( layerCurrent );
331}
332
334{
335 const QModelIndex idx = node2index( node );
336 if ( isExpanded( idx ) != expanded )
337 setExpanded( idx, expanded );
338}
339
340void QgsLayerTreeView::onCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key )
341{
342 if ( key != QLatin1String( "expandedLegendNodes" ) || !QgsLayerTree::isLayer( node ) )
343 return;
344
345 const QSet<QString> expandedLegendNodes = qgis::listToSet( node->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList() );
346
347 const QList<QgsLayerTreeModelLegendNode *> legendNodes = layerTreeModel()->layerLegendNodes( QgsLayerTree::toLayer( node ), true );
348 for ( QgsLayerTreeModelLegendNode *legendNode : legendNodes )
349 {
350 const QString key = legendNode->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString();
351 if ( !key.isEmpty() )
352 setExpanded( legendNode2index( legendNode ), expandedLegendNodes.contains( key ) );
353 }
354}
355
357{
359 //checkModel();
360}
361
363{
364 const QModelIndex idx = node2index( node );
365 setExpanded( idx, node->isExpanded() );
366
367 const auto constChildren = node->children();
368 for ( QgsLayerTreeNode *child : constChildren )
370}
371
372QgsMapLayer *QgsLayerTreeView::layerForIndex( const QModelIndex &index ) const
373{
374 // Check if model has been set and index is valid
375 if ( layerTreeModel() && index.isValid() )
376 {
377 QgsLayerTreeNode *node = index2node( index );
378 if ( node )
379 {
380 if ( QgsLayerTree::isLayer( node ) )
381 return QgsLayerTree::toLayer( node )->layer();
382 }
383 else
384 {
385 // possibly a legend node
386 QgsLayerTreeModelLegendNode *legendNode = index2legendNode( index );
387 if ( legendNode )
388 return legendNode->layerNode()->layer();
389 }
390 }
391 return nullptr;
392}
393
395{
396 return index2node( selectionModel()->currentIndex() );
397}
398
400{
402 if ( QgsLayerTree::isGroup( node ) )
403 return QgsLayerTree::toGroup( node );
404 else if ( QgsLayerTree::isLayer( node ) )
405 {
406 QgsLayerTreeNode *parent = node->parent();
407 if ( QgsLayerTree::isGroup( parent ) )
408 return QgsLayerTree::toGroup( parent );
409 }
410
411 if ( QgsLayerTreeModelLegendNode *legendNode = index2legendNode( selectionModel()->currentIndex() ) )
412 {
413 QgsLayerTreeLayer *parent = legendNode->layerNode();
414 if ( QgsLayerTree::isGroup( parent->parent() ) )
415 return QgsLayerTree::toGroup( parent->parent() );
416 }
417
418 return nullptr;
419}
420
422{
423 return index2legendNode( selectionModel()->currentIndex() );
424}
425
426QList<QgsLayerTreeNode *> QgsLayerTreeView::selectedNodes( bool skipInternal ) const
427{
428 QModelIndexList mapped;
429 const QModelIndexList selected = selectionModel()->selectedIndexes();
430 mapped.reserve( selected.size() );
431 for ( const QModelIndex &index : selected )
432 mapped << mProxyModel->mapToSource( index );
433
434 return layerTreeModel()->indexes2nodes( mapped, skipInternal );
435}
436
437QList<QgsLayerTreeLayer *> QgsLayerTreeView::selectedLayerNodes() const
438{
439 QList<QgsLayerTreeLayer *> layerNodes;
440 const QList<QgsLayerTreeNode *> constSelectedNodes = selectedNodes();
441 layerNodes.reserve( constSelectedNodes.size() );
442 for ( QgsLayerTreeNode *node : constSelectedNodes )
443 {
444 if ( QgsLayerTree::isLayer( node ) )
445 layerNodes << QgsLayerTree::toLayer( node );
446 }
447 return layerNodes;
448}
449
450QList<QgsMapLayer *> QgsLayerTreeView::selectedLayers() const
451{
452 QList<QgsMapLayer *> list;
453 const QList<QgsLayerTreeLayer *> constSelectedLayerNodes = selectedLayerNodes();
454 list.reserve( constSelectedLayerNodes.size() );
455 for ( QgsLayerTreeLayer *node : constSelectedLayerNodes )
456 {
457 if ( node->layer() )
458 list << node->layer();
459 }
460 return list;
461}
462
463QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeView::selectedLegendNodes() const
464{
465 QList<QgsLayerTreeModelLegendNode *> res;
466 const QModelIndexList selected = selectionModel()->selectedIndexes();
467 res.reserve( selected.size() );
468 for ( const QModelIndex &index : selected )
469 {
470 const QModelIndex &modelIndex = mProxyModel->mapToSource( index );
471 if ( QgsLayerTreeModelLegendNode *node = layerTreeModel()->index2legendNode( modelIndex ) )
472 {
473 res.push_back( node );
474 }
475 }
476
477 return res;
478}
479
481{
482 QModelIndexList mapped;
483 const QModelIndexList selected = selectionModel()->selectedIndexes();
484 mapped.reserve( selected.size() );
485 for ( const QModelIndex &index : selected )
486 mapped << mProxyModel->mapToSource( index );
487
488 const QList<QgsLayerTreeNode *> nodes = layerTreeModel()->indexes2nodes( mapped, false );
489 const QSet<QgsMapLayer *> layersSet = QgsLayerTreeUtils::collectMapLayersRecursive( nodes );
490 return qgis::setToList( layersSet );
491}
492
494{
495 if ( !mIndicators[node].contains( indicator ) )
496 {
497 mIndicators[node].append( indicator );
498 connect( indicator, &QgsLayerTreeViewIndicator::changed, this, [ = ]
499 {
500 update();
501 viewport()->repaint();
502 } );
503 update();
504 viewport()->repaint(); //update() does not automatically trigger a repaint()
505 }
506}
507
509{
510 mIndicators[node].removeOne( indicator );
511 update();
512}
513
514QList<QgsLayerTreeViewIndicator *> QgsLayerTreeView::indicators( QgsLayerTreeNode *node ) const
515{
516 return mIndicators.value( node );
517}
518
520QStringList QgsLayerTreeView::viewOnlyCustomProperties()
521{
522 return QStringList() << QStringLiteral( "expandedLegendNodes" );
523}
525
526void QgsLayerTreeView::refreshLayerSymbology( const QString &layerId )
527{
528 QgsLayerTreeLayer *nodeLayer = layerTreeModel()->rootGroup()->findLayer( layerId );
529 if ( nodeLayer )
530 layerTreeModel()->refreshLayerLegend( nodeLayer );
531}
532
533
534static void _expandAllLegendNodes( QgsLayerTreeLayer *nodeLayer, bool expanded, QgsLayerTreeModel *model )
535{
536 // for layers we also need to find out with legend nodes contain some children and make them expanded/collapsed
537 // if we are collapsing, we just write out an empty list
538 QStringList lst;
539 if ( expanded )
540 {
541 const auto constLayerLegendNodes = model->layerLegendNodes( nodeLayer, true );
542 for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
543 {
544 const QString parentKey = legendNode->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::ParentRuleKey ) ).toString();
545 if ( !parentKey.isEmpty() && !lst.contains( parentKey ) )
546 lst << parentKey;
547 }
548 }
549 nodeLayer->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), lst );
550}
551
552
553static void _expandAllNodes( QgsLayerTreeGroup *parent, bool expanded, QgsLayerTreeModel *model )
554{
555 const auto constChildren = parent->children();
556 for ( QgsLayerTreeNode *node : constChildren )
557 {
558 node->setExpanded( expanded );
559 if ( QgsLayerTree::isGroup( node ) )
560 _expandAllNodes( QgsLayerTree::toGroup( node ), expanded, model );
561 else if ( QgsLayerTree::isLayer( node ) )
562 _expandAllLegendNodes( QgsLayerTree::toLayer( node ), expanded, model );
563 }
564}
565
566
568{
569 // unfortunately expandAll() does not emit expanded() signals
570 _expandAllNodes( layerTreeModel()->rootGroup(), true, layerTreeModel() );
571 expandAll();
572}
573
575{
576 // unfortunately collapseAll() does not emit collapsed() signals
577 _expandAllNodes( layerTreeModel()->rootGroup(), false, layerTreeModel() );
578 collapseAll();
579}
580
582{
583 if ( mMessageBar == messageBar )
584 return;
585
586 mMessageBar = messageBar;
587
588 if ( mMessageBar )
590 [ = ]( const QString & message, Qgis::MessageLevel level = Qgis::MessageLevel::Info, int duration = 5 )
591 {
592 Q_UNUSED( duration )
593 mMessageBar->pushMessage( message, level );
594 }
595 );
596}
597
599{
600 mShowPrivateLayers = showPrivate;
601 mProxyModel->setShowPrivateLayers( showPrivate );
602}
603
605{
606 return mShowPrivateLayers;
607}
608
610{
611 if ( mBlockDoubleClickTimer->isActive() )
612 event->accept();
613 else
614 QTreeView::mouseDoubleClickEvent( event );
615}
616
617void QgsLayerTreeView::mouseReleaseEvent( QMouseEvent *event )
618{
619 // we need to keep last mouse position in order to know whether to emit an indicator's clicked() signal
620 // (the item delegate needs to know which indicator has been clicked)
621 mLastReleaseMousePos = event->pos();
622
623 const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
624 if ( event->modifiers() & Qt::ControlModifier )
626 else
628 QTreeView::mouseReleaseEvent( event );
629 layerTreeModel()->setFlags( oldFlags );
630}
631
632void QgsLayerTreeView::keyPressEvent( QKeyEvent *event )
633{
634 if ( event->key() == Qt::Key_Space )
635 {
636 const QList<QgsLayerTreeNode *> constSelectedNodes = selectedNodes();
637
638 if ( !constSelectedNodes.isEmpty() )
639 {
640 const bool isFirstNodeChecked = constSelectedNodes[0]->itemVisibilityChecked();
641 for ( QgsLayerTreeNode *node : constSelectedNodes )
642 {
643 node->setItemVisibilityChecked( ! isFirstNodeChecked );
644 }
645
646 // if we call the original keyPress handler, the current item will be checked to the original state yet again
647 return;
648 }
649 }
650
651 const QgsLayerTreeModel::Flags oldFlags = layerTreeModel()->flags();
652 if ( event->modifiers() & Qt::ControlModifier )
654 else
656 QTreeView::keyPressEvent( event );
657 layerTreeModel()->setFlags( oldFlags );
658}
659
660void QgsLayerTreeView::dragEnterEvent( QDragEnterEvent *event )
661{
662 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.uri" ) ) )
663 {
664 // the mime data are coming from layer tree, so ignore that, do not import those layers again
665 if ( !event->mimeData()->hasFormat( QStringLiteral( "application/qgis.layertreemodeldata" ) ) )
666 {
667 event->accept();
668 return;
669 }
670 }
671 QTreeView::dragEnterEvent( event );
672}
673
674void QgsLayerTreeView::dragMoveEvent( QDragMoveEvent *event )
675{
676 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.uri" ) ) )
677 {
678 // the mime data are coming from layer tree, so ignore that, do not import those layers again
679 if ( !event->mimeData()->hasFormat( QStringLiteral( "application/qgis.layertreemodeldata" ) ) )
680 {
681 event->accept();
682 return;
683 }
684 }
685 QTreeView::dragMoveEvent( event );
686}
687
688void QgsLayerTreeView::dropEvent( QDropEvent *event )
689{
690 if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.uri" ) ) )
691 {
692 // the mime data are coming from layer tree, so ignore that, do not import those layers again
693 if ( !event->mimeData()->hasFormat( QStringLiteral( "application/qgis.layertreemodeldata" ) ) )
694 {
695 event->accept();
696
697 QModelIndex index = indexAt( event->pos() );
698 if ( index.isValid() )
699 {
700 setCurrentIndex( index );
701 }
702
703 emit datasetsDropped( event );
704 return;
705 }
706 }
707 if ( event->keyboardModifiers() & Qt::AltModifier || event->keyboardModifiers() & Qt::ControlModifier )
708 {
709 event->accept();
710 }
711 QTreeView::dropEvent( event );
712}
713
714void QgsLayerTreeView::resizeEvent( QResizeEvent *event )
715{
716 // Since last column is resized to content (instead of stretched), the active
717 // selection rectangle ends at width of widest visible item in tree,
718 // regardless of which item is selected. This causes layer indicators to
719 // become 'inactive' (not clickable and no tool tip) unless their rectangle
720 // enters the view item's selection (active) rectangle.
721 // Always resetting the minimum section size relative to the viewport ensures
722 // the view item's selection rectangle extends to the right edge of the
723 // viewport, which allows indicators to become active again.
724 header()->setMinimumSectionSize( viewport()->width() );
725 QTreeView::resizeEvent( event );
726}
727
728void QgsLayerTreeView::onHorizontalScroll( int value )
729{
730 Q_UNUSED( value )
731 viewport()->update();
732}
733
734void QgsLayerTreeView::onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
735{
736 Q_UNUSED( topLeft )
737 Q_UNUSED( bottomRight )
738
739 // If an item is resized asynchronously (e.g. wms legend)
740 // The items below will need to be shifted vertically.
741 // This doesn't happen automatically, unless the viewport update is triggered.
742
743 if ( roles.contains( Qt::SizeHintRole ) )
744 viewport()->update();
745
746 mBlockDoubleClickTimer->start();
747 //checkModel();
748}
749
750#if 0
751// for model debugging
752void QgsLayerTreeView::checkModel()
753{
754 std::function<void( QgsLayerTreeNode *, int )> debug;
755 debug = [ & ]( QgsLayerTreeNode * node, int depth )
756 {
757 if ( depth == 1 )
758 qDebug() << "----------------------------------------------";
759
760 qDebug() << depth << node->name() << node2index( node ) << layerTreeModel()->rowCount( node2sourceIndex( node ) ) << mProxyModel->rowCount( node2index( node ) );
761 Q_ASSERT( node == index2node( node2index( node ) ) );
762 Q_ASSERT( node == layerTreeModel()->index2node( node2sourceIndex( node ) ) );
763 Q_ASSERT( layerTreeModel()->rowCount( node2sourceIndex( node ) ) == mProxyModel->rowCount( node2index( node ) ) );
764
765 for ( int i = 0; i < mProxyModel->rowCount( node2index( node ) ); i++ )
766 {
767 QgsLayerTreeNode *childNode { index2node( mProxyModel->index( i, 0, node2index( node ) ) ) };
768 if ( childNode )
769 debug( childNode, depth + 1 );
770 else
771 qDebug() << "Warning no child node!";
772 }
773 };
774 debug( layerTreeModel()->rootGroup(), 1 );
775}
776#endif
777
779{
780 return mProxyModel;
781}
782
783QgsLayerTreeNode *QgsLayerTreeView::index2node( const QModelIndex &index ) const
784{
785 return layerTreeModel()->index2node( mProxyModel->mapToSource( index ) );
786}
787
789{
790 return mProxyModel->mapFromSource( node2sourceIndex( node ) );
791}
792
794{
795 return layerTreeModel()->node2index( node );
796}
797
799{
800 return QgsLayerTreeModel::index2legendNode( mProxyModel->mapToSource( index ) );
801}
802
804{
805 return mProxyModel->mapFromSource( legendNode2sourceIndex( legendNode ) );
806}
807
809{
810 return layerTreeModel()->legendNode2index( legendNode );
811}
812
814 : QSortFilterProxyModel( parent )
815 , mLayerTreeModel( treeModel )
816{
817 setSourceModel( treeModel );
818}
819
820void QgsLayerTreeProxyModel::setFilterText( const QString &filterText )
821{
822 if ( filterText == mFilterText )
823 return;
824
825 mFilterText = filterText;
826 invalidateFilter();
827}
828
829bool QgsLayerTreeProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
830{
831 QgsLayerTreeNode *node = mLayerTreeModel->index2node( mLayerTreeModel->index( sourceRow, 0, sourceParent ) );
832 return nodeShown( node );
833}
834
835bool QgsLayerTreeProxyModel::nodeShown( QgsLayerTreeNode *node ) const
836{
837 if ( !node )
838 return true;
839
840 if ( node->nodeType() == QgsLayerTreeNode::NodeGroup )
841 {
842 return true;
843 }
844 else
845 {
846 QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer();
847 if ( !layer )
848 return true;
849 if ( !mFilterText.isEmpty() && !layer->name().contains( mFilterText, Qt::CaseInsensitive ) )
850 return false;
851 if ( ! mShowPrivateLayers && layer->flags().testFlag( QgsMapLayer::LayerFlag::Private ) )
852 {
853 return false;
854 }
855 return true;
856 }
857}
858
860{
861 return mShowPrivateLayers;
862}
863
865{
866 mShowPrivateLayers = showPrivate;
867 invalidateFilter();
868}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:99
@ Info
Information message.
Definition qgis.h:100
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:4865
static QgsLayerTreeEmbeddedWidgetRegistry * layerTreeEmbeddedWidgetRegistry()
Returns the global layer tree embedded widget registry, used for registering widgets that may be embe...
Definition qgsgui.cpp:124
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.
@ 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.
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.
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.
QHash< QgsLayerTreeNode *, QList< QgsLayerTreeViewIndicator * > > mIndicators
Storage of indicators used with the tree view.
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.
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
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.
bool showPrivateLayers()
Returns the show private layers status.
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 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.
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:75
QString name
Definition qgsmaplayer.h:78
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
@ 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.