From 1fbddb214df01e9e01e8dfeee04b23325ea144ce Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 18 Aug 2025 10:58:21 +1000 Subject: [PATCH] [api] Make QgsLayerTreeView proxy handling more flexible Allow use of custom QgsLayerTreeProxyModel subclasses --- .../gui/auto_additions/qgslayertreeview.py | 11 +++++---- .../layertree/qgslayertreeview.sip.in | 20 ++++++++++++++++ python/gui/auto_additions/qgslayertreeview.py | 11 +++++---- .../layertree/qgslayertreeview.sip.in | 20 ++++++++++++++++ src/gui/layertree/qgslayertreeview.cpp | 14 +++++++---- src/gui/layertree/qgslayertreeview.h | 24 ++++++++++++++++--- tests/src/python/test_qgslayertreeview.py | 24 ++++++++++++++++++- 7 files changed, 106 insertions(+), 18 deletions(-) diff --git a/python/PyQt6/gui/auto_additions/qgslayertreeview.py b/python/PyQt6/gui/auto_additions/qgslayertreeview.py index d546c39f573..3c03becca57 100644 --- a/python/PyQt6/gui/auto_additions/qgslayertreeview.py +++ b/python/PyQt6/gui/auto_additions/qgslayertreeview.py @@ -7,12 +7,13 @@ try: except (NameError, AttributeError): pass try: - QgsLayerTreeViewMenuProvider.__abstract_methods__ = ['createContextMenu'] - QgsLayerTreeViewMenuProvider.__group__ = ['layertree'] -except (NameError, AttributeError): - pass -try: + QgsLayerTreeProxyModel.__virtual_methods__ = ['nodeShown'] QgsLayerTreeProxyModel.__overridden_methods__ = ['filterAcceptsRow'] QgsLayerTreeProxyModel.__group__ = ['layertree'] except (NameError, AttributeError): pass +try: + QgsLayerTreeViewMenuProvider.__abstract_methods__ = ['createContextMenu'] + QgsLayerTreeViewMenuProvider.__group__ = ['layertree'] +except (NameError, AttributeError): + pass diff --git a/python/PyQt6/gui/auto_generated/layertree/qgslayertreeview.sip.in b/python/PyQt6/gui/auto_generated/layertree/qgslayertreeview.sip.in index bc7e0b752c4..7889cdf56a5 100644 --- a/python/PyQt6/gui/auto_generated/layertree/qgslayertreeview.sip.in +++ b/python/PyQt6/gui/auto_generated/layertree/qgslayertreeview.sip.in @@ -75,6 +75,13 @@ shown). virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const; + virtual bool nodeShown( QgsLayerTreeNode *node ) const; +%Docstring +Returns ``True`` if the specified ``node`` should be shown. + +.. versionadded:: 4.0 +%End + }; @@ -118,6 +125,19 @@ Constructor for QgsLayerTreeView %Docstring Overridden :py:func:`~QgsLayerTreeView.setModel` from base class. Only :py:class:`QgsLayerTreeModel` is an acceptable model. + +.. note:: + + This method automatically creates a :py:class:`QgsLayerTreeProxyModel` to use as a proxy. +%End + + void setModel( QgsLayerTreeModel *model, QgsLayerTreeProxyModel *proxyModel ); +%Docstring +Sets the ``model`` and ``proxyModel`` for the view. + +Use this method when a custom proxy model is required. + +.. versionadded:: 4.0 %End QgsLayerTreeModel *layerTreeModel() const; diff --git a/python/gui/auto_additions/qgslayertreeview.py b/python/gui/auto_additions/qgslayertreeview.py index d546c39f573..3c03becca57 100644 --- a/python/gui/auto_additions/qgslayertreeview.py +++ b/python/gui/auto_additions/qgslayertreeview.py @@ -7,12 +7,13 @@ try: except (NameError, AttributeError): pass try: - QgsLayerTreeViewMenuProvider.__abstract_methods__ = ['createContextMenu'] - QgsLayerTreeViewMenuProvider.__group__ = ['layertree'] -except (NameError, AttributeError): - pass -try: + QgsLayerTreeProxyModel.__virtual_methods__ = ['nodeShown'] QgsLayerTreeProxyModel.__overridden_methods__ = ['filterAcceptsRow'] QgsLayerTreeProxyModel.__group__ = ['layertree'] except (NameError, AttributeError): pass +try: + QgsLayerTreeViewMenuProvider.__abstract_methods__ = ['createContextMenu'] + QgsLayerTreeViewMenuProvider.__group__ = ['layertree'] +except (NameError, AttributeError): + pass diff --git a/python/gui/auto_generated/layertree/qgslayertreeview.sip.in b/python/gui/auto_generated/layertree/qgslayertreeview.sip.in index bc7e0b752c4..7889cdf56a5 100644 --- a/python/gui/auto_generated/layertree/qgslayertreeview.sip.in +++ b/python/gui/auto_generated/layertree/qgslayertreeview.sip.in @@ -75,6 +75,13 @@ shown). virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const; + virtual bool nodeShown( QgsLayerTreeNode *node ) const; +%Docstring +Returns ``True`` if the specified ``node`` should be shown. + +.. versionadded:: 4.0 +%End + }; @@ -118,6 +125,19 @@ Constructor for QgsLayerTreeView %Docstring Overridden :py:func:`~QgsLayerTreeView.setModel` from base class. Only :py:class:`QgsLayerTreeModel` is an acceptable model. + +.. note:: + + This method automatically creates a :py:class:`QgsLayerTreeProxyModel` to use as a proxy. +%End + + void setModel( QgsLayerTreeModel *model, QgsLayerTreeProxyModel *proxyModel ); +%Docstring +Sets the ``model`` and ``proxyModel`` for the view. + +Use this method when a custom proxy model is required. + +.. versionadded:: 4.0 %End QgsLayerTreeModel *layerTreeModel() const; diff --git a/src/gui/layertree/qgslayertreeview.cpp b/src/gui/layertree/qgslayertreeview.cpp index 23717f36f7b..374154ae13d 100644 --- a/src/gui/layertree/qgslayertreeview.cpp +++ b/src/gui/layertree/qgslayertreeview.cpp @@ -91,6 +91,15 @@ void QgsLayerTreeView::setModel( QAbstractItemModel *model ) if ( !treeModel ) return; + auto proxyModel = new QgsLayerTreeProxyModel( treeModel, this ); + proxyModel->setShowPrivateLayers( mShowPrivateLayers ); + proxyModel->setHideValidLayers( mHideValidLayers ); + + setModel( treeModel, proxyModel ); +} + +void QgsLayerTreeView::setModel( QgsLayerTreeModel *treeModel, QgsLayerTreeProxyModel *proxyModel ) +{ if ( mMessageBar ) connect( treeModel, &QgsLayerTreeModel::messageEmitted, this, [this]( const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info, int duration = 5 ) { Q_UNUSED( duration ) @@ -99,8 +108,7 @@ void QgsLayerTreeView::setModel( QAbstractItemModel *model ) treeModel->addTargetScreenProperties( QgsScreenProperties( screen() ) ); - mProxyModel = new QgsLayerTreeProxyModel( treeModel, this ); - + mProxyModel = proxyModel; connect( mProxyModel, &QAbstractItemModel::rowsInserted, this, &QgsLayerTreeView::modelRowsInserted ); connect( mProxyModel, &QAbstractItemModel::rowsRemoved, this, &QgsLayerTreeView::modelRowsRemoved ); @@ -108,8 +116,6 @@ void QgsLayerTreeView::setModel( QAbstractItemModel *model ) new ModelTest( mProxyModel, this ); #endif - mProxyModel->setShowPrivateLayers( mShowPrivateLayers ); - mProxyModel->setHideValidLayers( mHideValidLayers ); QTreeView::setModel( mProxyModel ); connect( treeModel->rootGroup(), &QgsLayerTreeNode::expandedChanged, this, &QgsLayerTreeView::onExpandedChanged ); diff --git a/src/gui/layertree/qgslayertreeview.h b/src/gui/layertree/qgslayertreeview.h index b6391e1c50c..0d438160519 100644 --- a/src/gui/layertree/qgslayertreeview.h +++ b/src/gui/layertree/qgslayertreeview.h @@ -96,9 +96,14 @@ class GUI_EXPORT QgsLayerTreeProxyModel : public QSortFilterProxyModel protected: bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override; - private: - bool nodeShown( QgsLayerTreeNode *node ) const; + /** + * Returns TRUE if the specified \a node should be shown. + * + * \since QGIS 4.0 + */ + virtual bool nodeShown( QgsLayerTreeNode *node ) const; + private: QgsLayerTreeModel *mLayerTreeModel = nullptr; QString mFilterText; bool mShowPrivateLayers = false; @@ -138,9 +143,22 @@ class GUI_EXPORT QgsLayerTreeView : public QTreeView explicit QgsLayerTreeView( QWidget *parent SIP_TRANSFERTHIS = nullptr ); ~QgsLayerTreeView() override; - //! Overridden setModel() from base class. Only QgsLayerTreeModel is an acceptable model. + /** + * Overridden setModel() from base class. Only QgsLayerTreeModel is an acceptable model. + * + * \note This method automatically creates a QgsLayerTreeProxyModel to use as a proxy. + */ void setModel( QAbstractItemModel *model ) override; + /** + * Sets the \a model and \a proxyModel for the view. + * + * Use this method when a custom proxy model is required. + * + * \since QGIS 4.0 + */ + void setModel( QgsLayerTreeModel *model, QgsLayerTreeProxyModel *proxyModel ); + //! Gets access to the model casted to QgsLayerTreeModel QgsLayerTreeModel *layerTreeModel() const; diff --git a/tests/src/python/test_qgslayertreeview.py b/tests/src/python/test_qgslayertreeview.py index a3e993bf12a..a920154787d 100644 --- a/tests/src/python/test_qgslayertreeview.py +++ b/tests/src/python/test_qgslayertreeview.py @@ -22,7 +22,11 @@ from qgis.core import ( QgsMarkerSymbol, QgsMapLayerLegend, ) -from qgis.gui import QgsLayerTreeView, QgsLayerTreeViewDefaultActions +from qgis.gui import ( + QgsLayerTreeView, + QgsLayerTreeViewDefaultActions, + QgsLayerTreeProxyModel, +) import unittest from qgis.testing import start_app, QgisTestCase @@ -814,6 +818,24 @@ class TestQgsLayerTreeView(QgisTestCase): view.selectedLegendNodes(), [legend_nodes[0], legend_nodes[2]] ) + def test_set_model_and_proxy(self): + root = QgsLayerTree() + model = QgsLayerTreeModel(root) + view = QgsLayerTreeView() + + view.setModel(model) + self.assertEqual(view.layerTreeModel(), model) + # a proxy should have been auto-created + self.assertIsInstance(view.model(), QgsLayerTreeProxyModel) + + # set an explicit proxy + root2 = QgsLayerTree() + model2 = QgsLayerTreeModel(root2) + my_proxy = QgsLayerTreeProxyModel(model2, None) + view.setModel(model2, my_proxy) + self.assertEqual(view.layerTreeModel(), model2) + self.assertEqual(view.model(), my_proxy) + if __name__ == "__main__": unittest.main()