Allow non sublayer items to be added to QgsProviderSublayerModel

E.g. embedded project items
This commit is contained in:
Nyall Dawson 2021-07-05 10:35:17 +10:00
parent 288321505a
commit 73f45ee467
5 changed files with 460 additions and 75 deletions

View File

@ -10,7 +10,9 @@ QgsProviderSublayerModel.Role.FeatureCount.__doc__ = "Feature count (for vector
QgsProviderSublayerModel.Role.WkbType.__doc__ = "WKB geometry type (for vector sublayers)"
QgsProviderSublayerModel.Role.GeometryColumnName.__doc__ = "Geometry column name (for vector sublayers)"
QgsProviderSublayerModel.Role.LayerNumber.__doc__ = "Layer number"
QgsProviderSublayerModel.Role.__doc__ = 'Custom model roles\n\n' + '* ``ProviderKey``: ' + QgsProviderSublayerModel.Role.ProviderKey.__doc__ + '\n' + '* ``LayerType``: ' + QgsProviderSublayerModel.Role.LayerType.__doc__ + '\n' + '* ``Uri``: ' + QgsProviderSublayerModel.Role.Uri.__doc__ + '\n' + '* ``Name``: ' + QgsProviderSublayerModel.Role.Name.__doc__ + '\n' + '* ``Description``: ' + QgsProviderSublayerModel.Role.Description.__doc__ + '\n' + '* ``Path``: ' + QgsProviderSublayerModel.Role.Path.__doc__ + '\n' + '* ``FeatureCount``: ' + QgsProviderSublayerModel.Role.FeatureCount.__doc__ + '\n' + '* ``WkbType``: ' + QgsProviderSublayerModel.Role.WkbType.__doc__ + '\n' + '* ``GeometryColumnName``: ' + QgsProviderSublayerModel.Role.GeometryColumnName.__doc__ + '\n' + '* ``LayerNumber``: ' + QgsProviderSublayerModel.Role.LayerNumber.__doc__
QgsProviderSublayerModel.Role.IsNonLayerItem.__doc__ = "``True`` if item is a non-sublayer item (e.g. an embedded project)"
QgsProviderSublayerModel.Role.NonLayerItemType.__doc__ = "Item type (for non-sublayer items)"
QgsProviderSublayerModel.Role.__doc__ = 'Custom model roles\n\n' + '* ``ProviderKey``: ' + QgsProviderSublayerModel.Role.ProviderKey.__doc__ + '\n' + '* ``LayerType``: ' + QgsProviderSublayerModel.Role.LayerType.__doc__ + '\n' + '* ``Uri``: ' + QgsProviderSublayerModel.Role.Uri.__doc__ + '\n' + '* ``Name``: ' + QgsProviderSublayerModel.Role.Name.__doc__ + '\n' + '* ``Description``: ' + QgsProviderSublayerModel.Role.Description.__doc__ + '\n' + '* ``Path``: ' + QgsProviderSublayerModel.Role.Path.__doc__ + '\n' + '* ``FeatureCount``: ' + QgsProviderSublayerModel.Role.FeatureCount.__doc__ + '\n' + '* ``WkbType``: ' + QgsProviderSublayerModel.Role.WkbType.__doc__ + '\n' + '* ``GeometryColumnName``: ' + QgsProviderSublayerModel.Role.GeometryColumnName.__doc__ + '\n' + '* ``LayerNumber``: ' + QgsProviderSublayerModel.Role.LayerNumber.__doc__ + '\n' + '* ``IsNonLayerItem``: ' + QgsProviderSublayerModel.Role.IsNonLayerItem.__doc__ + '\n' + '* ``NonLayerItemType``: ' + QgsProviderSublayerModel.Role.NonLayerItemType.__doc__
# --
# monkey patching scoped based enum
QgsProviderSublayerModel.Column.Name.__doc__ = "Layer name"

View File

@ -35,6 +35,8 @@ class QgsProviderSublayerModel: QAbstractItemModel
WkbType,
GeometryColumnName,
LayerNumber,
IsNonLayerItem,
NonLayerItemType,
};
enum class Column
@ -43,6 +45,91 @@ class QgsProviderSublayerModel: QAbstractItemModel
Description,
};
class NonLayerItem
{
%Docstring(signature="appended")
.. versionadded:: 3.22
%End
%TypeHeaderCode
#include "qgsprovidersublayermodel.h"
%End
public:
QString type() const;
%Docstring
Returns the item's type.
.. seealso:: :py:func:`setType`
%End
void setType( const QString &type );
%Docstring
Sets the item's ``type``.
.. seealso:: :py:func:`type`
%End
QString name() const;
%Docstring
Returns the item's name.
.. seealso:: :py:func:`setName`
%End
void setName( const QString &name );
%Docstring
Sets the item's ``name``.
.. seealso:: :py:func:`setName`
%End
QString description() const;
%Docstring
Returns the item's description.
.. seealso:: :py:func:`setDescription`
%End
void setDescription( const QString &description );
%Docstring
Sets the item's ``description``.
.. seealso:: :py:func:`setDescription`
%End
QString uri() const;
%Docstring
Returns the item's URI.
.. seealso:: :py:func:`setUri`
%End
void setUri( const QString &uri );
%Docstring
Set the item's ``uri``.
.. seealso:: :py:func:`setUri`
%End
QIcon icon() const;
%Docstring
Returns the item's icon.
.. seealso:: :py:func:`setIcon`
%End
void setIcon( const QIcon &icon );
%Docstring
Sets the item's ``icon``.
.. seealso:: :py:func:`setIcon`
%End
};
QgsProviderSublayerModel( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsProviderSublayerModel, with the specified ``parent`` object.
@ -61,6 +148,12 @@ Returns the sublayer details shown in the model.
.. seealso:: :py:func:`setSublayerDetails`
%End
void addNonLayerItem( const QgsProviderSublayerModel::NonLayerItem &item );
%Docstring
Adds a non-layer item (e.g. an embedded QGIS project item) to the model.
%End
virtual QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const;
virtual QModelIndex parent( const QModelIndex &index ) const;

View File

@ -56,6 +56,13 @@ QList<QgsProviderSublayerDetails> QgsProviderSublayerModel::sublayerDetails() co
return mSublayers;
}
void QgsProviderSublayerModel::addNonLayerItem( const QgsProviderSublayerModel::NonLayerItem &item )
{
beginInsertRows( QModelIndex(), mSublayers.count() + mNonLayerItems.count(), mSublayers.count() + mNonLayerItems.count() );
mNonLayerItems.append( item );
endInsertRows();
}
QModelIndex QgsProviderSublayerModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( column < 0 || column >= columnCount() )
@ -64,7 +71,7 @@ QModelIndex QgsProviderSublayerModel::index( int row, int column, const QModelIn
return QModelIndex();
}
if ( !parent.isValid() && row >= 0 && row < mSublayers.size() )
if ( !parent.isValid() && row >= 0 && row < mSublayers.size() + mNonLayerItems.size() )
{
//return an index for the sublayer at this position
return createIndex( row, column );
@ -92,7 +99,7 @@ int QgsProviderSublayerModel::rowCount( const QModelIndex &parent ) const
{
if ( !parent.isValid() )
{
return mSublayers.size();
return mSublayers.size() + mNonLayerItems.size();
}
else
{
@ -109,99 +116,152 @@ QVariant QgsProviderSublayerModel::data( const QModelIndex &index, int role ) co
if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
return QVariant();
const QgsProviderSublayerDetails details = mSublayers.at( index.row() );
switch ( role )
if ( index.row() < mSublayers.count() )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
case Qt::EditRole:
{
switch ( static_cast< Column >( index.column() ) )
{
case QgsProviderSublayerModel::Column::Name:
return details.name();
case QgsProviderSublayerModel::Column::Description:
{
switch ( details.type() )
{
case QgsMapLayerType::VectorLayer:
{
QString count;
if ( details.featureCount() == static_cast< long long >( Qgis::FeatureCountState::Uncounted )
|| details.featureCount() == static_cast< long long >( Qgis::FeatureCountState::UnknownCount ) )
count = tr( "Uncounted" );
else
count = QLocale().toString( details.featureCount() );
const QgsProviderSublayerDetails details = mSublayers.at( index.row() );
if ( !details.description().isEmpty() )
return QStringLiteral( "%1 - %2 (%3)" ).arg( details.description(),
QgsWkbTypes::displayString( details.wkbType() ),
count );
else
return QStringLiteral( "%2 (%3)" ).arg(
switch ( role )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
case Qt::EditRole:
{
switch ( static_cast< Column >( index.column() ) )
{
case QgsProviderSublayerModel::Column::Name:
return details.name();
case QgsProviderSublayerModel::Column::Description:
{
switch ( details.type() )
{
case QgsMapLayerType::VectorLayer:
{
QString count;
if ( details.featureCount() == static_cast< long long >( Qgis::FeatureCountState::Uncounted )
|| details.featureCount() == static_cast< long long >( Qgis::FeatureCountState::UnknownCount ) )
count = tr( "Uncounted" );
else
count = QLocale().toString( details.featureCount() );
if ( !details.description().isEmpty() )
return QStringLiteral( "%1 - %2 (%3)" ).arg( details.description(),
QgsWkbTypes::displayString( details.wkbType() ),
count );
else
return QStringLiteral( "%2 (%3)" ).arg(
QgsWkbTypes::displayString( details.wkbType() ),
count );
}
case QgsMapLayerType::RasterLayer:
case QgsMapLayerType::PluginLayer:
case QgsMapLayerType::MeshLayer:
case QgsMapLayerType::VectorTileLayer:
case QgsMapLayerType::AnnotationLayer:
case QgsMapLayerType::PointCloudLayer:
return details.description();
}
break;
case QgsMapLayerType::RasterLayer:
case QgsMapLayerType::PluginLayer:
case QgsMapLayerType::MeshLayer:
case QgsMapLayerType::VectorTileLayer:
case QgsMapLayerType::AnnotationLayer:
case QgsMapLayerType::PointCloudLayer:
return details.description();
}
break;
}
return details.name();
}
return details.name();
}
case Qt::DecorationRole:
{
if ( index.column() == 0 )
return details.type() == QgsMapLayerType::VectorLayer
? QgsIconUtils::iconForWkbType( details.wkbType() )
: QgsIconUtils::iconForLayerType( details.type() );
else
return QVariant();
}
case Qt::DecorationRole:
{
if ( index.column() == 0 )
return details.type() == QgsMapLayerType::VectorLayer
? QgsIconUtils::iconForWkbType( details.wkbType() )
: QgsIconUtils::iconForLayerType( details.type() );
else
case static_cast< int >( Role::IsNonLayerItem ):
return false;
case static_cast< int >( Role::ProviderKey ):
return details.providerKey();
case static_cast< int >( Role::LayerType ):
return static_cast< int >( details.type() );
case static_cast< int >( Role::Uri ):
return details.uri();
case static_cast< int >( Role::Name ):
return details.name();
case static_cast< int >( Role::Description ):
return details.description();
case static_cast< int >( Role::Path ):
return details.path();
case static_cast< int >( Role::FeatureCount ):
return details.featureCount();
case static_cast< int >( Role::WkbType ):
return details.wkbType();
case static_cast< int >( Role::GeometryColumnName ):
return details.geometryColumnName();
case static_cast< int >( Role::LayerNumber ):
return details.layerNumber();
default:
return QVariant();
}
}
else
{
const NonLayerItem details = mNonLayerItems.at( index.row() - mSublayers.count() );
case static_cast< int >( Role::ProviderKey ):
return details.providerKey();
switch ( role )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
case Qt::EditRole:
{
switch ( static_cast< Column >( index.column() ) )
{
case QgsProviderSublayerModel::Column::Name:
return details.name();
case QgsProviderSublayerModel::Column::Description:
return details.description();
}
return QVariant();
}
case static_cast< int >( Role::LayerType ):
return static_cast< int >( details.type() );
case Qt::DecorationRole:
{
if ( index.column() == 0 )
return details.icon();
else
return QVariant();
}
case static_cast< int >( Role::Uri ):
return details.uri();
case static_cast< int >( Role::IsNonLayerItem ):
return true;
case static_cast< int >( Role::Name ):
return details.name();
case static_cast< int >( Role::Uri ):
return details.uri();
case static_cast< int >( Role::Description ):
return details.description();
case static_cast< int >( Role::Name ):
return details.name();
case static_cast< int >( Role::Path ):
return details.path();
case static_cast< int >( Role::Description ):
return details.description();
case static_cast< int >( Role::FeatureCount ):
return details.featureCount();
case static_cast< int >( Role::NonLayerItemType ):
return details.type();
case static_cast< int >( Role::WkbType ):
return details.wkbType();
case static_cast< int >( Role::GeometryColumnName ):
return details.geometryColumnName();
case static_cast< int >( Role::LayerNumber ):
return details.layerNumber();
default:
return QVariant();
default:
return QVariant();
}
}
}
@ -233,3 +293,58 @@ QVariant QgsProviderSublayerModel::headerData( int section, Qt::Orientation orie
}
return QVariant();
}
//
// QgsProviderSublayerModel::NonLayerItem
//
QString QgsProviderSublayerModel::NonLayerItem::type() const
{
return mType;
}
void QgsProviderSublayerModel::NonLayerItem::setType( const QString &type )
{
mType = type;
}
QString QgsProviderSublayerModel::NonLayerItem::name() const
{
return mName;
}
void QgsProviderSublayerModel::NonLayerItem::setName( const QString &name )
{
mName = name;
}
QString QgsProviderSublayerModel::NonLayerItem::description() const
{
return mDescription;
}
void QgsProviderSublayerModel::NonLayerItem::setDescription( const QString &description )
{
mDescription = description;
}
QString QgsProviderSublayerModel::NonLayerItem::uri() const
{
return mUri;
}
void QgsProviderSublayerModel::NonLayerItem::setUri( const QString &uri )
{
mUri = uri;
}
QIcon QgsProviderSublayerModel::NonLayerItem::icon() const
{
return mIcon;
}
void QgsProviderSublayerModel::NonLayerItem::setIcon( const QIcon &icon )
{
mIcon = icon;
}

View File

@ -49,6 +49,8 @@ class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel
WkbType, //!< WKB geometry type (for vector sublayers)
GeometryColumnName, //!< Geometry column name (for vector sublayers)
LayerNumber, //!< Layer number
IsNonLayerItem, //!< TRUE if item is a non-sublayer item (e.g. an embedded project)
NonLayerItemType, //!< Item type (for non-sublayer items)
};
//! Model columns
@ -58,6 +60,87 @@ class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel
Description = 1, //!< Layer description
};
/**
* \ingroup core
*
* \brief
* \since QGIS 3.22
*/
class NonLayerItem
{
public:
/**
* Returns the item's type.
* \see setType()
*/
QString type() const;
/**
* Sets the item's \a type.
* \see type()
*/
void setType( const QString &type );
/**
* Returns the item's name.
* \see setName()
*/
QString name() const;
/**
* Sets the item's \a name.
* \see setName()
*/
void setName( const QString &name );
/**
* Returns the item's description.
* \see setDescription()
*/
QString description() const;
/**
* Sets the item's \a description.
* \see setDescription()
*/
void setDescription( const QString &description );
/**
* Returns the item's URI.
* \see setUri()
*/
QString uri() const;
/**
* Set the item's \a uri.
* \see setUri()
*/
void setUri( const QString &uri );
/**
* Returns the item's icon.
* \see setIcon()
*/
QIcon icon() const;
/**
* Sets the item's \a icon.
* \see setIcon()
*/
void setIcon( const QIcon &icon );
private:
QString mType;
QString mName;
QString mDescription;
QString mUri;
QIcon mIcon;
};
/**
* Constructor for QgsProviderSublayerModel, with the specified \a parent object.
*/
@ -76,6 +159,12 @@ class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel
* \see setSublayerDetails()
*/
QList< QgsProviderSublayerDetails > sublayerDetails() const;
/**
* Adds a non-layer item (e.g. an embedded QGIS project item) to the model.
*/
void addNonLayerItem( const QgsProviderSublayerModel::NonLayerItem &item );
QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override;
QModelIndex parent( const QModelIndex &index ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
@ -86,6 +175,7 @@ class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel
private:
QList<QgsProviderSublayerDetails> mSublayers;
QList<NonLayerItem> mNonLayerItems;
};

View File

@ -154,6 +154,91 @@ class TestQgsProviderSublayerModel(unittest.TestCase):
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Name), 'layer 3')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Description), None)
def test_model_with_non_layer_items(self):
model = QgsProviderSublayerModel()
self.assertEqual(model.rowCount(QModelIndex()), 0)
self.assertEqual(model.columnCount(QModelIndex()), 2)
self.assertEqual(model.headerData(0, Qt.Horizontal, Qt.DisplayRole), 'Layer')
self.assertEqual(model.headerData(0, Qt.Horizontal, Qt.ToolTipRole), 'Layer')
self.assertEqual(model.headerData(1, Qt.Horizontal, Qt.DisplayRole), 'Description')
self.assertEqual(model.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')
model.setSublayerDetails([layer1])
self.assertEqual(model.rowCount(QModelIndex()), 1)
self.assertEqual(model.data(model.index(0, 0), Qt.DisplayRole), 'layer 1')
self.assertEqual(model.data(model.index(0, 1), Qt.DisplayRole), 'description 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.ProviderKey), 'gdal')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.LayerType), QgsMapLayerType.RasterLayer)
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Uri), 'uri 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Name), 'layer 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Description), 'description 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), False)
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(model.rowCount(QModelIndex()), 2)
self.assertEqual(model.data(model.index(0, 0), Qt.DisplayRole), 'layer 1')
self.assertEqual(model.data(model.index(0, 1), Qt.DisplayRole), 'description 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.ProviderKey), 'gdal')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.LayerType), QgsMapLayerType.RasterLayer)
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Uri), 'uri 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Name), 'layer 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Description), 'description 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), False)
self.assertEqual(model.data(model.index(1, 0), Qt.DisplayRole), 'item name 1')
self.assertEqual(model.data(model.index(1, 1), Qt.DisplayRole), 'item desc 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.Uri), 'item uri 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.Name), 'item name 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.Description), 'item desc 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), True)
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.NonLayerItemType), 'item type 1')
item2 = QgsProviderSublayerModel.NonLayerItem()
item2.setUri('item uri 2')
item2.setName('item name 2')
item2.setType('item type 2')
item2.setDescription('item desc 2')
model.addNonLayerItem(item2)
self.assertEqual(model.rowCount(QModelIndex()), 3)
self.assertEqual(model.data(model.index(0, 0), Qt.DisplayRole), 'layer 1')
self.assertEqual(model.data(model.index(0, 1), Qt.DisplayRole), 'description 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.ProviderKey), 'gdal')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.LayerType), QgsMapLayerType.RasterLayer)
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Uri), 'uri 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Name), 'layer 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.Description), 'description 1')
self.assertEqual(model.data(model.index(0, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), False)
self.assertEqual(model.data(model.index(1, 0), Qt.DisplayRole), 'item name 1')
self.assertEqual(model.data(model.index(1, 1), Qt.DisplayRole), 'item desc 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.Uri), 'item uri 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.Name), 'item name 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.Description), 'item desc 1')
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.IsNonLayerItem), True)
self.assertEqual(model.data(model.index(1, 0), QgsProviderSublayerModel.Role.NonLayerItemType), 'item type 1')
self.assertEqual(model.data(model.index(2, 0), Qt.DisplayRole), 'item name 2')
self.assertEqual(model.data(model.index(2, 1), Qt.DisplayRole), 'item desc 2')
self.assertEqual(model.data(model.index(2, 0), QgsProviderSublayerModel.Role.Uri), 'item uri 2')
self.assertEqual(model.data(model.index(2, 0), QgsProviderSublayerModel.Role.Name), 'item name 2')
self.assertEqual(model.data(model.index(2, 0), QgsProviderSublayerModel.Role.Description), 'item desc 2')
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')
if __name__ == '__main__':
unittest.main()