Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add QgsProviderSublayerProxyModel for filtering/sorting
QgsProviderSublayerModel instances
  • Loading branch information
nyalldawson committed Jul 6, 2021
1 parent 73f45ee commit 129ab50
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 9 deletions.
Expand Up @@ -14,6 +14,15 @@ class QgsProviderSublayerModel: QAbstractItemModel
{
%Docstring(signature="appended")

A model for representing the sublayers present in a URI.

:py:class:`QgsProviderSublayerModel` is designed to present a tree view of the sublayers
available for a URI, including any vector, raster or mesh sublayers present.

Additionally, :py:class:`QgsProviderSublayerModel` can include some non-sublayer items,
e.g. in order to represent other content available for a URI, such as
embedded project items. The non-sublayer items can be added by calling
:py:func:`~addNonLayerItem`.

.. versionadded:: 3.22
%End
Expand Down Expand Up @@ -49,6 +58,7 @@ class QgsProviderSublayerModel: QAbstractItemModel
{
%Docstring(signature="appended")

Contains details for a non-sublayer item to include in a QgsProviderSublayerModel.

.. versionadded:: 3.22
%End
Expand Down Expand Up @@ -169,6 +179,49 @@ Adds a non-layer item (e.g. an embedded QGIS project item) to the model.

};


class QgsProviderSublayerProxyModel: QSortFilterProxyModel
{
%Docstring(signature="appended")

A QSortFilterProxyModel for filtering and sorting a QgsProviderSublayerModel.

.. versionadded:: 3.22
%End

%TypeHeaderCode
#include "qgsprovidersublayermodel.h"
%End
public:

QgsProviderSublayerProxyModel( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsProviderSublayerProxyModel, with the specified ``parent`` object.
%End

QString filterString() const;
%Docstring
Returns the filter string used for filtering items in the model.

.. seealso:: :py:func:`setFilterString`
%End

void setFilterString( const QString &filter );
%Docstring
Sets the ``filter`` string used for filtering items in the model.

.. seealso:: :py:func:`filterString`
%End

protected:
virtual bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const;

virtual bool lessThan( const QModelIndex &source_left, const QModelIndex &source_right ) const;


};


/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
54 changes: 54 additions & 0 deletions src/core/providers/qgsprovidersublayermodel.cpp
Expand Up @@ -348,3 +348,57 @@ void QgsProviderSublayerModel::NonLayerItem::setIcon( const QIcon &icon )
{
mIcon = icon;
}

//
// QgsProviderSublayerProxyModel
//

QgsProviderSublayerProxyModel::QgsProviderSublayerProxyModel( QObject *parent )
: QSortFilterProxyModel( parent )
{
setDynamicSortFilter( true );
sort( 0 );
}

bool QgsProviderSublayerProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
{
const QModelIndex sourceIndex = sourceModel()->index( source_row, 0, source_parent );

if ( mFilterString.trimmed().isEmpty() )
return true;

if ( sourceModel()->data( sourceIndex, static_cast< int >( QgsProviderSublayerModel::Role::Name ) ).toString().contains( mFilterString, Qt::CaseInsensitive ) )
return true;

if ( sourceModel()->data( sourceIndex, static_cast< int >( QgsProviderSublayerModel::Role::Description ) ).toString().contains( mFilterString, Qt::CaseInsensitive ) )
return true;

return false;
}

bool QgsProviderSublayerProxyModel::lessThan( const QModelIndex &source_left, const QModelIndex &source_right ) const
{
const bool leftIsNonLayer = sourceModel()->data( source_left, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool();
const bool rightIsNonLayer = sourceModel()->data( source_right, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool();

if ( leftIsNonLayer && !rightIsNonLayer )
return true;
else if ( rightIsNonLayer && !leftIsNonLayer )
return false;

const QString leftName = sourceModel()->data( source_left, static_cast< int >( QgsProviderSublayerModel::Role::Name ) ).toString();
const QString rightName = sourceModel()->data( source_right, static_cast< int >( QgsProviderSublayerModel::Role::Name ) ).toString();

return leftName.compare( rightName, Qt::CaseInsensitive );
}

QString QgsProviderSublayerProxyModel::filterString() const
{
return mFilterString;
}

void QgsProviderSublayerProxyModel::setFilterString( const QString &filter )
{
mFilterString = filter;
invalidateFilter();
}
59 changes: 56 additions & 3 deletions src/core/providers/qgsprovidersublayermodel.h
Expand Up @@ -20,14 +20,23 @@

#include "qgstaskmanager.h"
#include <QAbstractItemModel>
#include <QSortFilterProxyModel>

class QgsProviderSublayerDetails;

/**
* \ingroup core
*
* \brief
* \brief A model for representing the sublayers present in a URI.
*
* QgsProviderSublayerModel is designed to present a tree view of the sublayers
* available for a URI, including any vector, raster or mesh sublayers present.
*
* Additionally, QgsProviderSublayerModel can include some non-sublayer items,
* e.g. in order to represent other content available for a URI, such as
* embedded project items. The non-sublayer items can be added by calling
* addNonLayerItem().
*
* \since QGIS 3.22
*/
class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel
Expand Down Expand Up @@ -63,7 +72,7 @@ class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel
/**
* \ingroup core
*
* \brief
* \brief Contains details for a non-sublayer item to include in a QgsProviderSublayerModel.
* \since QGIS 3.22
*/
Expand Down Expand Up @@ -179,4 +188,48 @@ class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel

};


/**
* \ingroup core
*
* \brief A QSortFilterProxyModel for filtering and sorting a QgsProviderSublayerModel.
*
* \since QGIS 3.22
*/
class CORE_EXPORT QgsProviderSublayerProxyModel: public QSortFilterProxyModel
{
Q_OBJECT

public:

/**
* Constructor for QgsProviderSublayerProxyModel, with the specified \a parent object.
*/
QgsProviderSublayerProxyModel( QObject *parent SIP_TRANSFERTHIS = nullptr );

/**
* Returns the filter string used for filtering items in the model.
*
* \see setFilterString()
*/
QString filterString() const;

/**
* Sets the \a filter string used for filtering items in the model.
*
* \see filterString()
*/
void setFilterString( const QString &filter );

protected:
bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override;
bool lessThan( const QModelIndex &source_left, const QModelIndex &source_right ) const override;

private:

QString mFilterString;

};


#endif // QGSPROVIDERSUBLAYERMODEL_H
93 changes: 87 additions & 6 deletions tests/src/python/test_qgsprovidersublayermodel.py
Expand Up @@ -11,24 +11,19 @@
__copyright__ = 'Copyright 2020, The QGIS Project'

import qgis # NOQA
import os

from qgis.core import (
QgsProviderRegistry,
QgsMapLayerType,
QgsWkbTypes,
QgsProviderSublayerDetails,
Qgis,
QgsCoordinateTransformContext,
QgsVectorLayer,
QgsProviderSublayerProxyModel,
QgsProviderSublayerModel
)
from qgis.PyQt.QtCore import (
Qt,
QModelIndex
)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath

# Convenience instances in case you may need them
# to find the srs.db
Expand Down Expand Up @@ -239,6 +234,92 @@ def test_model_with_non_layer_items(self):
self.assertEqual(model.data(model.index(2, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), True)
self.assertEqual(model.data(model.index(2, 0), QgsProviderSublayerModel.Role.NonLayerItemType), 'item type 2')

def test_proxy(self):
"""
Test QgsProviderSublayerProxyModel
"""
model = QgsProviderSublayerModel()
proxy = QgsProviderSublayerProxyModel()
proxy.setSourceModel(model)
self.assertEqual(model.rowCount(QModelIndex()), 0)
self.assertEqual(proxy.columnCount(QModelIndex()), 2)
self.assertEqual(proxy.headerData(0, Qt.Horizontal, Qt.DisplayRole), 'Layer')
self.assertEqual(proxy.headerData(0, Qt.Horizontal, Qt.ToolTipRole), 'Layer')
self.assertEqual(proxy.headerData(1, Qt.Horizontal, Qt.DisplayRole), 'Description')
self.assertEqual(proxy.headerData(1, Qt.Horizontal, Qt.ToolTipRole), 'Description')

layer1 = QgsProviderSublayerDetails()
layer1.setType(QgsMapLayerType.RasterLayer)
layer1.setName('layer 1')
layer1.setDescription('description 1')
layer1.setProviderKey('gdal')
layer1.setUri('uri 1')

layer2 = QgsProviderSublayerDetails()
layer2.setType(QgsMapLayerType.VectorLayer)
layer2.setName('another layer 2')
layer2.setDescription('description 2')
layer2.setProviderKey('ogr')
layer2.setUri('uri 2')
layer2.setFeatureCount(-1)
layer2.setWkbType(QgsWkbTypes.LineString)

model.setSublayerDetails([layer1, layer2])

item1 = QgsProviderSublayerModel.NonLayerItem()
item1.setUri('item uri 1')
item1.setName('item name 1')
item1.setType('item type 1')
item1.setDescription('item desc 1')

model.addNonLayerItem(item1)

self.assertEqual(proxy.rowCount(QModelIndex()), 3)

self.assertEqual(proxy.data(proxy.index(0, 0), Qt.DisplayRole), 'item name 1')
self.assertEqual(proxy.data(proxy.index(0, 1), Qt.DisplayRole), 'item desc 1')
self.assertEqual(proxy.data(proxy.index(0, 0), QgsProviderSublayerModel.Role.Uri), 'item uri 1')
self.assertEqual(proxy.data(proxy.index(0, 0), QgsProviderSublayerModel.Role.Name), 'item name 1')
self.assertEqual(proxy.data(proxy.index(0, 0), QgsProviderSublayerModel.Role.Description), 'item desc 1')
self.assertEqual(proxy.data(proxy.index(0, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), True)

self.assertEqual(proxy.data(proxy.index(1, 0), Qt.DisplayRole), 'another layer 2')
self.assertEqual(proxy.data(proxy.index(1, 1), Qt.DisplayRole), 'description 2 - LineString (Uncounted)')
self.assertEqual(proxy.data(proxy.index(1, 0), QgsProviderSublayerModel.Role.ProviderKey), 'ogr')
self.assertEqual(proxy.data(proxy.index(1, 0), QgsProviderSublayerModel.Role.LayerType), QgsMapLayerType.VectorLayer)
self.assertEqual(proxy.data(proxy.index(1, 0), QgsProviderSublayerModel.Role.Uri), 'uri 2')
self.assertEqual(proxy.data(proxy.index(1, 0), QgsProviderSublayerModel.Role.Name), 'another layer 2')
self.assertEqual(proxy.data(proxy.index(1, 0), QgsProviderSublayerModel.Role.Description), 'description 2')
self.assertEqual(proxy.data(proxy.index(1, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), False)

self.assertEqual(proxy.data(proxy.index(2, 0), Qt.DisplayRole), 'layer 1')
self.assertEqual(proxy.data(proxy.index(2, 1), Qt.DisplayRole), 'description 1')
self.assertEqual(proxy.data(proxy.index(2, 0), QgsProviderSublayerModel.Role.ProviderKey), 'gdal')
self.assertEqual(proxy.data(proxy.index(2, 0), QgsProviderSublayerModel.Role.LayerType), QgsMapLayerType.RasterLayer)
self.assertEqual(proxy.data(proxy.index(2, 0), QgsProviderSublayerModel.Role.Uri), 'uri 1')
self.assertEqual(proxy.data(proxy.index(2, 0), QgsProviderSublayerModel.Role.Name), 'layer 1')
self.assertEqual(proxy.data(proxy.index(2, 0), QgsProviderSublayerModel.Role.Description), 'description 1')
self.assertEqual(proxy.data(proxy.index(2, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), False)

proxy.setFilterString(' 1')
self.assertEqual(proxy.rowCount(QModelIndex()), 2)
self.assertEqual(proxy.data(proxy.index(0, 0), Qt.DisplayRole), 'item name 1')
self.assertEqual(proxy.data(proxy.index(1, 0), Qt.DisplayRole), 'layer 1')

proxy.setFilterString(' 2')
self.assertEqual(proxy.rowCount(QModelIndex()), 1)
self.assertEqual(proxy.data(proxy.index(0, 0), Qt.DisplayRole), 'another layer 2')

proxy.setFilterString('ITEM')
self.assertEqual(proxy.rowCount(QModelIndex()), 1)
self.assertEqual(proxy.data(proxy.index(0, 0), Qt.DisplayRole), 'item name 1')

proxy.setFilterString('')
self.assertEqual(proxy.rowCount(QModelIndex()), 3)
self.assertEqual(proxy.data(proxy.index(0, 0), Qt.DisplayRole), 'item name 1')
self.assertEqual(proxy.data(proxy.index(1, 0), Qt.DisplayRole), 'another layer 2')
self.assertEqual(proxy.data(proxy.index(2, 0), Qt.DisplayRole), 'layer 1')


if __name__ == '__main__':
unittest.main()

0 comments on commit 129ab50

Please sign in to comment.