Split off map layer storage handling from QgsProject to QgsMapLayerStore

Turns out that there's valid use cases for having layer stores
outside of the cost of using a whole QgsProject.

Partially reverts the merger of QgsMapLayerRegistry into QgsProject,
except that there's no singletons here.
This commit is contained in:
Nyall Dawson 2017-05-02 10:30:08 +10:00
parent 8e70aa84fa
commit f9bd83c035
7 changed files with 1316 additions and 0 deletions

View File

@ -92,6 +92,7 @@
%Include qgsmaplayermodel.sip
%Include qgsmaplayerproxymodel.sip
%Include qgsmaplayerrenderer.sip
%Include qgsmaplayerstore.sip
%Include qgsmaplayerstylemanager.sip
%Include qgsmaprenderercache.sip
%Include qgsmaprenderercustompainterjob.sip

View File

@ -0,0 +1,292 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsmaplayerstore.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsMapLayerStore : QObject
{
%Docstring
A storage object for map layers, in which the layers are owned by the
store and have their lifetime bound to the store.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsmaplayerstore.h"
%End
public:
explicit QgsMapLayerStore( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsMapLayerStore.
%End
~QgsMapLayerStore();
int count() const;
%Docstring
Returns the number of layers contained in the store.
:rtype: int
%End
int __len__() const;
%Docstring
Returns the number of layers contained in the store.
:rtype: int
%End
%MethodCode
sipRes = sipCpp->count();
%End
QgsMapLayer *mapLayer( const QString &id ) const;
%Docstring
Retrieve a pointer to a layer by layer ``id``.
\param id ID of layer to retrieve
:return: matching layer, or None if no matching layer found
.. seealso:: mapLayersByName()
.. seealso:: mapLayers()
:rtype: QgsMapLayer
%End
QList<QgsMapLayer *> mapLayersByName( const QString &name ) const;
%Docstring
Retrieve a list of matching layers by layer ``name``.
\param name name of layers to match
:return: list of matching layers
.. seealso:: mapLayer()
.. seealso:: mapLayers()
:rtype: list of QgsMapLayer
%End
QMap<QString, QgsMapLayer *> mapLayers() const;
%Docstring
Returns a map of all layers by layer ID.
.. seealso:: mapLayer()
.. seealso:: mapLayersByName()
.. seealso:: layers()
:rtype: QMap<str, QgsMapLayer *>
%End
QList<QgsMapLayer *> addMapLayers( const QList<QgsMapLayer *> &layers /Transfer/ );
%Docstring
\brief
Add a list of ``layers`` to the store. Ownership of the layers is transferred
to the store.
The layersAdded() and layerWasAdded() signals will always be emitted.
\param layers A list of layer which should be added to the store.
:return: a list of the map layers that were added
successfully. If a layer is invalid, or already exists in the store,
it will not be part of the returned list.
.. seealso:: addMapLayer()
:rtype: list of QgsMapLayer
%End
QgsMapLayer *addMapLayer( QgsMapLayer *layer /Transfer/ );
%Docstring
\brief
Add a ``layer`` to the store. Ownership of the layer is transferred to the
store.
The layersAdded() and layerWasAdded() signals will always be emitted.
If you are adding multiple layers at once, you should use
addMapLayers() instead.
\param layer A layer to add to the store
:return: None if unable to add layer, otherwise pointer to newly added layer
.. seealso:: addMapLayers
.. note::
Use addMapLayers() if adding more than one layer at a time.
.. seealso:: addMapLayers()
:rtype: QgsMapLayer
%End
void removeMapLayers( const QStringList &layerIds ) /PyName=removeMapLayersById/;
%Docstring
\brief
Remove a set of layers from the store by layer ID.
The specified layers will be removed from the store.
These layers will also be deleted.
\param layerIds list of IDs of the layers to remove
.. seealso:: takeMapLayer()
.. seealso:: removeMapLayer()
.. seealso:: removeAllMapLayers()
.. note::
available in Python bindings as removeMapLayersById.
%End
void removeMapLayers( const QList<QgsMapLayer *> &layers );
void removeMapLayer( const QString &id );
%Docstring
\brief
Remove a layer from the store by layer ``id``.
The specified layer will be removed from the store. The layer will also be deleted.
\param id ID of the layer to remove
.. seealso:: takeMapLayer()
.. seealso:: removeMapLayers()
.. seealso:: removeAllMapLayers()
%End
void removeMapLayer( QgsMapLayer *layer );
%Docstring
\brief
Remove a ``layer`` from the store.
The specified layer will be removed from the store. The layer will also be deleted.
\param layer The layer to remove. Null pointers are ignored.
.. seealso:: takeMapLayer()
.. seealso:: removeMapLayers()
.. seealso:: removeAllMapLayers()
%End
QgsMapLayer *takeMapLayer( QgsMapLayer *layer ) /TransferBack/;
%Docstring
Takes a ``layer`` from the store. If the layer was owned by the store, the
layer will be returned without deleting it. The caller takes ownership of
the layer and is responsible for deleting it.
.. seealso:: removeMapLayer()
:rtype: QgsMapLayer
%End
void removeAllMapLayers();
%Docstring
Removes all registered layers. These layers will also be deleted.
.. note::
Calling this method will cause the removeAll() signal to
be emitted.
.. seealso:: removeMapLayer()
.. seealso:: removeMapLayers()
%End
signals:
void layersWillBeRemoved( const QStringList &layerIds );
%Docstring
Emitted when one or more layers are about to be removed from the store.
\param layerIds A list of IDs for the layers which are to be removed.
.. seealso:: layerWillBeRemoved()
.. seealso:: layersRemoved()
%End
void layersWillBeRemoved( const QList<QgsMapLayer *> &layers );
%Docstring
Emitted when one or more layers are about to be removed from the store.
\param layers A list of layers which are to be removed.
.. seealso:: layerWillBeRemoved()
.. seealso:: layersRemoved()
%End
void layerWillBeRemoved( const QString &layerId );
%Docstring
Emitted when a layer is about to be removed from the store.
\param layerId The ID of the layer to be removed.
.. note::
Consider using layersWillBeRemoved() instead.
.. seealso:: layersWillBeRemoved()
.. seealso:: layerRemoved()
%End
void layerWillBeRemoved( QgsMapLayer *layer );
%Docstring
Emitted when a layer is about to be removed from the store.
\param layer The layer to be removed.
.. note::
Consider using layersWillBeRemoved() instead.
.. seealso:: layersWillBeRemoved()
.. seealso:: layerRemoved()
%End
void layersRemoved( const QStringList &layerIds );
%Docstring
Emitted after one or more layers were removed from the store.
\param layerIds A list of IDs of the layers which were removed.
.. seealso:: layersWillBeRemoved()
%End
void layerRemoved( const QString &layerId );
%Docstring
Emitted after a layer was removed from the store.
\param layerId The ID of the layer removed.
.. note::
Consider using layersRemoved() instead
.. seealso:: layerWillBeRemoved()
%End
void allLayersRemoved();
%Docstring
Emitted when all layers are removed, before layersWillBeRemoved() and
layerWillBeRemoved() signals are emitted. The layersWillBeRemoved() and
layerWillBeRemoved() signals will still be emitted following this signal.
You can use this signal to do easy (and fast) cleanup.
%End
void layersAdded( const QList<QgsMapLayer *> &layers );
%Docstring
Emitted when one or more layers were added to the store.
\param layers List of layers which have been added.
.. seealso:: legendLayersAdded()
.. seealso:: layerWasAdded()
%End
void layerWasAdded( QgsMapLayer *layer );
%Docstring
Emitted when a ``layer`` was added to the store.
.. note::
Consider using layersAdded() instead
.. seealso:: layersAdded()
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsmaplayerstore.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -176,6 +176,7 @@ SET(QGIS_CORE_SRCS
qgsmaplayerlegend.cpp
qgsmaplayermodel.cpp
qgsmaplayerproxymodel.cpp
qgsmaplayerstore.cpp
qgsmaplayerstylemanager.cpp
qgsmaprenderercache.cpp
qgsmaprenderercustompainterjob.cpp
@ -525,6 +526,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsmaplayerlegend.h
qgsmaplayermodel.h
qgsmaplayerproxymodel.h
qgsmaplayerstore.h
qgsmaplayerstylemanager.h
qgsmaprenderercache.h
qgsmaprenderercustompainterjob.h

View File

@ -0,0 +1,195 @@
/***************************************************************************
qgsmaplayerstore.cpp
--------------------
begin : May 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsmaplayerstore.h"
#include "qgslogger.h"
QgsMapLayerStore::QgsMapLayerStore( QObject *parent )
: QObject( parent )
{}
QgsMapLayerStore::~QgsMapLayerStore()
{
removeAllMapLayers();
}
int QgsMapLayerStore::count() const
{
return mMapLayers.size();
}
QgsMapLayer *QgsMapLayerStore::mapLayer( const QString &layerId ) const
{
return mMapLayers.value( layerId );
}
QList<QgsMapLayer *> QgsMapLayerStore::mapLayersByName( const QString &layerName ) const
{
QList<QgsMapLayer *> myResultList;
Q_FOREACH ( QgsMapLayer *layer, mMapLayers )
{
if ( layer->name() == layerName )
{
myResultList << layer;
}
}
return myResultList;
}
QList<QgsMapLayer *> QgsMapLayerStore::addMapLayers(
const QList<QgsMapLayer *> &layers )
{
QList<QgsMapLayer *> myResultList;
Q_FOREACH ( QgsMapLayer *myLayer, layers )
{
if ( !myLayer || !myLayer->isValid() )
{
QgsDebugMsg( "Cannot add invalid layers" );
continue;
}
//check the layer is not already registered!
if ( !mMapLayers.contains( myLayer->id() ) )
{
mMapLayers[myLayer->id()] = myLayer;
myResultList << mMapLayers[myLayer->id()];
myLayer->setParent( this );
connect( myLayer, &QObject::destroyed, this, &QgsMapLayerStore::onMapLayerDeleted );
emit layerWasAdded( myLayer );
}
}
if ( !myResultList.isEmpty() )
{
emit layersAdded( myResultList );
}
return myResultList;
}
QgsMapLayer *
QgsMapLayerStore::addMapLayer( QgsMapLayer *layer )
{
QList<QgsMapLayer *> addedLayers;
addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer );
return addedLayers.isEmpty() ? nullptr : addedLayers[0];
}
void QgsMapLayerStore::removeMapLayers( const QStringList &layerIds )
{
QList<QgsMapLayer *> layers;
Q_FOREACH ( const QString &myId, layerIds )
{
layers << mMapLayers.value( myId );
}
removeMapLayers( layers );
}
void QgsMapLayerStore::removeMapLayers( const QList<QgsMapLayer *> &layers )
{
if ( layers.isEmpty() )
return;
QStringList layerIds;
QList<QgsMapLayer *> layerList;
Q_FOREACH ( QgsMapLayer *layer, layers )
{
// check layer and the store contains it
if ( layer && mMapLayers.contains( layer->id() ) )
{
layerIds << layer->id();
layerList << layer;
}
}
if ( layerIds.isEmpty() )
return;
emit layersWillBeRemoved( layerIds );
emit layersWillBeRemoved( layerList );
Q_FOREACH ( QgsMapLayer *lyr, layerList )
{
QString myId( lyr->id() );
emit layerWillBeRemoved( myId );
emit layerWillBeRemoved( lyr );
mMapLayers.remove( myId );
if ( lyr->parent() == this )
{
delete lyr;
}
emit layerRemoved( myId );
}
emit layersRemoved( layerIds );
}
void QgsMapLayerStore::removeMapLayer( const QString &layerId )
{
removeMapLayers( QList<QgsMapLayer *>() << mMapLayers.value( layerId ) );
}
void QgsMapLayerStore::removeMapLayer( QgsMapLayer *layer )
{
if ( layer )
removeMapLayers( QList<QgsMapLayer *>() << layer );
}
QgsMapLayer *QgsMapLayerStore::takeMapLayer( QgsMapLayer *layer )
{
if ( !layer )
return nullptr;
if ( mMapLayers.contains( layer->id() ) )
{
emit layersWillBeRemoved( QStringList() << layer->id() );
emit layersWillBeRemoved( QList<QgsMapLayer *>() << layer );
emit layerWillBeRemoved( layer->id() );
emit layerWillBeRemoved( layer );
mMapLayers.remove( layer->id() );
layer->setParent( nullptr );
emit layerRemoved( layer->id() );
emit layersRemoved( QStringList() << layer->id() );
return layer;
}
return nullptr; //don't return layer - it wasn't owned and accordingly we aren't transferring ownership
}
void QgsMapLayerStore::removeAllMapLayers()
{
emit allLayersRemoved();
// now let all observers know to clear themselves,
// and then consequently any of their map legends
removeMapLayers( mMapLayers.keys() );
mMapLayers.clear();
}
void QgsMapLayerStore::onMapLayerDeleted( QObject *obj )
{
QString id = mMapLayers.key( static_cast<QgsMapLayer *>( obj ) );
if ( !id.isNull() )
{
QgsDebugMsg( QString( "Map layer deleted without unregistering! %1" ).arg( id ) );
mMapLayers.remove( id );
}
}
QMap<QString, QgsMapLayer *> QgsMapLayerStore::mapLayers() const
{
return mMapLayers;
}

330
src/core/qgsmaplayerstore.h Normal file
View File

@ -0,0 +1,330 @@
/***************************************************************************
qgsmaplayerstore.h
------------------
begin : May 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSMAPLAYERSTORE_H
#define QGSMAPLAYERSTORE_H
#include "qgis_core.h"
#include "qgis.h"
#include "qgsmaplayer.h"
#include <QObject>
/**
* \class QgsMapLayerStore
* \ingroup core
* A storage object for map layers, in which the layers are owned by the
* store and have their lifetime bound to the store.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsMapLayerStore : public QObject
{
Q_OBJECT
public:
/**
* Constructor for QgsMapLayerStore.
*/
explicit QgsMapLayerStore( QObject *parent SIP_TRANSFERTHIS = nullptr );
~QgsMapLayerStore();
/**
* Returns the number of layers contained in the store.
*/
int count() const;
#ifdef SIP_RUN
/**
* Returns the number of layers contained in the store.
*/
int __len__() const;
% MethodCode
sipRes = sipCpp->count();
% End
#endif
/**
* Retrieve a pointer to a layer by layer \a id.
* \param id ID of layer to retrieve
* \returns matching layer, or nullptr if no matching layer found
* \see mapLayersByName()
* \see mapLayers()
*/
QgsMapLayer *mapLayer( const QString &id ) const;
/**
* Retrieve a list of matching layers by layer \a name.
* \param name name of layers to match
* \returns list of matching layers
* \see mapLayer()
* \see mapLayers()
*/
QList<QgsMapLayer *> mapLayersByName( const QString &name ) const;
/**
* Returns a map of all layers by layer ID.
* \see mapLayer()
* \see mapLayersByName()
* \see layers()
*/
QMap<QString, QgsMapLayer *> mapLayers() const;
#ifndef SIP_RUN
/**
* Returns a list of registered map layers with a specified layer type.
*
* Example:
*
* QVector<QgsVectorLayer*> vectorLayers = store->layers<QgsVectorLayer*>();
*
* \note not available in Python bindings
* \see mapLayers()
*/
template <typename T>
QVector<T> layers() const
{
QVector<T> layers;
QMap<QString, QgsMapLayer *>::const_iterator layerIt = mMapLayers.constBegin();
for ( ; layerIt != mMapLayers.constEnd(); ++layerIt )
{
T tLayer = qobject_cast<T>( layerIt.value() );
if ( tLayer )
{
layers << tLayer;
}
}
return layers;
}
#endif
/**
* \brief
* Add a list of \a layers to the store. Ownership of the layers is transferred
* to the store.
*
* The layersAdded() and layerWasAdded() signals will always be emitted.
*
* \param layers A list of layer which should be added to the store.
*
* \returns a list of the map layers that were added
* successfully. If a layer is invalid, or already exists in the store,
* it will not be part of the returned list.
*
* \see addMapLayer()
*/
QList<QgsMapLayer *> addMapLayers( const QList<QgsMapLayer *> &layers SIP_TRANSFER );
/**
* \brief
* Add a \a layer to the store. Ownership of the layer is transferred to the
* store.
*
* The layersAdded() and layerWasAdded() signals will always be emitted.
* If you are adding multiple layers at once, you should use
* addMapLayers() instead.
*
* \param layer A layer to add to the store
*
* \returns nullptr if unable to add layer, otherwise pointer to newly added layer
*
* \see addMapLayers
*
* \note Use addMapLayers() if adding more than one layer at a time.
* \see addMapLayers()
*/
QgsMapLayer *addMapLayer( QgsMapLayer *layer SIP_TRANSFER );
/**
* \brief
* Remove a set of layers from the store by layer ID.
*
* The specified layers will be removed from the store.
* These layers will also be deleted.
*
* \param layerIds list of IDs of the layers to remove
*
* \see takeMapLayer()
* \see removeMapLayer()
* \see removeAllMapLayers()
* \note available in Python bindings as removeMapLayersById.
*/
void removeMapLayers( const QStringList &layerIds ) SIP_PYNAME( removeMapLayersById );
/**
* \brief
* Remove a set of \a layers from the store.
*
* The specified layers will be removed from the store.
* These layers will also be deleted.
*
* \param layers A list of layers to remove. Null pointers are ignored.
*
* \see takeMapLayer()
* \see removeMapLayer()
* \see removeAllMapLayers()
*/
//TODO QGIS 3.0 - add PyName alias to avoid list type conversion error
void removeMapLayers( const QList<QgsMapLayer *> &layers );
/**
* \brief
* Remove a layer from the store by layer \a id.
*
* The specified layer will be removed from the store. The layer will also be deleted.
*
* \param id ID of the layer to remove
*
* \see takeMapLayer()
* \see removeMapLayers()
* \see removeAllMapLayers()
*/
void removeMapLayer( const QString &id );
/**
* \brief
* Remove a \a layer from the store.
*
* The specified layer will be removed from the store. The layer will also be deleted.
*
* \param layer The layer to remove. Null pointers are ignored.
*
* \see takeMapLayer()
* \see removeMapLayers()
* \see removeAllMapLayers()
*/
void removeMapLayer( QgsMapLayer *layer );
/**
* Takes a \a layer from the store. If the layer was owned by the store, the
* layer will be returned without deleting it. The caller takes ownership of
* the layer and is responsible for deleting it.
* \see removeMapLayer()
*/
QgsMapLayer *takeMapLayer( QgsMapLayer *layer ) SIP_TRANSFERBACK;
/**
* Removes all registered layers. These layers will also be deleted.
*
* \note Calling this method will cause the removeAll() signal to
* be emitted.
* \see removeMapLayer()
* \see removeMapLayers()
*/
void removeAllMapLayers();
signals:
/**
* Emitted when one or more layers are about to be removed from the store.
*
* \param layerIds A list of IDs for the layers which are to be removed.
* \see layerWillBeRemoved()
* \see layersRemoved()
*/
void layersWillBeRemoved( const QStringList &layerIds );
/**
* Emitted when one or more layers are about to be removed from the store.
*
* \param layers A list of layers which are to be removed.
* \see layerWillBeRemoved()
* \see layersRemoved()
*/
void layersWillBeRemoved( const QList<QgsMapLayer *> &layers );
/**
* Emitted when a layer is about to be removed from the store.
*
* \param layerId The ID of the layer to be removed.
*
* \note Consider using layersWillBeRemoved() instead.
* \see layersWillBeRemoved()
* \see layerRemoved()
*/
void layerWillBeRemoved( const QString &layerId );
/**
* Emitted when a layer is about to be removed from the store.
*
* \param layer The layer to be removed.
*
* \note Consider using layersWillBeRemoved() instead.
* \see layersWillBeRemoved()
* \see layerRemoved()
*/
void layerWillBeRemoved( QgsMapLayer *layer );
/**
* Emitted after one or more layers were removed from the store.
*
* \param layerIds A list of IDs of the layers which were removed.
* \see layersWillBeRemoved()
*/
void layersRemoved( const QStringList &layerIds );
/**
* Emitted after a layer was removed from the store.
*
* \param layerId The ID of the layer removed.
*
* \note Consider using layersRemoved() instead
* \see layerWillBeRemoved()
*/
void layerRemoved( const QString &layerId );
/**
* Emitted when all layers are removed, before layersWillBeRemoved() and
* layerWillBeRemoved() signals are emitted. The layersWillBeRemoved() and
* layerWillBeRemoved() signals will still be emitted following this signal.
* You can use this signal to do easy (and fast) cleanup.
*/
void allLayersRemoved();
/**
* Emitted when one or more layers were added to the store.
*
* \param layers List of layers which have been added.
*
* \see legendLayersAdded()
* \see layerWasAdded()
*/
void layersAdded( const QList<QgsMapLayer *> &layers );
/**
* Emitted when a \a layer was added to the store.
*
* \note Consider using layersAdded() instead
* \see layersAdded()
*/
void layerWasAdded( QgsMapLayer *layer );
private slots:
void onMapLayerDeleted( QObject *obj );
private:
QMap<QString, QgsMapLayer *> mMapLayers;
};
#endif //QGSMAPLAYERSTORE_H

View File

@ -75,6 +75,7 @@ ADD_PYTHON_TEST(PyQgsMapCanvas test_qgsmapcanvas.py)
ADD_PYTHON_TEST(PyQgsMapCanvasAnnotationItem test_qgsmapcanvasannotationitem.py)
ADD_PYTHON_TEST(PyQgsMapLayer test_qgsmaplayer.py)
ADD_PYTHON_TEST(PyQgsMapLayerModel test_qgsmaplayermodel.py)
ADD_PYTHON_TEST(PyQgsMapLayerStore test_qgsmaplayerstore.py)
ADD_PYTHON_TEST(PyQgsMapRenderer test_qgsmaprenderer.py)
ADD_PYTHON_TEST(PyQgsMapRendererCache test_qgsmaprenderercache.py)
ADD_PYTHON_TEST(PyQgsMapThemeCollection test_qgsmapthemecollection.py)

View File

@ -0,0 +1,495 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsMapLayerStore.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Nyall Dawson'
__date__ = '2017-05'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.core import QgsMapLayerStore, QgsVectorLayer, QgsMapLayer
from qgis.testing import start_app, unittest
from qgis.PyQt.QtCore import QT_VERSION_STR
import sip
from qgis.PyQt.QtTest import QSignalSpy
start_app()
def createLayer(name):
return QgsVectorLayer("Point?field=x:string", name, "memory")
class TestQgsMapLayerStore(unittest.TestCase):
def setUp(self):
pass
def test_addMapLayer(self):
""" test adding individual map layers to store"""
store = QgsMapLayerStore()
l1 = createLayer('test')
self.assertEqual(store.addMapLayer(l1), l1)
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(store.count(), 1)
self.assertEqual(len(store), 1)
# adding a second layer should leave existing layers intact
l2 = createLayer('test2')
self.assertEqual(store.addMapLayer(l2), l2)
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(len(store.mapLayersByName('test2')), 1)
self.assertEqual(store.count(), 2)
self.assertEqual(len(store), 2)
def test_addMapLayerAlreadyAdded(self):
""" test that already added layers can't be readded to store """
store = QgsMapLayerStore()
l1 = createLayer('test')
store.addMapLayer(l1)
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(store.count(), 1)
self.assertEqual(store.addMapLayer(l1), None)
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(store.count(), 1)
self.assertEqual(len(store), 1)
def test_addMapLayerInvalid(self):
""" test that invalid map layers can't be added to store """
store = QgsMapLayerStore()
self.assertEqual(store.addMapLayer(QgsVectorLayer("Point?field=x:string", 'test', "xxx")), None)
self.assertEqual(len(store.mapLayersByName('test')), 0)
self.assertEqual(store.count(), 0)
def test_addMapLayerSignals(self):
""" test that signals are correctly emitted when adding map layer"""
store = QgsMapLayerStore()
layer_was_added_spy = QSignalSpy(store.layerWasAdded)
layers_added_spy = QSignalSpy(store.layersAdded)
l1 = createLayer('test')
store.addMapLayer(l1)
# can't seem to actually test the data which was emitted, so best we can do is test
# the signal count
self.assertEqual(len(layer_was_added_spy), 1)
self.assertEqual(len(layers_added_spy), 1)
store.addMapLayer(createLayer('test2'))
self.assertEqual(len(layer_was_added_spy), 2)
self.assertEqual(len(layers_added_spy), 2)
# try readding a layer already in the store
store.addMapLayer(l1)
# should be no extra signals emitted
self.assertEqual(len(layer_was_added_spy), 2)
self.assertEqual(len(layers_added_spy), 2)
def test_addMapLayers(self):
""" test adding multiple map layers to store """
store = QgsMapLayerStore()
l1 = createLayer('test')
l2 = createLayer('test2')
self.assertEqual(set(store.addMapLayers([l1, l2])), {l1, l2})
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(len(store.mapLayersByName('test2')), 1)
self.assertEqual(store.count(), 2)
# adding more layers should leave existing layers intact
l3 = createLayer('test3')
l4 = createLayer('test4')
self.assertEqual(set(store.addMapLayers([l3, l4])), {l3, l4})
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(len(store.mapLayersByName('test2')), 1)
self.assertEqual(len(store.mapLayersByName('test3')), 1)
self.assertEqual(len(store.mapLayersByName('test4')), 1)
self.assertEqual(store.count(), 4)
store.removeAllMapLayers()
def test_addMapLayersInvalid(self):
""" test that invalid map layersd can't be added to store """
store = QgsMapLayerStore()
self.assertEqual(store.addMapLayers([QgsVectorLayer("Point?field=x:string", 'test', "xxx")]), [])
self.assertEqual(len(store.mapLayersByName('test')), 0)
self.assertEqual(store.count(), 0)
def test_addMapLayersAlreadyAdded(self):
""" test that already added layers can't be readded to store """
store = QgsMapLayerStore()
l1 = createLayer('test')
self.assertEqual(store.addMapLayers([l1]), [l1])
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(store.count(), 1)
self.assertEqual(store.addMapLayers([l1]), [])
self.assertEqual(len(store.mapLayersByName('test')), 1)
self.assertEqual(store.count(), 1)
def test_addMapLayersSignals(self):
""" test that signals are correctly emitted when adding map layers"""
store = QgsMapLayerStore()
layer_was_added_spy = QSignalSpy(store.layerWasAdded)
layers_added_spy = QSignalSpy(store.layersAdded)
l1 = createLayer('test')
l2 = createLayer('test2')
store.addMapLayers([l1, l2])
# can't seem to actually test the data which was emitted, so best we can do is test
# the signal count
self.assertEqual(len(layer_was_added_spy), 2)
self.assertEqual(len(layers_added_spy), 1)
store.addMapLayers([createLayer('test3'), createLayer('test4')])
self.assertEqual(len(layer_was_added_spy), 4)
self.assertEqual(len(layers_added_spy), 2)
# try readding a layer already in the store
store.addMapLayers([l1, l2])
# should be no extra signals emitted
self.assertEqual(len(layer_was_added_spy), 4)
self.assertEqual(len(layers_added_spy), 2)
def test_mapLayerById(self):
""" test retrieving map layer by ID """
store = QgsMapLayerStore()
# test no crash with empty store
self.assertEqual(store.mapLayer('bad'), None)
self.assertEqual(store.mapLayer(None), None)
l1 = createLayer('test')
l2 = createLayer('test2')
store.addMapLayers([l1, l2])
self.assertEqual(store.mapLayer('bad'), None)
self.assertEqual(store.mapLayer(None), None)
self.assertEqual(store.mapLayer(l1.id()), l1)
self.assertEqual(store.mapLayer(l2.id()), l2)
def test_mapLayersByName(self):
""" test retrieving map layer by name """
store = QgsMapLayerStore()
# test no crash with empty store
self.assertEqual(store.mapLayersByName('bad'), [])
self.assertEqual(store.mapLayersByName(None), [])
l1 = createLayer('test')
l2 = createLayer('test2')
store.addMapLayers([l1, l2])
self.assertEqual(store.mapLayersByName('bad'), [])
self.assertEqual(store.mapLayersByName(None), [])
self.assertEqual(store.mapLayersByName('test'), [l1])
self.assertEqual(store.mapLayersByName('test2'), [l2])
#duplicate name
l3 = createLayer('test')
store.addMapLayer(l3)
self.assertEqual(set(store.mapLayersByName('test')), {l1, l3})
def test_mapLayers(self):
""" test retrieving map layers list """
store = QgsMapLayerStore()
# test no crash with empty store
self.assertEqual(store.mapLayers(), {})
l1 = createLayer('test')
l2 = createLayer('test2')
store.addMapLayers([l1, l2])
self.assertEqual(store.mapLayers(), {l1.id(): l1, l2.id(): l2})
def test_removeMapLayersById(self):
""" test removing map layers by ID """
store = QgsMapLayerStore()
# test no crash with empty store
store.removeMapLayersById(['bad'])
store.removeMapLayersById([None])
l1 = createLayer('test')
l2 = createLayer('test2')
l3 = createLayer('test3')
store.addMapLayers([l1, l2, l3])
self.assertEqual(store.count(), 3)
#remove bad layers
store.removeMapLayersById(['bad'])
self.assertEqual(store.count(), 3)
store.removeMapLayersById([None])
self.assertEqual(store.count(), 3)
# remove valid layers
l1_id = l1.id()
store.removeMapLayersById([l1_id])
self.assertEqual(store.count(), 2)
# double remove
store.removeMapLayersById([l1_id])
self.assertEqual(store.count(), 2)
# test that layer has been deleted
self.assertTrue(sip.isdeleted(l1))
# remove multiple
store.removeMapLayersById([l2.id(), l3.id()])
self.assertEqual(store.count(), 0)
self.assertTrue(sip.isdeleted(l2))
# try removing a layer not in the store
l4 = createLayer('test4')
store.removeMapLayersById([l4.id()])
self.assertFalse(sip.isdeleted(l4))
def test_removeMapLayersByLayer(self):
""" test removing map layers by layer"""
store = QgsMapLayerStore()
# test no crash with empty store
store.removeMapLayers([None])
l1 = createLayer('test')
l2 = createLayer('test2')
l3 = createLayer('test3')
store.addMapLayers([l1, l2, l3])
self.assertEqual(store.count(), 3)
#remove bad layers
store.removeMapLayers([None])
self.assertEqual(store.count(), 3)
# remove valid layers
store.removeMapLayers([l1])
self.assertEqual(store.count(), 2)
# test that layer has been deleted
self.assertTrue(sip.isdeleted(l1))
# remove multiple
store.removeMapLayers([l2, l3])
self.assertEqual(store.count(), 0)
self.assertTrue(sip.isdeleted(l2))
self.assertTrue(sip.isdeleted(l3))
def test_removeMapLayerById(self):
""" test removing a map layer by ID """
store = QgsMapLayerStore()
# test no crash with empty store
store.removeMapLayer('bad')
store.removeMapLayer(None)
l1 = createLayer('test')
l2 = createLayer('test2')
store.addMapLayers([l1, l2])
self.assertEqual(store.count(), 2)
#remove bad layers
store.removeMapLayer('bad')
self.assertEqual(store.count(), 2)
store.removeMapLayer(None)
self.assertEqual(store.count(), 2)
# remove valid layers
l1_id = l1.id()
store.removeMapLayer(l1_id)
self.assertEqual(store.count(), 1)
# double remove
store.removeMapLayer(l1_id)
self.assertEqual(store.count(), 1)
# test that layer has been deleted
self.assertTrue(sip.isdeleted(l1))
# remove second layer
store.removeMapLayer(l2.id())
self.assertEqual(store.count(), 0)
self.assertTrue(sip.isdeleted(l2))
# try removing a layer not in the store
l3 = createLayer('test3')
store.removeMapLayer(l3.id())
self.assertFalse(sip.isdeleted(l3))
def test_removeMapLayerByLayer(self):
""" test removing a map layer by layer """
store = QgsMapLayerStore()
# test no crash with empty store
store.removeMapLayer('bad')
store.removeMapLayer(None)
l1 = createLayer('test')
l2 = createLayer('test2')
store.addMapLayers([l1, l2])
self.assertEqual(store.count(), 2)
#remove bad layers
store.removeMapLayer(None)
self.assertEqual(store.count(), 2)
l3 = createLayer('test3')
store.removeMapLayer(l3)
self.assertEqual(store.count(), 2)
# remove valid layers
store.removeMapLayer(l1)
self.assertEqual(store.count(), 1)
# test that layer has been deleted
self.assertTrue(sip.isdeleted(l1))
# remove second layer
store.removeMapLayer(l2)
self.assertEqual(store.count(), 0)
self.assertTrue(sip.isdeleted(l2))
# try removing a layer not in the store
l3 = createLayer('test3')
store.removeMapLayer(l3)
self.assertFalse(sip.isdeleted(l3))
def test_removeAllMapLayers(self):
""" test removing all map layers from store """
store = QgsMapLayerStore()
l1 = createLayer('test')
l2 = createLayer('test2')
store.addMapLayers([l1, l2])
self.assertEqual(store.count(), 2)
store.removeAllMapLayers()
self.assertEqual(store.count(), 0)
self.assertEqual(store.mapLayersByName('test'), [])
self.assertEqual(store.mapLayersByName('test2'), [])
def test_addRemoveLayersSignals(self):
""" test that signals are correctly emitted when removing map layers"""
store = QgsMapLayerStore()
layers_will_be_removed_spy = QSignalSpy(store.layersWillBeRemoved)
layer_will_be_removed_spy_str = QSignalSpy(store.layerWillBeRemoved[str])
layer_will_be_removed_spy_layer = QSignalSpy(store.layerWillBeRemoved[QgsMapLayer])
layers_removed_spy = QSignalSpy(store.layersRemoved)
layer_removed_spy = QSignalSpy(store.layerRemoved)
remove_all_spy = QSignalSpy(store.allLayersRemoved)
l1 = createLayer('l1')
l2 = createLayer('l2')
l3 = createLayer('l3')
l4 = createLayer('l4')
store.addMapLayers([l1, l2, l3, l4])
# remove 1 layer
store.removeMapLayer(l1)
# can't seem to actually test the data which was emitted, so best we can do is test
# the signal count
self.assertEqual(len(layers_will_be_removed_spy), 1)
self.assertEqual(len(layer_will_be_removed_spy_str), 1)
self.assertEqual(len(layer_will_be_removed_spy_layer), 1)
self.assertEqual(len(layers_removed_spy), 1)
self.assertEqual(len(layer_removed_spy), 1)
self.assertEqual(len(remove_all_spy), 0)
self.assertEqual(store.count(), 3)
# remove 2 layers at once
store.removeMapLayersById([l2.id(), l3.id()])
self.assertEqual(len(layers_will_be_removed_spy), 2)
self.assertEqual(len(layer_will_be_removed_spy_str), 3)
self.assertEqual(len(layer_will_be_removed_spy_layer), 3)
self.assertEqual(len(layers_removed_spy), 2)
self.assertEqual(len(layer_removed_spy), 3)
self.assertEqual(len(remove_all_spy), 0)
self.assertEqual(store.count(), 1)
# remove all
store.removeAllMapLayers()
self.assertEqual(len(layers_will_be_removed_spy), 3)
self.assertEqual(len(layer_will_be_removed_spy_str), 4)
self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
self.assertEqual(len(layers_removed_spy), 3)
self.assertEqual(len(layer_removed_spy), 4)
self.assertEqual(len(remove_all_spy), 1)
#remove some layers which aren't in the store
store.removeMapLayersById(['asdasd'])
self.assertEqual(len(layers_will_be_removed_spy), 3)
self.assertEqual(len(layer_will_be_removed_spy_str), 4)
self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
self.assertEqual(len(layers_removed_spy), 3)
self.assertEqual(len(layer_removed_spy), 4)
self.assertEqual(len(remove_all_spy), 1)
l5 = createLayer('test5')
store.removeMapLayer(l5)
self.assertEqual(len(layers_will_be_removed_spy), 3)
self.assertEqual(len(layer_will_be_removed_spy_str), 4)
self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
self.assertEqual(len(layers_removed_spy), 3)
self.assertEqual(len(layer_removed_spy), 4)
self.assertEqual(len(remove_all_spy), 1)
def test_RemoveLayerShouldNotSegFault(self):
store = QgsMapLayerStore()
# Should not segfault
store.removeMapLayersById(['not_exists'])
store.removeMapLayer('not_exists2')
# check also that the removal of an unexistent layer does not insert a null layer
for k, layer in list(store.mapLayers().items()):
assert(layer is not None)
def testTakeLayer(self):
# test taking ownership of a layer from the store
l1 = createLayer('l1')
l2 = createLayer('l2')
store = QgsMapLayerStore()
# add one layer to store
store.addMapLayer(l1)
self.assertEqual(store.mapLayers(), {l1.id(): l1})
self.assertEqual(l1.parent(), store)
# try taking some layers which don't exist in store
self.assertFalse(store.takeMapLayer(None))
self.assertFalse(store.takeMapLayer(l2))
# but l2 should still exist..
self.assertTrue(l2.isValid())
# take layer from store
self.assertEqual(store.takeMapLayer(l1), l1)
self.assertFalse(store.mapLayers()) # no layers left
# but l1 should still exist
self.assertTrue(l1.isValid())
# layer should have no parent now
self.assertFalse(l1.parent())
# destroy store
store = None
self.assertTrue(l1.isValid())
if __name__ == '__main__':
unittest.main()