Skip to content

Commit bd3cf76

Browse files
author
Hugo Mercier
authoredAug 31, 2016
Merge pull request #3320 from mhugo/fix_snapping2
Data dependency between layers + snapping fix
2 parents 504badb + 0749ba4 commit bd3cf76

27 files changed

+953
-219
lines changed
 

‎ci/travis/linux/qt5/blacklist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PyQgsSipCoverage
99
PyQgsSpatialiteProvider
1010
PyQgsVirtualLayerDefinition
1111
PyQgsVirtualLayerProvider
12+
PyQgsLayerDependencies
Code has comments. Press enter to view.
1213
qgis_composermapgridtest
1314
qgis_composerutils
1415
ProcessingGrass7AlgorithmsImageryTest

‎images/images.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@
665665
<file>themes/default/mActionAddAfsLayer.svg</file>
666666
<file>themes/default/mIconFormSelect.svg</file>
667667
<file>themes/default/mActionMultiEdit.svg</file>
668+
<file>themes/default/dependencies.svg</file>
668669
</qresource>
669670
<qresource prefix="/images/tips">
670671
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Lines changed: 151 additions & 0 deletions
Loading

‎python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
%Include qgslogger.sip
8181
%Include qgsmaphittest.sip
8282
%Include qgsmaplayer.sip
83+
%Include qgsmaplayerdependency.sip
8384
%Include qgsmaplayerlegend.sip
8485
%Include qgsmaplayermodel.sip
8586
%Include qgsmaplayerproxymodel.sip

‎python/core/qgsmaplayer.sip

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,32 @@ class QgsMapLayer : QObject
676676
*/
677677
void emitStyleChanged();
678678

679+
/**
680+
* Sets the list of layers that may modify data/geometries of this layer when modified.
681+
* @see dependencies()
682+
*
683+
* @param layersIds IDs of the layers that this layer depends on
684+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
685+
*/
686+
virtual bool setDataDependencies( const QSet<QString>& layersIds );
687+
688+
/**
689+
* Sets the list of layers that may modify data/geometries of this layer when modified.
690+
* @see dependencies()
691+
*
692+
* @param set of QgsMapLayerDependency. Only user-defined dependencies will be added
693+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
694+
*/
695+
bool setDataDependencies( const QSet<QgsMapLayerDependency>& layers );
696+
697+
/**
698+
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
699+
* as well as dependencies given by the provider
700+
*
701+
* @returns a set of QgsMapLayerDependency
702+
*/
703+
virtual QSet<QgsMapLayerDependency> dependencies() const;
704+
679705
signals:
680706

681707
/** Emit a signal with status (e.g. to be caught by QgisApp and display a msg on status bar) */
@@ -766,4 +792,7 @@ class QgsMapLayer : QObject
766792
void appendError( const QgsErrorMessage &error );
767793
/** Set error message */
768794
void setError( const QgsError &error );
795+
796+
//! Checks if new change dependency candidates introduce a cycle
797+
bool hasDataDependencyCycle( const QSet<QgsMapLayerDependency>& layersIds ) const;
769798
};

‎python/core/qgsmaplayerdependency.sip

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class QgsMapLayerDependency
2+
{
3+
%TypeHeaderCode
4+
#include "qgsmaplayerdependency.h"
5+
%End
6+
public:
7+
//! Type of dependency
8+
enum Type
9+
{
10+
PresenceDependency = 1, // The layer must be already present (in the registry) for this dependency to be resolved
11+
DataDependency = 2 // The layer may be invalidated by data changes on another layer
12+
};
13+
14+
//! Origin of the dependency
15+
enum Origin
16+
{
17+
FromProvider = 0, // Dependency given by the provider, the user cannot change it
18+
FromUser = 1 // Dependency given by the user
19+
};
20+
21+
//! Standard constructor
22+
QgsMapLayerDependency( QString layerId, Type type = DataDependency, Origin origin = FromUser );
23+
24+
//! Return the dependency type
25+
Type type() const;
26+
27+
//! Return the dependency origin
28+
Origin origin() const;
29+
30+
//! Return the ID of the layer this dependency depends on
31+
QString layerId() const;
32+
33+
bool operator==( const QgsMapLayerDependency& other ) const;
34+
};
35+
36+

‎python/core/qgsvectordataprovider.sip

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ class QgsVectorDataProvider : QgsDataProvider
368368
/**
369369
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
370370
*/
371-
virtual QSet<QString> layerDependencies() const;
371+
virtual QSet<QgsMapLayerDependency> dependencies() const;
372372

373373
signals:
374374
/** Signals an error in this provider */

‎python/core/qgsvectorlayer.sip

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,23 @@ class QgsVectorLayer : QgsMapLayer
422422
const QList<QgsVectorJoinInfo> vectorJoins() const;
423423

424424
/**
425-
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
425+
* Sets the list of layers that may modify data/geometries of this layer when modified.
426+
* This is meant mainly to declare database triggers between layers.
427+
* When one of these layers is modified (feature added/deleted or geometry changed),
428+
* dataChanged() will be emitted, allowing users of this layer to refresh / update it.
429+
*
430+
* @param layersIds IDs of the layers that this layer depends on
431+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
432+
*/
433+
bool setDataDependencies( const QSet<QString>& layersIds );
434+
435+
/**
436+
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
437+
* as well as dependencies given by the provider
438+
*
439+
* @returns a set of QgsMapLayerDependency
426440
*/
427-
virtual QSet<QString> layerDependencies() const;
441+
virtual QSet<QgsMapLayerDependency> dependencies() const;
428442

429443
/**
430444
* Add a new field which is calculated by the expression specified

‎src/app/qgsvectorlayerproperties.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include "qgsdatasourceuri.h"
5353
#include "qgsrenderer.h"
5454
#include "qgsexpressioncontext.h"
55+
#include "layertree/qgslayertreelayer.h"
5556

5657
#include <QMessageBox>
5758
#include <QDir>
@@ -291,6 +292,27 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(
291292

292293
QString title = QString( tr( "Layer Properties - %1" ) ).arg( mLayer->name() );
293294
restoreOptionsBaseUi( title );
295+
296+
mLayersDependenciesTreeGroup.reset( QgsProject::instance()->layerTreeRoot()->clone() );
297+
QgsLayerTreeLayer* layer = mLayersDependenciesTreeGroup->findLayer( mLayer->id() );
298+
layer->parent()->takeChild( layer );
299+
mLayersDependenciesTreeModel.reset( new QgsLayerTreeModel( mLayersDependenciesTreeGroup.data() ) );
300+
// use visibility as selection
301+
mLayersDependenciesTreeModel->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );
302+
303+
mLayersDependenciesTreeGroup->setVisible( Qt::Unchecked );
304+
305+
QSet<QString> dependencySources;
306+
Q_FOREACH ( const QgsMapLayerDependency& dep, mLayer->dependencies() )
307+
{
308+
dependencySources << dep.layerId();
309+
}
310+
Q_FOREACH ( QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
311+
{
312+
layer->setVisible( dependencySources.contains( layer->layerId() ) ? Qt::Checked : Qt::Unchecked );
313+
}
314+
315+
mLayersDependenciesTreeView->setModel( mLayersDependenciesTreeModel.data() );
294316
} // QgsVectorLayerProperties ctor
295317

296318

@@ -558,6 +580,18 @@ void QgsVectorLayerProperties::apply()
558580
QgsExpressionContextUtils::setLayerVariables( mLayer, mVariableEditor->variablesInActiveScope() );
559581
updateVariableEditor();
560582

583+
// save layer dependencies
584+
QSet<QString> deps;
585+
Q_FOREACH ( const QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
586+
{
587+
if ( layer->isVisible() )
588+
deps << layer->layerId();
589+
}
590+
if ( ! mLayer->setDataDependencies( deps ) )
591+
{
592+
QMessageBox::warning( nullptr, tr( "Dependency cycle" ), tr( "This configuration introduces a cycle in data dependencies and will be ignored" ) );
593+
}
594+
561595
// update symbology
562596
emit refreshLegend( mLayer->id() );
563597

‎src/app/qgsvectorlayerproperties.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "qgscontexthelp.h"
2626
#include "qgsmaplayerstylemanager.h"
2727
#include "qgsvectorlayer.h"
28+
#include "layertree/qgslayertreemodel.h"
29+
#include "layertree/qgslayertreegroup.h"
2830

2931
class QgsMapLayer;
3032

@@ -193,6 +195,9 @@ class APP_EXPORT QgsVectorLayerProperties : public QgsOptionsDialogBase, private
193195

194196
QgsExpressionContext createExpressionContext() const override;
195197

198+
QScopedPointer<QgsLayerTreeGroup> mLayersDependenciesTreeGroup;
199+
QScopedPointer<QgsLayerTreeModel> mLayersDependenciesTreeModel;
200+
196201
private slots:
197202
void openPanel( QgsPanelWidget* panel );
198203
};

‎src/core/qgslayerdefinition.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,22 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
111111
joinNode.toElement().setAttribute( "joinLayerId", newid );
112112
}
113113
}
114+
115+
// change IDs of dependencies
116+
QDomNodeList dataDeps = doc.elementsByTagName( "dataDependencies" );
117+
for ( int i = 0; i < dataDeps.size(); i++ )
118+
{
119+
QDomNodeList layers = dataDeps.at( i ).childNodes();
120+
for ( int j = 0; j < layers.size(); j++ )
121+
{
122+
QDomElement elt = layers.at( j ).toElement();
123+
if ( elt.attribute( "id" ) == oldid )
124+
{
125+
elt.setAttribute( "id", newid );
126+
}
127+
}
128+
}
129+
114130
}
115131

116132
QDomElement layerTreeElem = doc.documentElement().firstChildElement( "layer-tree-group" );

‎src/core/qgsmaplayer.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,3 +1680,78 @@ void QgsMapLayer::setExtent( const QgsRectangle &r )
16801680
{
16811681
mExtent = r;
16821682
}
1683+
1684+
static QList<const QgsMapLayer*> _depOutEdges( const QgsMapLayer* vl, const QgsMapLayer* that, const QSet<QgsMapLayerDependency>& layers )
1685+
{
1686+
QList<const QgsMapLayer*> lst;
1687+
if ( vl == that )
1688+
{
1689+
Q_FOREACH ( const QgsMapLayerDependency& dep, layers )
1690+
{
1691+
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) )
1692+
lst << l;
1693+
}
1694+
}
1695+
else
1696+
{
1697+
Q_FOREACH ( const QgsMapLayerDependency& dep, vl->dependencies() )
1698+
{
1699+
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) )
1700+
lst << l;
1701+
}
1702+
}
1703+
return lst;
1704+
}
1705+
1706+
static bool _depHasCycleDFS( const QgsMapLayer* n, QHash<const QgsMapLayer*, int>& mark, const QgsMapLayer* that, const QSet<QgsMapLayerDependency>& layers )
1707+
{
1708+
if ( mark.value( n ) == 1 ) // temporary
1709+
return true;
1710+
if ( mark.value( n ) == 0 ) // not visited
1711+
{
1712+
mark[n] = 1; // temporary
1713+
Q_FOREACH ( const QgsMapLayer* m, _depOutEdges( n, that, layers ) )
1714+
{
1715+
if ( _depHasCycleDFS( m, mark, that, layers ) )
1716+
return true;
1717+
}
1718+
mark[n] = 2; // permanent
1719+
}
1720+
return false;
1721+
}
1722+
1723+
bool QgsMapLayer::hasDataDependencyCycle( const QSet<QgsMapLayerDependency>& layers ) const
1724+
{
1725+
QHash<const QgsMapLayer*, int> marks;
1726+
return _depHasCycleDFS( this, marks, this, layers );
1727+
}
1728+
1729+
QSet<QgsMapLayerDependency> QgsMapLayer::dependencies() const
1730+
{
1731+
return mDataDependencies;
1732+
}
1733+
1734+
bool QgsMapLayer::setDataDependencies( const QSet<QString>& layersIds )
1735+
{
1736+
QSet<QgsMapLayerDependency> deps;
1737+
Q_FOREACH ( QString layerId, layersIds )
1738+
{
1739+
deps << QgsMapLayerDependency( layerId );
1740+
}
1741+
if ( hasDataDependencyCycle( deps ) )
1742+
return false;
1743+
1744+
mDataDependencies = deps;
1745+
return true;
1746+
}
1747+
1748+
bool QgsMapLayer::setDataDependencies( const QSet<QgsMapLayerDependency>& layers )
1749+
{
1750+
QSet<QString> deps;
1751+
Q_FOREACH ( const QgsMapLayerDependency& dep, layers )
1752+
{
1753+
if ( dep.origin() == QgsMapLayerDependency::FromUser && dep.type() == QgsMapLayerDependency::DataDependency )
1754+
deps << dep.layerId();
1755+
}
1756+
return setDataDependencies( deps );
1757+
}

‎src/core/qgsmaplayer.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "qgsrectangle.h"
3333
#include "qgscoordinatereferencesystem.h"
3434
#include "qgsrendercontext.h"
35+
#include "qgsmaplayerdependency.h"
3536

3637
class QgsMapLayerLegend;
3738
class QgsMapLayerRenderer;
@@ -698,6 +699,32 @@ class CORE_EXPORT QgsMapLayer : public QObject
698699
*/
699700
void emitStyleChanged();
700701

702+
/**
703+
* Sets the list of layers that may modify data/geometries of this layer when modified.
704+
* @see dependencies()
705+
*
706+
* @param layersIds IDs of the layers that this layer depends on
707+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
708+
*/
709+
virtual bool setDataDependencies( const QSet<QString>& layersIds );
710+
711+
/**
712+
* Sets the list of layers that may modify data/geometries of this layer when modified.
713+
* @see dependencies()
714+
*
715+
* @param layers set of QgsMapLayerDependency. Only user-defined dependencies will be added
716+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
717+
*/
718+
bool setDataDependencies( const QSet<QgsMapLayerDependency>& layers );
719+
720+
/**
721+
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
722+
* as well as dependencies given by the provider
723+
*
724+
* @returns a set of QgsMapLayerDependency
725+
*/
726+
virtual QSet<QgsMapLayerDependency> dependencies() const;
727+
701728
signals:
702729

703730
/** Emit a signal with status (e.g. to be caught by QgisApp and display a msg on status bar) */
@@ -836,6 +863,12 @@ class CORE_EXPORT QgsMapLayer : public QObject
836863
/** \brief Error */
837864
QgsError mError;
838865

866+
//! List of layers that may modify this layer on modification
867+
QSet<QgsMapLayerDependency> mDataDependencies;
868+
869+
//! Checks whether a new set of data dependencies will introduce a cycle
870+
bool hasDataDependencyCycle( const QSet<QgsMapLayerDependency>& layers ) const;
871+
839872
private:
840873
/**
841874
* This method returns true by default but can be overwritten to specify

‎src/core/qgsmaplayerdependency.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
2+
/***************************************************************************
3+
qgsmaplayerdependency.h - description
4+
-------------------
5+
begin : September 2016
6+
copyright : (C) 2016 by Hugo Mercier
7+
email : hugo dot mercier at oslandia dot com
8+
***************************************************************************/
9+
10+
/***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************/
18+
19+
#ifndef QGSMAPLAYERDEPENDENCY_H
20+
#define QGSMAPLAYERDEPENDENCY_H
21+
22+
#include <QString>
23+
24+
/** \ingroup core
25+
* This class models dependencies with or between map layers.
26+
* A dependency is defined by a layer ID, a type and an origin.
27+
* The two combinations of type/origin that are currently supported are:
28+
* - PresenceDependency && FromProvider: virtual layers for instance which may depend on other layers already loaded to work
29+
* - DataDependency && FromUser: dependencies given by the user, mainly to represent database triggers
30+
*
31+
* @note added in QGIS 3.0
32+
*/
33+
class CORE_EXPORT QgsMapLayerDependency
34+
{
35+
public:
36+
//! Type of dependency
37+
enum Type
38+
{
39+
PresenceDependency = 1, //< The layer must be already present (in the registry) for this dependency to be resolved
40+
DataDependency = 2 //< The layer may be invalidated by data changes on another layer
41+
};
42+
43+
//! Origin of the dependency
44+
enum Origin
45+
{
46+
FromProvider = 0, //< Dependency given by the provider, the user cannot change it
47+
FromUser = 1 //< Dependency given by the user
48+
};
49+
50+
//! Standard constructor
51+
QgsMapLayerDependency( QString layerId, Type type = DataDependency, Origin origin = FromUser ) :
52+
mType( type ),
53+
mOrigin( origin ),
54+
mLayerId( layerId )
55+
{}
56+
57+
//! Return the dependency type
58+
Type type() const { return mType; }
59+
60+
//! Return the dependency origin
61+
Origin origin() const { return mOrigin; }
62+
63+
//! Return the ID of the layer this dependency depends on
64+
QString layerId() const { return mLayerId; }
65+
66+
//! Comparison operator
67+
bool operator==( const QgsMapLayerDependency& other ) const
68+
{
69+
return layerId() == other.layerId() && origin() == other.origin() && type() == other.type();
70+
}
71+
private:
72+
Type mType;
73+
Origin mOrigin;
74+
QString mLayerId;
75+
};
76+
77+
/**
78+
* global qHash function for QgsMapLayerDependency, so that it can be used in a QSet
79+
*/
80+
inline uint qHash( const QgsMapLayerDependency& dep )
81+
{
82+
return qHash( dep.layerId() ) + dep.origin() + dep.type();
83+
}
84+
85+
#endif

‎src/core/qgspointlocator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ QgsPointLocator::QgsPointLocator( QgsVectorLayer* layer, const QgsCoordinateRefe
629629
connect( mLayer, SIGNAL( featureAdded( QgsFeatureId ) ), this, SLOT( onFeatureAdded( QgsFeatureId ) ) );
630630
connect( mLayer, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SLOT( onFeatureDeleted( QgsFeatureId ) ) );
631631
connect( mLayer, SIGNAL( geometryChanged( QgsFeatureId, const QgsGeometry& ) ), this, SLOT( onGeometryChanged( QgsFeatureId, const QgsGeometry& ) ) );
632+
connect( mLayer, SIGNAL( dataChanged() ), this, SLOT( destroyIndex() ) );
632633
}
633634

634635

‎src/core/qgspointlocator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ class CORE_EXPORT QgsPointLocator : public QObject
207207

208208
protected:
209209
bool rebuildIndex( int maxFeaturesToIndex = -1 );
210+
protected slots:
210211
void destroyIndex();
211-
212212
private slots:
213213
void onFeatureAdded( QgsFeatureId fid );
214214
void onFeatureDeleted( QgsFeatureId fid );

‎src/core/qgsproject.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,12 @@ bool QgsProject::read()
889889
mVisibilityPresetCollection.reset( new QgsMapThemeCollection() );
890890
mVisibilityPresetCollection->readXml( *doc );
891891

892+
// reassign change dependencies now that all layers are loaded
893+
QMap<QString, QgsMapLayer*> existingMaps = QgsMapLayerRegistry::instance()->mapLayers();
894+
for ( QMap<QString, QgsMapLayer*>::iterator it = existingMaps.begin(); it != existingMaps.end(); it++ )
895+
{
896+
it.value()->setDataDependencies( it.value()->dependencies() );
897+
}
892898

893899
// read the project: used by map canvas and legend
894900
emit readProject( *doc );
@@ -957,6 +963,8 @@ QgsExpressionContext QgsProject::createExpressionContext() const
957963

958964
void QgsProject::onMapLayersAdded( const QList<QgsMapLayer*>& layers )
959965
{
966+
QMap<QString, QgsMapLayer*> existingMaps = QgsMapLayerRegistry::instance()->mapLayers();
967+
960968
Q_FOREACH ( QgsMapLayer* layer, layers )
961969
{
962970
QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer*>( layer );
@@ -985,6 +993,17 @@ void QgsProject::onMapLayersAdded( const QList<QgsMapLayer*>& layers )
985993
}
986994

987995
connect( layer, SIGNAL( configChanged() ), this, SLOT( setDirty() ) );
996+
997+
// check if we have to update connections for layers with dependencies
998+
for ( QMap<QString, QgsMapLayer*>::iterator it = existingMaps.begin(); it != existingMaps.end(); it++ )
999+
{
1000+
QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
1001+
if ( deps.contains( layer->id() ) )
1002+
{
1003+
// reconnect to change signals
1004+
it.value()->setDataDependencies( deps );
1005+
}
1006+
}
9881007
}
9891008
}
9901009

‎src/core/qgsvectordataprovider.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,9 +613,9 @@ void QgsVectorDataProvider::pushError( const QString& msg )
613613
emit raiseError( msg );
614614
}
615615

616-
QSet<QString> QgsVectorDataProvider::layerDependencies() const
616+
QSet<QgsMapLayerDependency> QgsVectorDataProvider::dependencies() const
617617
{
618-
return QSet<QString>();
618+
return QSet<QgsMapLayerDependency>();
619619
}
620620

621621
QgsGeometry* QgsVectorDataProvider::convertToProviderType( const QgsGeometry& geom ) const

‎src/core/qgsvectordataprovider.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class QTextCodec;
2727
#include "qgsdataprovider.h"
2828
#include "qgsfeature.h"
2929
#include "qgsaggregatecalculator.h"
30+
#include "qgsmaplayerdependency.h"
3031

3132
typedef QList<int> QgsAttributeList;
3233
typedef QSet<int> QgsAttributeIds;
@@ -428,7 +429,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
428429
/**
429430
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
430431
*/
431-
virtual QSet<QString> layerDependencies() const;
432+
virtual QSet<QgsMapLayerDependency> dependencies() const;
432433

433434
signals:
434435
/** Signals an error in this provider */

‎src/core/qgsvectorlayer.cpp

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,17 @@ bool QgsVectorLayer::readXml( const QDomNode& layer_node )
14561456
}
14571457
updateFields();
14581458

1459+
QDomNode depsNode = layer_node.namedItem( "dataDependencies" );
1460+
QDomNodeList depsNodes = depsNode.childNodes();
1461+
QSet<QString> sources;
1462+
for ( int i = 0; i < depsNodes.count(); i++ )
1463+
{
1464+
QDomNode node = depsNodes.at( i );
1465+
QString source = depsNodes.at( i ).toElement().attribute( "id" );
1466+
sources << source;
1467+
}
1468+
setDataDependencies( sources );
1469+
14591470
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
14601471

14611472
return mValid; // should be true if read successfully
@@ -1628,10 +1639,12 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
16281639

16291640
// dependencies
16301641
QDomElement dependenciesElement = document.createElement( "layerDependencies" );
1631-
Q_FOREACH ( QString layerId, layerDependencies() )
1642+
Q_FOREACH ( const QgsMapLayerDependency& dep, dependencies() )
16321643
{
1644+
if ( dep.type() != QgsMapLayerDependency::PresenceDependency )
1645+
continue;
16331646
QDomElement depElem = document.createElement( "layer" );
1634-
depElem.setAttribute( "id", layerId );
1647+
depElem.setAttribute( "id", dep.layerId() );
16351648
dependenciesElement.appendChild( depElem );
16361649
}
16371650
layer_node.appendChild( dependenciesElement );
@@ -1647,6 +1660,21 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
16471660
}
16481661
layer_node.appendChild( defaultsElem );
16491662

1663+
// change dependencies
1664+
QDomElement dataDependenciesElement = document.createElement( "dataDependencies" );
1665+
Q_FOREACH ( const QgsMapLayerDependency& dep, dependencies() )
1666+
{
1667+
if ( dep.type() != QgsMapLayerDependency::DataDependency )
1668+
continue;
1669+
QDomElement depElem = document.createElement( "layer" );
1670+
depElem.setAttribute( "id", dep.layerId() );
1671+
dataDependenciesElement.appendChild( depElem );
1672+
}
1673+
layer_node.appendChild( dataDependenciesElement );
1674+
1675+
// save expression fields
1676+
mExpressionFieldBuffer->writeXml( layer_node, document );
1677+
16501678
writeStyleManager( layer_node, document );
16511679

16521680
// renderer specific settings
@@ -4061,11 +4089,59 @@ QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &theResultFl
40614089
return QgsMapLayer::loadNamedStyle( theURI, theResultFlag );
40624090
}
40634091

4064-
QSet<QString> QgsVectorLayer::layerDependencies() const
4092+
QSet<QgsMapLayerDependency> QgsVectorLayer::dependencies() const
40654093
{
40664094
if ( mDataProvider )
4095+
return mDataProvider->dependencies() + mDataDependencies;
4096+
return mDataDependencies;
4097+
}
4098+
4099+
bool QgsVectorLayer::setDataDependencies( const QSet<QString>& layersIds )
4100+
{
4101+
QSet<QgsMapLayerDependency> deps;
4102+
Q_FOREACH ( QString layerId, layersIds )
40674103
{
4068-
return mDataProvider->layerDependencies();
4104+
deps << QgsMapLayerDependency( layerId );
40694105
}
4070-
return QSet<QString>();
4106+
4107+
if ( hasDataDependencyCycle( deps ) )
4108+
return false;
4109+
4110+
QSet<QgsMapLayerDependency> toAdd = deps - dependencies();
4111+
4112+
// disconnect layers that are not present in the list of dependencies anymore
4113+
Q_FOREACH ( const QgsMapLayerDependency& dep, mDataDependencies )
4114+
{
4115+
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) );
4116+
if ( lyr == nullptr )
4117+
continue;
4118+
disconnect( lyr, SIGNAL( featureAdded( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
4119+
disconnect( lyr, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
4120+
disconnect( lyr, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SIGNAL( dataChanged() ) );
4121+
disconnect( lyr, SIGNAL( dataChanged() ), this, SIGNAL( dataChanged() ) );
4122+
}
4123+
4124+
// assign new dependencies
4125+
if ( mDataProvider )
4126+
mDataDependencies = mDataProvider->dependencies() + deps;
4127+
else
4128+
mDataDependencies = deps;
4129+
4130+
// connect to new layers
4131+
Q_FOREACH ( const QgsMapLayerDependency& dep, mDataDependencies )
4132+
{
4133+
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) );
4134+
if ( lyr == nullptr )
4135+
continue;
4136+
connect( lyr, SIGNAL( featureAdded( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
4137+
connect( lyr, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
4138+
connect( lyr, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SIGNAL( dataChanged() ) );
4139+
connect( lyr, SIGNAL( dataChanged() ), this, SIGNAL( dataChanged() ) );
4140+
}
4141+
4142+
// if new layers are present, emit a data change
4143+
if ( ! toAdd.isEmpty() )
4144+
emit dataChanged();
4145+
4146+
return true;
40714147
}

‎src/core/qgsvectorlayer.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,23 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
515515
const QList<QgsVectorJoinInfo> vectorJoins() const;
516516

517517
/**
518-
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
518+
* Sets the list of layers that may modify data/geometries of this layer when modified.
519+
* This is meant mainly to declare database triggers between layers.
520+
* When one of these layers is modified (feature added/deleted or geometry changed),
521+
* dataChanged() will be emitted, allowing users of this layer to refresh / update it.
522+
*
523+
* @param layersIds IDs of the layers that this layer depends on
524+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
525+
*/
526+
bool setDataDependencies( const QSet<QString>& layersIds ) override;
527+
528+
/**
529+
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
530+
* as well as dependencies given by the provider
531+
*
532+
* @returns a set of QgsMapLayerDependency
519533
*/
520-
virtual QSet<QString> layerDependencies() const;
534+
virtual QSet<QgsMapLayerDependency> dependencies() const override;
521535

522536
/**
523537
* Add a new field which is calculated by the expression specified

‎src/providers/virtual/qgsvirtuallayerprovider.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,13 +601,13 @@ QgsAttributeList QgsVirtualLayerProvider::pkAttributeIndexes() const
601601
return QgsAttributeList();
602602
}
603603

604-
QSet<QString> QgsVirtualLayerProvider::layerDependencies() const
604+
QSet<QgsMapLayerDependency> QgsVirtualLayerProvider::dependencies() const
605605
{
606-
QSet<QString> deps;
606+
QSet<QgsMapLayerDependency> deps;
607607
Q_FOREACH ( const QgsVirtualLayerDefinition::SourceLayer& l, mDefinition.sourceLayers() )
608608
{
609609
if ( l.isReferenced() )
610-
deps << l.reference();
610+
deps << QgsMapLayerDependency( l.reference(), QgsMapLayerDependency::PresenceDependency, QgsMapLayerDependency::FromProvider );
611611
}
612612
return deps;
613613
}

‎src/providers/virtual/qgsvirtuallayerprovider.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class QgsVirtualLayerProvider: public QgsVectorDataProvider
7979
QgsAttributeList pkAttributeIndexes() const override;
8080

8181
/** Get the list of layer ids on which this layer depends */
82-
QSet<QString> layerDependencies() const override;
82+
QSet<QgsMapLayerDependency> dependencies() const override;
8383

8484
private:
8585

‎src/ui/qgsvectorlayerpropertiesbase.ui

Lines changed: 77 additions & 196 deletions
Large diffs are not rendered by default.

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ ADD_PYTHON_TEST(PyQgsLayerDefinition test_qgslayerdefinition.py)
108108
ADD_PYTHON_TEST(PyQgsWFSProvider test_provider_wfs.py)
109109
ADD_PYTHON_TEST(PyQgsWFSProviderGUI test_provider_wfs_gui.py)
110110
ADD_PYTHON_TEST(PyQgsConsole test_console.py)
111+
ADD_PYTHON_TEST(PyQgsLayerDependencies test_layer_dependencies.py)
111112

112113
IF (NOT WIN32)
113114
ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsSnappingUtils (complement to C++-based tests)
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Hugo Mercier'
10+
__date__ = '12/07/2016'
11+
__copyright__ = 'Copyright 2016, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import qgis # NOQA
16+
import os
17+
18+
from qgis.core import (QgsMapLayerRegistry,
19+
QgsVectorLayer,
20+
QgsMapSettings,
21+
QgsSnappingUtils,
22+
QgsPointLocator,
23+
QgsTolerance,
24+
QgsRectangle,
25+
QgsPoint,
26+
QgsFeature,
27+
QgsGeometry,
28+
QgsProject,
29+
QgsLayerDefinition
30+
)
31+
32+
from qgis.testing import start_app, unittest
33+
from utilities import unitTestDataPath
34+
35+
from qgis.PyQt.QtCore import QSize, QPoint
36+
37+
import tempfile
38+
39+
try:
40+
from pyspatialite import dbapi2 as sqlite3
41+
except ImportError:
42+
print("You should install pyspatialite to run the tests")
43+
raise ImportError
44+
45+
# Convenience instances in case you may need them
46+
start_app()
47+
48+
49+
class TestLayerDependencies(unittest.TestCase):
50+
51+
@classmethod
52+
def setUpClass(cls):
53+
"""Run before all tests"""
54+
55+
# create a temp spatialite db with a trigger
56+
fo = tempfile.NamedTemporaryFile()
57+
fn = fo.name
58+
fo.close()
59+
cls.fn = fn
60+
con = sqlite3.connect(fn)
61+
cur = con.cursor()
62+
cur.execute("SELECT InitSpatialMetadata(1)")
63+
cur.execute("create table node(id integer primary key autoincrement);")
64+
cur.execute("select AddGeometryColumn('node', 'geom', 4326, 'POINT');")
65+
cur.execute("create table section(id integer primary key autoincrement, node1 integer, node2 integer);")
66+
cur.execute("select AddGeometryColumn('section', 'geom', 4326, 'LINESTRING');")
67+
cur.execute("create trigger add_nodes after insert on section begin insert into node (geom) values (st_startpoint(NEW.geom)); insert into node (geom) values (st_endpoint(NEW.geom)); end;")
68+
cur.execute("insert into node (geom) values (geomfromtext('point(0 0)', 4326));")
69+
cur.execute("insert into node (geom) values (geomfromtext('point(1 0)', 4326));")
70+
cur.execute("create table node2(id integer primary key autoincrement);")
71+
cur.execute("select AddGeometryColumn('node2', 'geom', 4326, 'POINT');")
72+
cur.execute("create trigger add_nodes2 after insert on node begin insert into node2 (geom) values (st_translate(NEW.geom, 0.2, 0, 0)); end;")
73+
con.commit()
74+
con.close()
75+
76+
cls.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % fn, "points", "spatialite")
77+
assert (cls.pointsLayer.isValid())
78+
cls.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % fn, "lines", "spatialite")
79+
assert (cls.linesLayer.isValid())
80+
cls.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % fn, "_points2", "spatialite")
81+
assert (cls.pointsLayer2.isValid())
82+
QgsMapLayerRegistry.instance().addMapLayers([cls.pointsLayer, cls.linesLayer, cls.pointsLayer2])
83+
84+
# save the project file
85+
fo = tempfile.NamedTemporaryFile()
86+
fn = fo.name
87+
fo.close()
88+
cls.projectFile = fn
89+
QgsProject.instance().setFileName(cls.projectFile)
90+
QgsProject.instance().write()
91+
92+
@classmethod
93+
def tearDownClass(cls):
94+
"""Run after all tests"""
95+
pass
96+
97+
def setUp(self):
98+
"""Run before each test."""
99+
pass
100+
101+
def tearDown(self):
102+
"""Run after each test."""
103+
pass
104+
105+
def test_resetSnappingIndex(self):
106+
self.pointsLayer.setDataDependencies([])
107+
self.linesLayer.setDataDependencies([])
108+
self.pointsLayer2.setDataDependencies([])
109+
110+
ms = QgsMapSettings()
111+
ms.setOutputSize(QSize(100, 100))
112+
ms.setExtent(QgsRectangle(0, 0, 1, 1))
113+
self.assertTrue(ms.hasValidSettings())
114+
115+
u = QgsSnappingUtils()
116+
u.setMapSettings(ms)
117+
u.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced)
118+
layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels)]
119+
u.setLayers(layers)
120+
121+
m = u.snapToMap(QPoint(95, 100))
122+
self.assertTrue(m.isValid())
123+
self.assertTrue(m.hasVertex())
124+
self.assertEqual(m.point(), QgsPoint(1, 0))
125+
126+
f = QgsFeature(self.linesLayer.fields())
127+
f.setFeatureId(1)
128+
geom = QgsGeometry.fromWkt("LINESTRING(0 0,1 1)")
129+
f.setGeometry(geom)
130+
self.linesLayer.startEditing()
131+
self.linesLayer.addFeatures([f])
132+
self.linesLayer.commitChanges()
133+
134+
l1 = len([f for f in self.pointsLayer.getFeatures()])
135+
self.assertEqual(l1, 4)
136+
m = u.snapToMap(QPoint(95, 0))
137+
# snapping not updated
138+
self.pointsLayer.setDataDependencies([])
139+
self.assertEqual(m.isValid(), False)
140+
141+
# set layer dependencies
142+
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
143+
# add another line
144+
f = QgsFeature(self.linesLayer.fields())
145+
f.setFeatureId(2)
146+
geom = QgsGeometry.fromWkt("LINESTRING(0 0,0.5 0.5)")
147+
f.setGeometry(geom)
148+
self.linesLayer.startEditing()
149+
self.linesLayer.addFeatures([f])
150+
self.linesLayer.commitChanges()
151+
# check the snapped point is ok
152+
m = u.snapToMap(QPoint(45, 50))
153+
self.assertTrue(m.isValid())
154+
self.assertTrue(m.hasVertex())
155+
self.assertEqual(m.point(), QgsPoint(0.5, 0.5))
156+
self.pointsLayer.setDataDependencies([])
157+
158+
# test chained layer dependencies A -> B -> C
159+
layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels),
160+
QgsSnappingUtils.LayerConfig(self.pointsLayer2, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels)
161+
]
162+
u.setLayers(layers)
163+
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
164+
self.pointsLayer2.setDataDependencies([self.pointsLayer.id()])
165+
# add another line
166+
f = QgsFeature(self.linesLayer.fields())
167+
f.setFeatureId(3)
168+
geom = QgsGeometry.fromWkt("LINESTRING(0 0.2,0.5 0.8)")
169+
f.setGeometry(geom)
170+
self.linesLayer.startEditing()
171+
self.linesLayer.addFeatures([f])
172+
self.linesLayer.commitChanges()
173+
# check the second snapped point is ok
174+
m = u.snapToMap(QPoint(75, 100 - 80))
175+
self.assertTrue(m.isValid())
176+
self.assertTrue(m.hasVertex())
177+
self.assertEqual(m.point(), QgsPoint(0.7, 0.8))
178+
self.pointsLayer.setDataDependencies([])
179+
self.pointsLayer2.setDataDependencies([])
180+
181+
def test_cycleDetection(self):
182+
self.assertTrue(self.pointsLayer.setDataDependencies([self.linesLayer.id()]))
183+
self.assertFalse(self.linesLayer.setDataDependencies([self.pointsLayer.id()]))
184+
self.pointsLayer.setDataDependencies([])
185+
self.linesLayer.setDataDependencies([])
186+
187+
def test_layerDefinitionRewriteId(self):
188+
tmpfile = os.path.join(tempfile.tempdir, "test.qlr")
189+
190+
ltr = QgsProject.instance().layerTreeRoot()
191+
192+
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
193+
194+
QgsLayerDefinition.exportLayerDefinition(tmpfile, [ltr])
195+
196+
grp = ltr.addGroup("imported")
197+
QgsLayerDefinition.loadLayerDefinition(tmpfile, grp)
198+
199+
newPointsLayer = None
200+
newLinesLayer = None
201+
for l in grp.findLayers():
202+
if l.layerId().startswith('points'):
203+
newPointsLayer = l.layer()
204+
elif l.layerId().startswith('lines'):
205+
newLinesLayer = l.layer()
206+
self.assertFalse(newPointsLayer is None)
207+
self.assertFalse(newLinesLayer is None)
208+
self.assertTrue(newLinesLayer.id() in [dep.layerId() for dep in newPointsLayer.dependencies()])
209+
210+
self.pointsLayer.setDataDependencies([])
211+
212+
def test_signalConnection(self):
213+
# remove all layers
214+
QgsMapLayerRegistry.instance().removeAllMapLayers()
215+
# set dependencies and add back layers
216+
self.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % self.fn, "points", "spatialite")
217+
assert (self.pointsLayer.isValid())
218+
self.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % self.fn, "lines", "spatialite")
219+
assert (self.linesLayer.isValid())
220+
self.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % self.fn, "_points2", "spatialite")
221+
assert (self.pointsLayer2.isValid())
222+
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
223+
self.pointsLayer2.setDataDependencies([self.pointsLayer.id()])
224+
# this should update connections between layers
225+
QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer])
226+
QgsMapLayerRegistry.instance().addMapLayers([self.linesLayer])
227+
QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer2])
228+
229+
ms = QgsMapSettings()
230+
ms.setOutputSize(QSize(100, 100))
231+
ms.setExtent(QgsRectangle(0, 0, 1, 1))
232+
self.assertTrue(ms.hasValidSettings())
233+
234+
u = QgsSnappingUtils()
235+
u.setMapSettings(ms)
236+
u.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced)
237+
layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels),
238+
QgsSnappingUtils.LayerConfig(self.pointsLayer2, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels)
239+
]
240+
u.setLayers(layers)
241+
# add another line
242+
f = QgsFeature(self.linesLayer.fields())
243+
f.setFeatureId(4)
244+
geom = QgsGeometry.fromWkt("LINESTRING(0.5 0.2,0.6 0)")
245+
f.setGeometry(geom)
246+
self.linesLayer.startEditing()
247+
self.linesLayer.addFeatures([f])
248+
self.linesLayer.commitChanges()
249+
# check the second snapped point is ok
250+
m = u.snapToMap(QPoint(75, 100 - 0))
251+
self.assertTrue(m.isValid())
252+
self.assertTrue(m.hasVertex())
253+
self.assertEqual(m.point(), QgsPoint(0.8, 0.0))
254+
255+
self.pointsLayer.setDataDependencies([])
256+
self.pointsLayer2.setDataDependencies([])
257+
258+
259+
if __name__ == '__main__':
260+
unittest.main()

‎tests/src/python/test_provider_virtual.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -677,18 +677,18 @@ def test_ProjectDependencies(self):
677677
self.assertEqual(l2.isValid(), True)
678678
QgsMapLayerRegistry.instance().addMapLayer(l2)
679679

680-
self.assertEqual(len(l2.layerDependencies()), 1)
681-
self.assertEqual(l2.layerDependencies()[0].startswith('france_parts'), True)
680+
self.assertEqual(len(l2.dependencies()), 1)
681+
self.assertEqual(l2.dependencies()[0].layerId().startswith('france_parts'), True)
682682

683683
query = QUrl.toPercentEncoding("SELECT t1.objectid, t2.name_0 FROM france_parts as t1, aa as t2")
684684
l3 = QgsVectorLayer("?query=%s" % query, "bb", "virtual", False)
685685
self.assertEqual(l3.isValid(), True)
686686
QgsMapLayerRegistry.instance().addMapLayer(l3)
687687

688-
self.assertEqual(len(l2.layerDependencies()), 1)
689-
self.assertEqual(l2.layerDependencies()[0].startswith('france_parts'), True)
688+
self.assertEqual(len(l2.dependencies()), 1)
689+
self.assertEqual(l2.dependencies()[0].layerId().startswith('france_parts'), True)
690690

691-
self.assertEqual(len(l3.layerDependencies()), 2)
691+
self.assertEqual(len(l3.dependencies()), 2)
692692

693693
temp = os.path.join(tempfile.gettempdir(), "qgstestproject.qgs")
694694

0 commit comments

Comments
 (0)
Please sign in to comment.