Skip to content

Commit bd3cf76

Browse files
author
Hugo Mercier
authored
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

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PyQgsSipCoverage
99
PyQgsSpatialiteProvider
1010
PyQgsVirtualLayerDefinition
1111
PyQgsVirtualLayerProvider
12+
PyQgsLayerDependencies
1213
qgis_composermapgridtest
1314
qgis_composerutils
1415
ProcessingGrass7AlgorithmsImageryTest

images/images.qrc

+1
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>
+151
Loading

python/core/core.sip

+1
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

+29
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

+36
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

+1-1
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

+16-2
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

+34
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

+5
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

+16
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" );

0 commit comments

Comments
 (0)