Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Use weak layer references and loose matching for composer legend
customisation

Allows legend customisation to be restored when loading a composer
template

Fix #2738
  • Loading branch information
nyalldawson committed Apr 18, 2017
1 parent 7b202ed commit 0b36ee3
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 25 deletions.
7 changes: 5 additions & 2 deletions python/core/layertree/qgslayertreegroup.sip
Expand Up @@ -79,8 +79,11 @@ class QgsLayerTreeGroup : QgsLayerTreeNode
virtual QgsLayerTreeGroup* clone() const /Factory/;

//! Calls resolveReferences() on child tree nodes
//! @note added in 3.0
virtual void resolveReferences( const QgsProject* project );
//! \since QGIS 3.0
virtual void resolveReferences( const QgsProject *project, bool looseMatching = false );

//! Check or uncheck a node and all its children (taking into account exclusion rules)
virtual void setItemVisibilityCheckedRecursive( bool checked );

//! Return whether the group is mutually exclusive (only one child can be checked at a time)
//! @note added in 2.12
Expand Down
5 changes: 3 additions & 2 deletions python/core/layertree/qgslayertreelayer.sip
Expand Up @@ -49,8 +49,9 @@ class QgsLayerTreeLayer : QgsLayerTreeNode
virtual QgsLayerTreeLayer* clone() const /Factory/;

//! Resolves reference to layer from stored layer ID (if it has not been resolved already)
//! @note added in 3.0
virtual void resolveReferences( const QgsProject* project );
//! \since QGIS 3.0
virtual void resolveReferences( const QgsProject *project, bool looseMatching = false );


signals:
//! emitted when a previously unavailable layer got loaded
Expand Down
14 changes: 10 additions & 4 deletions python/core/layertree/qgslayertreenode.sip
Expand Up @@ -102,10 +102,16 @@ class QgsLayerTreeNode : QObject
//! Create a copy of the node. Returns new instance
virtual QgsLayerTreeNode *clone() const = 0 /Factory/;

//! Turn textual references to layers into map layer object from project.
//! This method should be called after readXml()
//! @note added in 3.0
virtual void resolveReferences( const QgsProject* project ) = 0;
/**
* Turn textual references to layers into map layer object from project.
* This method should be called after readXml()
* If \a looseMatching is true then a looser match will be used, where a layer
* will match if the name, public source, and data provider match. This can be
* used to match legend customisation from different projects where layers
* will have different layer IDs.
* \since QGIS 3.0
*/
virtual void resolveReferences( const QgsProject *project, bool looseMatching = false ) = 0;

//! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
//! @note added in 3.0
Expand Down
2 changes: 1 addition & 1 deletion src/core/composer/qgscomposerlegend.cpp
Expand Up @@ -489,7 +489,7 @@ bool QgsComposerLegend::readXml( const QDomElement &itemElem, const QDomDocument
{
std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem ) );
if ( mComposition )
tree->resolveReferences( mComposition->project() );
tree->resolveReferences( mComposition->project(), true );
setCustomLayerTree( tree.release() );
}
else
Expand Down
4 changes: 2 additions & 2 deletions src/core/layertree/qgslayertreegroup.cpp
Expand Up @@ -336,10 +336,10 @@ QgsLayerTreeGroup *QgsLayerTreeGroup::clone() const
return new QgsLayerTreeGroup( *this );
}

void QgsLayerTreeGroup::resolveReferences( const QgsProject *project )
void QgsLayerTreeGroup::resolveReferences( const QgsProject *project, bool looseMatching )
{
Q_FOREACH ( QgsLayerTreeNode *node, mChildren )
node->resolveReferences( project );
node->resolveReferences( project, looseMatching );
}

static bool _nodeIsChecked( QgsLayerTreeNode *node )
Expand Down
2 changes: 1 addition & 1 deletion src/core/layertree/qgslayertreegroup.h
Expand Up @@ -103,7 +103,7 @@ class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode

//! Calls resolveReferences() on child tree nodes
//! \since QGIS 3.0
virtual void resolveReferences( const QgsProject *project ) override;
virtual void resolveReferences( const QgsProject *project, bool looseMatching = false ) override;

//! Check or uncheck a node and all its children (taking into account exclusion rules)
virtual void setItemVisibilityCheckedRecursive( bool checked ) override;
Expand Down
50 changes: 46 additions & 4 deletions src/core/layertree/qgslayertreelayer.cpp
Expand Up @@ -28,9 +28,9 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( QgsMapLayer *layer )
attachToLayer();
}

QgsLayerTreeLayer::QgsLayerTreeLayer( const QString &layerId, const QString &name )
QgsLayerTreeLayer::QgsLayerTreeLayer( const QString &layerId, const QString &name, const QString &source, const QString &provider )
: QgsLayerTreeNode( NodeLayer, true )
, mRef( layerId )
, mRef( layerId, name, source, provider )
, mLayerName( name.isEmpty() ? QStringLiteral( "(?)" ) : name )
{
}
Expand All @@ -43,12 +43,25 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( const QgsLayerTreeLayer &other )
attachToLayer();
}

void QgsLayerTreeLayer::resolveReferences( const QgsProject *project )
void QgsLayerTreeLayer::resolveReferences( const QgsProject *project, bool looseMatching )
{
if ( mRef.layer )
return; // already assigned

QgsMapLayer *layer = project->mapLayer( mRef.layerId );

if ( !layer && looseMatching && !mRef.name.isEmpty() )
{
Q_FOREACH ( QgsMapLayer *l, project->mapLayersByName( mRef.name ) )
{
if ( mRef.layerMatchesSource( l ) )
{
layer = l;
break;
}
}
}

if ( !layer )
return;

Expand Down Expand Up @@ -98,11 +111,15 @@ QgsLayerTreeLayer *QgsLayerTreeLayer::readXml( QDomElement &element )

QString layerID = element.attribute( QStringLiteral( "id" ) );
QString layerName = element.attribute( QStringLiteral( "name" ) );

QString providerKey = element.attribute( "providerKey" );
QString source = element.attribute( "source" );

Qt::CheckState checked = QgsLayerTreeUtils::checkStateFromXml( element.attribute( QStringLiteral( "checked" ) ) );
bool isExpanded = ( element.attribute( QStringLiteral( "expanded" ), QStringLiteral( "1" ) ) == QLatin1String( "1" ) );

// needs to have the layer reference resolved later
QgsLayerTreeLayer *nodeLayer = new QgsLayerTreeLayer( layerID, layerName );
QgsLayerTreeLayer *nodeLayer = new QgsLayerTreeLayer( layerID, layerName, source, providerKey );

nodeLayer->readCommonXml( element );

Expand All @@ -125,6 +142,31 @@ void QgsLayerTreeLayer::writeXml( QDomElement &parentElement )
QDomElement elem = doc.createElement( QStringLiteral( "layer-tree-layer" ) );
elem.setAttribute( QStringLiteral( "id" ), layerId() );
elem.setAttribute( QStringLiteral( "name" ), name() );

if ( mRef.layer )
{
elem.setAttribute( "source", mRef.layer->publicSource() );
QString providerKey;
switch ( mRef.layer->type() )
{
case QgsMapLayer::VectorLayer:
{
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mRef.layer );
providerKey = vl->dataProvider()->name();
break;
}
case QgsMapLayer::RasterLayer:
{
QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( mRef.layer );
providerKey = rl->dataProvider()->name();
break;
}
case QgsMapLayer::PluginLayer:
break;
}
elem.setAttribute( "providerKey", providerKey );
}

elem.setAttribute( QStringLiteral( "checked" ), mChecked ? QStringLiteral( "Qt::Checked" ) : QStringLiteral( "Qt::Unchecked" ) );
elem.setAttribute( QStringLiteral( "expanded" ), mExpanded ? "1" : "0" );

Expand Down
4 changes: 2 additions & 2 deletions src/core/layertree/qgslayertreelayer.h
Expand Up @@ -43,7 +43,7 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
explicit QgsLayerTreeLayer( QgsMapLayer *layer );
QgsLayerTreeLayer( const QgsLayerTreeLayer &other );

explicit QgsLayerTreeLayer( const QString &layerId, const QString &name = QString() );
explicit QgsLayerTreeLayer( const QString &layerId, const QString &name = QString(), const QString &source = QString(), const QString &provider = QString() );

QString layerId() const { return mRef.layerId; }

Expand Down Expand Up @@ -72,7 +72,7 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode

//! Resolves reference to layer from stored layer ID (if it has not been resolved already)
//! \since QGIS 3.0
virtual void resolveReferences( const QgsProject *project ) override;
virtual void resolveReferences( const QgsProject *project, bool looseMatching = false ) override;

private slots:
//! Emits a nameChanged() signal if layer's name has changed
Expand Down
14 changes: 10 additions & 4 deletions src/core/layertree/qgslayertreenode.h
Expand Up @@ -113,10 +113,16 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
//! Create a copy of the node. Returns new instance
virtual QgsLayerTreeNode *clone() const = 0;

//! Turn textual references to layers into map layer object from project.
//! This method should be called after readXml()
//! \since QGIS 3.0
virtual void resolveReferences( const QgsProject *project ) = 0;
/**
* Turn textual references to layers into map layer object from project.
* This method should be called after readXml()
* If \a looseMatching is true then a looser match will be used, where a layer
* will match if the name, public source, and data provider match. This can be
* used to match legend customisation from different projects where layers
* will have different layer IDs.
* \since QGIS 3.0
*/
virtual void resolveReferences( const QgsProject *project, bool looseMatching = false ) = 0;

//! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
//! \since QGIS 3.0
Expand Down
57 changes: 56 additions & 1 deletion src/core/qgsmaplayerref.h
Expand Up @@ -19,6 +19,10 @@
#include <QPointer>

#include "qgsmaplayer.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "raster/qgsrasterlayer.h"
#include "raster/qgsrasterdataprovider.h"

/** Internal structure to keep weak pointer to QgsMapLayer or layerId
* if the layer is not available yet.
Expand All @@ -28,10 +32,61 @@ template<typename TYPE>
struct _LayerRef
{
_LayerRef( TYPE *l = nullptr ): layer( l ), layerId( l ? l->id() : QString() ) {}
_LayerRef( const QString &id ): layer(), layerId( id ) {}

/**
* Constructor for a weak layer reference, using a combination of layer ID,
* \a name, public \a source and \a provider key.
*/
_LayerRef( const QString &id, const QString &name = QString(), const QString &source = QString(), const QString &provider = QString() )
: layer()
, layerId( id )
, source( source )
, name( name )
, provider( provider )
{}

QPointer<TYPE> layer;
QString layerId;

//! Weak reference to layer public source
QString source;
//! Weak reference to layer name
QString name;
//! Weak reference to layer provider
QString provider;

/**
* Returns true if a layer matches the weak references to layer public source,
* layer name and data provider contained in this layer reference.
*/
bool layerMatchesSource( QgsMapLayer *layer ) const
{
if ( layer->publicSource() != source ||
layer->name() != name )
return false;

switch ( layer->type() )
{
case QgsMapLayer::VectorLayer:
{
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
if ( vl->dataProvider()->name() != provider )
return false;
break;
}
case QgsMapLayer::RasterLayer:
{
QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( layer );
if ( rl->dataProvider()->name() != provider )
return false;
break;
}
case QgsMapLayer::PluginLayer:
break;

}
return true;
}
};

typedef _LayerRef<QgsMapLayer> QgsMapLayerRef;
Expand Down
2 changes: 0 additions & 2 deletions tests/src/core/testqgscomposition.cpp
Expand Up @@ -720,7 +720,6 @@ void TestQgsComposition::legendRestoredFromTemplate()
QCOMPARE( layerNode2->layer(), layer );
QCOMPARE( model2->data( model->node2index( layerNode2 ), Qt::DisplayRole ).toString(), QString( "new title!" ) );

#if 0 //expected failure (#2738)
QString oldId = layer->id();
// new test
// remove existing layer
Expand Down Expand Up @@ -749,7 +748,6 @@ void TestQgsComposition::legendRestoredFromTemplate()
QVERIFY( layerNode3 );
QCOMPARE( layerNode3->layer(), layer2 );
QCOMPARE( model3->data( model->node2index( layerNode3 ), Qt::DisplayRole ).toString(), QString( "new title!" ) );
#endif
}

void TestQgsComposition::legendRestoredFromTemplateAutoUpdate()
Expand Down

0 comments on commit 0b36ee3

Please sign in to comment.