qgsproject: Add a sip removeMapLayers code to fix qt6 version

With this new method code, it is now possible to use
`QgsProject::removeMapLayers` with a list of layers or a list of
layers IDs in qt5 and qt6.

Co-authored-by: bdm-oslandia <benoit.de.mezzo@oslandia.com>
This commit is contained in:
Jean Felder 2025-04-28 17:18:58 +02:00 committed by Nyall Dawson
parent b2a377bb8f
commit a00f47c773
5 changed files with 202 additions and 46 deletions

View File

@ -38,7 +38,6 @@ PyQgsAuthenticationSystem
PyQgsDelimitedTextProvider
PyQgsEditWidgets
PyQgsElevationProfileCanvas
PyQgsProject
PyQgsFloatingWidget
PyQgsLayoutHtml
PyQgsMapBoxGlStyleConverter

View File

@ -1343,14 +1343,16 @@ should use :py:func:`~QgsProject.addMapLayers` instead.
.. seealso:: :py:func:`addMapLayers`
%End
void removeMapLayers( const QStringList &layerIds );
void removeMapLayers( SIP_PYOBJECT layers /TypeHint="Union[List[QgsVectorLayer], List[str]]"/ );
%Docstring
Remove a set of layers from the registry by layer ID.
Remove a set of layers from the registry.
The specified layers will be removed from the registry. If the registry
has ownership of any layers these layers will also be deleted.
:param layerIds: list of IDs of the layers to remove
:param layers: list of layers or list of layer IDs of the layers to
remove
.. note::
@ -1360,24 +1362,44 @@ has ownership of any layers these layers will also be deleted.
.. seealso:: :py:func:`removeAllMapLayers`
%End
void removeMapLayers( const QList<QgsMapLayer *> &layers );
%Docstring
Remove a set of layers from the registry.
The specified layers will be removed from the registry. If the registry
has ownership of any layers these layers will also be deleted.
:param layers: A list of layers to remove. ``None`` values are ignored.
.. note::
As a side-effect the QgsProject instance is marked dirty.
.. seealso:: :py:func:`removeMapLayer`
.. seealso:: :py:func:`removeAllMapLayers`
%MethodCode
if ( !PyList_Check( a0 ) )
{
sipIsErr = 1;
PyErr_SetString( PyExc_TypeError, "Expected a list of layers or layers IDs" );
}
else if ( PyList_GET_SIZE( a0 ) )
{
PyObject *firstLayerPyObj = PyList_GetItem( a0, 0 );
if ( firstLayerPyObj )
{
int state;
if ( sipCanConvertToType( firstLayerPyObj, sipType_QgsMapLayer, SIP_NOT_NONE ) )
{
const sipTypeDef *qlist_type = sipFindType( "QList<QgsMapLayer *>" );
QList<QgsMapLayer *> *layersList = reinterpret_cast<QList<QgsMapLayer *> *>( sipConvertToType( a0, qlist_type, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
sipCpp->removeMapLayers( *layersList );
}
sipReleaseType( layersList, qlist_type, state );
}
else if ( sipCanConvertToType( firstLayerPyObj, sipType_QString, SIP_NOT_NONE ) )
{
QStringList *layersId = reinterpret_cast<QStringList *>( sipConvertToType( a0, sipType_QStringList, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
sipCpp->removeMapLayers( *layersId );
}
sipReleaseType( layersId, sipType_QStringList, state );
}
else
{
sipIsErr = 1;
PyErr_SetString( PyExc_TypeError, "Expected a list of layers or layers IDs" );
}
}
}
%End
void removeMapLayer( const QString &layerId );

View File

@ -1343,14 +1343,16 @@ should use :py:func:`~QgsProject.addMapLayers` instead.
.. seealso:: :py:func:`addMapLayers`
%End
void removeMapLayers( const QStringList &layerIds );
void removeMapLayers( SIP_PYOBJECT layers /TypeHint="Union[List[QgsVectorLayer], List[str]]"/ );
%Docstring
Remove a set of layers from the registry by layer ID.
Remove a set of layers from the registry.
The specified layers will be removed from the registry. If the registry
has ownership of any layers these layers will also be deleted.
:param layerIds: list of IDs of the layers to remove
:param layers: list of layers or list of layer IDs of the layers to
remove
.. note::
@ -1360,24 +1362,44 @@ has ownership of any layers these layers will also be deleted.
.. seealso:: :py:func:`removeAllMapLayers`
%End
void removeMapLayers( const QList<QgsMapLayer *> &layers );
%Docstring
Remove a set of layers from the registry.
The specified layers will be removed from the registry. If the registry
has ownership of any layers these layers will also be deleted.
:param layers: A list of layers to remove. ``None`` values are ignored.
.. note::
As a side-effect the QgsProject instance is marked dirty.
.. seealso:: :py:func:`removeMapLayer`
.. seealso:: :py:func:`removeAllMapLayers`
%MethodCode
if ( !PyList_Check( a0 ) )
{
sipIsErr = 1;
PyErr_SetString( PyExc_TypeError, "Expected a list of layers or layers IDs" );
}
else if ( PyList_GET_SIZE( a0 ) )
{
PyObject *firstLayerPyObj = PyList_GetItem( a0, 0 );
if ( firstLayerPyObj )
{
int state;
if ( sipCanConvertToType( firstLayerPyObj, sipType_QgsMapLayer, SIP_NOT_NONE ) )
{
const sipTypeDef *qlist_type = sipFindType( "QList<QgsMapLayer *>" );
QList<QgsMapLayer *> *layersList = reinterpret_cast<QList<QgsMapLayer *> *>( sipConvertToType( a0, qlist_type, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
sipCpp->removeMapLayers( *layersList );
}
sipReleaseType( layersList, qlist_type, state );
}
else if ( sipCanConvertToType( firstLayerPyObj, sipType_QString, SIP_NOT_NONE ) )
{
QStringList *layersId = reinterpret_cast<QStringList *>( sipConvertToType( a0, sipType_QStringList, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
sipCpp->removeMapLayers( *layersId );
}
sipReleaseType( layersId, sipType_QStringList, state );
}
else
{
sipIsErr = 1;
PyErr_SetString( PyExc_TypeError, "Expected a list of layers or layers IDs" );
}
}
}
%End
void removeMapLayer( const QString &layerId );

View File

@ -1373,6 +1373,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
bool addToLegend = true,
bool takeOwnership SIP_PYARGREMOVE = true );
#ifndef SIP_RUN
/**
* \brief
* Remove a set of layers from the registry by layer ID.
@ -1404,6 +1405,62 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
* \see removeAllMapLayers()
*/
void removeMapLayers( const QList<QgsMapLayer *> &layers );
#else
/**
* \brief
* Remove a set of layers from the registry.
*
* The specified layers will be removed from the registry. If the registry has ownership
* of any layers these layers will also be deleted.
*
* \param layers list of layers or list of layer IDs of the layers to remove
*
* \note As a side-effect the QgsProject instance is marked dirty.
* \see removeMapLayer()
* \see removeAllMapLayers()
*/
void removeMapLayers( SIP_PYOBJECT layers SIP_TYPEHINT( Union[List[QgsVectorLayer], List[str]] ) );
% MethodCode
if ( !PyList_Check( a0 ) )
{
sipIsErr = 1;
PyErr_SetString( PyExc_TypeError, "Expected a list of layers or layers IDs" );
}
else if ( PyList_GET_SIZE( a0 ) )
{
PyObject *firstLayerPyObj = PyList_GetItem( a0, 0 );
if ( firstLayerPyObj )
{
int state;
if ( sipCanConvertToType( firstLayerPyObj, sipType_QgsMapLayer, SIP_NOT_NONE ) )
{
const sipTypeDef *qlist_type = sipFindType( "QList<QgsMapLayer *>" );
QList<QgsMapLayer *> *layersList = reinterpret_cast<QList<QgsMapLayer *> *>( sipConvertToType( a0, qlist_type, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
sipCpp->removeMapLayers( *layersList );
}
sipReleaseType( layersList, qlist_type, state );
}
else if ( sipCanConvertToType( firstLayerPyObj, sipType_QString, SIP_NOT_NONE ) )
{
QStringList *layersId = reinterpret_cast<QStringList *>( sipConvertToType( a0, sipType_QStringList, 0, SIP_NOT_NONE, &state, &sipIsErr ) );
if ( !sipIsErr )
{
sipCpp->removeMapLayers( *layersId );
}
sipReleaseType( layersId, sipType_QStringList, state );
}
else
{
sipIsErr = 1;
PyErr_SetString( PyExc_TypeError, "Expected a list of layers or layers IDs" );
}
}
}
% End
#endif
/**
* \brief

View File

@ -750,9 +750,6 @@ class TestQgsProject(QgisTestCase):
QgsProject.instance().removeMapLayers([l4.id()])
self.assertFalse(sip.isdeleted(l4))
# fails on qt5 due to removeMapLayers list type conversion - needs a PyName alias
# added to removeMapLayers for QGIS 3.0
@QgisTestCase.expectedFailure(QT_VERSION_STR[0] == "5")
def test_removeMapLayersByLayer(self):
"""test removing map layers by layer"""
QgsProject.instance().removeAllMapLayers()
@ -825,6 +822,65 @@ class TestQgsProject(QgisTestCase):
QgsProject.instance().removeMapLayer(l3.id())
self.assertFalse(sip.isdeleted(l3))
# ==== do it again with list
l1 = createLayer("test")
l2 = createLayer("test2")
QgsProject.instance().addMapLayers([l1, l2])
self.assertEqual(QgsProject.instance().count(), 2)
# remove - empty list
# nothing happens
QgsProject.instance().removeMapLayers([])
self.assertEqual(QgsProject.instance().count(), 2)
# remove layers
l1_id = l1.id()
l2_id = l2.id()
QgsProject.instance().removeMapLayers([l1_id, l2_id])
self.assertEqual(QgsProject.instance().count(), 0)
# mix list and list ids
# this should not work
l5 = createLayer("test5")
l6 = createLayer("test6")
l7 = createLayer("test7")
QgsProject.instance().addMapLayers([l5, l6, l7])
self.assertEqual(QgsProject.instance().count(), 3)
with self.assertRaises(TypeError):
QgsProject.instance().removeMapLayers(["test6", l5])
self.assertEqual(QgsProject.instance().count(), 3)
with self.assertRaises(TypeError):
QgsProject.instance().removeMapLayers([l5, l6, "test7"])
self.assertEqual(QgsProject.instance().count(), 3)
# remove vector and raster layers
r0 = QgsVectorLayer("points.shp", "points", "ogr")
r1 = QgsVectorLayer("lines.shp", "lines", "ogr")
r2 = QgsRasterLayer("landsat_4326.tif", "landsat", "gdal")
self.assertTrue(QgsProject.instance().addMapLayers([r0, r1, r2]))
self.assertEqual(QgsProject.instance().count(), 6)
QgsProject.instance().removeMapLayers([r0, r1, l7])
self.assertEqual(QgsProject.instance().count(), 3)
# try to remove with wrong layer names
QgsProject.instance().removeMapLayers(["test20", "test21"])
self.assertEqual(QgsProject.instance().count(), 3)
# try to remove only one layer
# this should not work
with self.assertRaises(TypeError):
QgsProject.instance().removeMapLayers(l5)
self.assertEqual(QgsProject.instance().count(), 3)
# try to remove with a list of int
# this should not work
with self.assertRaises(TypeError):
QgsProject.instance().removeMapLayers([4, 5, 6])
self.assertEqual(QgsProject.instance().count(), 3)
def test_removeMapLayerByLayer(self):
"""test removing a map layer by layer"""
QgsProject.instance().removeAllMapLayers()