Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #3320 from mhugo/fix_snapping2
Data dependency between layers + snapping fix
  • Loading branch information
Hugo Mercier committed Aug 31, 2016
2 parents 504badb + 0749ba4 commit bd3cf76
Show file tree
Hide file tree
Showing 27 changed files with 953 additions and 219 deletions.
1 change: 1 addition & 0 deletions ci/travis/linux/qt5/blacklist.txt
Expand Up @@ -9,6 +9,7 @@ PyQgsSipCoverage
PyQgsSpatialiteProvider
PyQgsVirtualLayerDefinition
PyQgsVirtualLayerProvider
PyQgsLayerDependencies

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Aug 31, 2016

Member

Not sure we should add new tests to the blacklist at this stage of the transition towards Qt5

This comment has been minimized.

Copy link
@3nids

3nids Aug 31, 2016

Member

This seems to be due to the spatialite problem. So not really strictl a new item to the blacklist.
What's the status for spatialite?

This comment has been minimized.

Copy link
@mhugo

mhugo Aug 31, 2016

Yes, this is the pyspatialite problem.
For what I understood from #2701 using standard sqlite3 module + "load_extension(mod_spatialite)" is the good way to do, assuming we will have "mod_spatialite" for windows and mac ...

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Aug 31, 2016

Member

It has been obsoleted by geopackage.

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Aug 31, 2016

Member

For what I understood from #2701 using standard sqlite3 module + "load_extension(mod_spatialite)" is the good way to do, assuming we will have "mod_spatialite" for windows and mac ...

+1 for this approach. It should work on travis so should be ok to avoid regressions and hopefully other OS's just follow.

This comment has been minimized.

Copy link
@mhugo

mhugo Aug 31, 2016

Ok, then I will try to propose a fix soon (that will be 4 items less in the blacklist).

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Aug 31, 2016

Member

Well, that is good news :)

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Aug 31, 2016

Member

A "qgis.utils.connect_spatialite" method that goes to spatialite or sqlite library depending on availability would be nice ;)

This comment has been minimized.

Copy link
@mhugo

mhugo Aug 31, 2016

Is it possible to add triggers to a geopackage ? With GDAL ?

This comment has been minimized.

Copy link
@mhugo

mhugo Aug 31, 2016

Yeah I was thinking about something like that: qgis.utils.connect_spatialite or qgis.pyspatialite

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Aug 31, 2016

Member

Is it possible to add triggers to a geopackage ? With GDAL ?

No idea :)

qgis_composermapgridtest
qgis_composerutils
ProcessingGrass7AlgorithmsImageryTest
Expand Down
1 change: 1 addition & 0 deletions images/images.qrc
Expand Up @@ -665,6 +665,7 @@
<file>themes/default/mActionAddAfsLayer.svg</file>
<file>themes/default/mIconFormSelect.svg</file>
<file>themes/default/mActionMultiEdit.svg</file>
<file>themes/default/dependencies.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Expand Down
151 changes: 151 additions & 0 deletions images/themes/default/dependencies.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -80,6 +80,7 @@
%Include qgslogger.sip
%Include qgsmaphittest.sip
%Include qgsmaplayer.sip
%Include qgsmaplayerdependency.sip
%Include qgsmaplayerlegend.sip
%Include qgsmaplayermodel.sip
%Include qgsmaplayerproxymodel.sip
Expand Down
29 changes: 29 additions & 0 deletions python/core/qgsmaplayer.sip
Expand Up @@ -676,6 +676,32 @@ class QgsMapLayer : QObject
*/
void emitStyleChanged();

/**
* Sets the list of layers that may modify data/geometries of this layer when modified.
* @see dependencies()
*
* @param layersIds IDs of the layers that this layer depends on
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
*/
virtual bool setDataDependencies( const QSet<QString>& layersIds );

/**
* Sets the list of layers that may modify data/geometries of this layer when modified.
* @see dependencies()
*
* @param set of QgsMapLayerDependency. Only user-defined dependencies will be added
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
*/
bool setDataDependencies( const QSet<QgsMapLayerDependency>& layers );

/**
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
* as well as dependencies given by the provider
*
* @returns a set of QgsMapLayerDependency
*/
virtual QSet<QgsMapLayerDependency> dependencies() const;

signals:

/** Emit a signal with status (e.g. to be caught by QgisApp and display a msg on status bar) */
Expand Down Expand Up @@ -766,4 +792,7 @@ class QgsMapLayer : QObject
void appendError( const QgsErrorMessage &error );
/** Set error message */
void setError( const QgsError &error );

//! Checks if new change dependency candidates introduce a cycle
bool hasDataDependencyCycle( const QSet<QgsMapLayerDependency>& layersIds ) const;
};
36 changes: 36 additions & 0 deletions python/core/qgsmaplayerdependency.sip
@@ -0,0 +1,36 @@
class QgsMapLayerDependency
{
%TypeHeaderCode
#include "qgsmaplayerdependency.h"
%End
public:
//! Type of dependency
enum Type
{
PresenceDependency = 1, // The layer must be already present (in the registry) for this dependency to be resolved
DataDependency = 2 // The layer may be invalidated by data changes on another layer
};

//! Origin of the dependency
enum Origin
{
FromProvider = 0, // Dependency given by the provider, the user cannot change it
FromUser = 1 // Dependency given by the user
};

//! Standard constructor
QgsMapLayerDependency( QString layerId, Type type = DataDependency, Origin origin = FromUser );

//! Return the dependency type
Type type() const;

//! Return the dependency origin
Origin origin() const;

//! Return the ID of the layer this dependency depends on
QString layerId() const;

bool operator==( const QgsMapLayerDependency& other ) const;
};


2 changes: 1 addition & 1 deletion python/core/qgsvectordataprovider.sip
Expand Up @@ -368,7 +368,7 @@ class QgsVectorDataProvider : QgsDataProvider
/**
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
*/
virtual QSet<QString> layerDependencies() const;
virtual QSet<QgsMapLayerDependency> dependencies() const;

signals:
/** Signals an error in this provider */
Expand Down
18 changes: 16 additions & 2 deletions python/core/qgsvectorlayer.sip
Expand Up @@ -422,9 +422,23 @@ class QgsVectorLayer : QgsMapLayer
const QList<QgsVectorJoinInfo> vectorJoins() const;

/**
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
* Sets the list of layers that may modify data/geometries of this layer when modified.
* This is meant mainly to declare database triggers between layers.
* When one of these layers is modified (feature added/deleted or geometry changed),
* dataChanged() will be emitted, allowing users of this layer to refresh / update it.
*
* @param layersIds IDs of the layers that this layer depends on
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
*/
bool setDataDependencies( const QSet<QString>& layersIds );

/**
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
* as well as dependencies given by the provider
*
* @returns a set of QgsMapLayerDependency
*/
virtual QSet<QString> layerDependencies() const;
virtual QSet<QgsMapLayerDependency> dependencies() const;

/**
* Add a new field which is calculated by the expression specified
Expand Down
34 changes: 34 additions & 0 deletions src/app/qgsvectorlayerproperties.cpp
Expand Up @@ -52,6 +52,7 @@
#include "qgsdatasourceuri.h"
#include "qgsrenderer.h"
#include "qgsexpressioncontext.h"
#include "layertree/qgslayertreelayer.h"

#include <QMessageBox>
#include <QDir>
Expand Down Expand Up @@ -291,6 +292,27 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(

QString title = QString( tr( "Layer Properties - %1" ) ).arg( mLayer->name() );
restoreOptionsBaseUi( title );

mLayersDependenciesTreeGroup.reset( QgsProject::instance()->layerTreeRoot()->clone() );
QgsLayerTreeLayer* layer = mLayersDependenciesTreeGroup->findLayer( mLayer->id() );
layer->parent()->takeChild( layer );
mLayersDependenciesTreeModel.reset( new QgsLayerTreeModel( mLayersDependenciesTreeGroup.data() ) );
// use visibility as selection
mLayersDependenciesTreeModel->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );

mLayersDependenciesTreeGroup->setVisible( Qt::Unchecked );

QSet<QString> dependencySources;
Q_FOREACH ( const QgsMapLayerDependency& dep, mLayer->dependencies() )
{
dependencySources << dep.layerId();
}
Q_FOREACH ( QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
{
layer->setVisible( dependencySources.contains( layer->layerId() ) ? Qt::Checked : Qt::Unchecked );
}

mLayersDependenciesTreeView->setModel( mLayersDependenciesTreeModel.data() );
} // QgsVectorLayerProperties ctor


Expand Down Expand Up @@ -558,6 +580,18 @@ void QgsVectorLayerProperties::apply()
QgsExpressionContextUtils::setLayerVariables( mLayer, mVariableEditor->variablesInActiveScope() );
updateVariableEditor();

// save layer dependencies
QSet<QString> deps;
Q_FOREACH ( const QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
{
if ( layer->isVisible() )
deps << layer->layerId();
}
if ( ! mLayer->setDataDependencies( deps ) )
{
QMessageBox::warning( nullptr, tr( "Dependency cycle" ), tr( "This configuration introduces a cycle in data dependencies and will be ignored" ) );
}

// update symbology
emit refreshLegend( mLayer->id() );

Expand Down
5 changes: 5 additions & 0 deletions src/app/qgsvectorlayerproperties.h
Expand Up @@ -25,6 +25,8 @@
#include "qgscontexthelp.h"
#include "qgsmaplayerstylemanager.h"
#include "qgsvectorlayer.h"
#include "layertree/qgslayertreemodel.h"
#include "layertree/qgslayertreegroup.h"

class QgsMapLayer;

Expand Down Expand Up @@ -193,6 +195,9 @@ class APP_EXPORT QgsVectorLayerProperties : public QgsOptionsDialogBase, private

QgsExpressionContext createExpressionContext() const override;

QScopedPointer<QgsLayerTreeGroup> mLayersDependenciesTreeGroup;
QScopedPointer<QgsLayerTreeModel> mLayersDependenciesTreeModel;

private slots:
void openPanel( QgsPanelWidget* panel );
};
Expand Down
16 changes: 16 additions & 0 deletions src/core/qgslayerdefinition.cpp
Expand Up @@ -111,6 +111,22 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
joinNode.toElement().setAttribute( "joinLayerId", newid );
}
}

// change IDs of dependencies
QDomNodeList dataDeps = doc.elementsByTagName( "dataDependencies" );
for ( int i = 0; i < dataDeps.size(); i++ )
{
QDomNodeList layers = dataDeps.at( i ).childNodes();
for ( int j = 0; j < layers.size(); j++ )
{
QDomElement elt = layers.at( j ).toElement();
if ( elt.attribute( "id" ) == oldid )
{
elt.setAttribute( "id", newid );
}
}
}

}

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

0 comments on commit bd3cf76

Please sign in to comment.