Skip to content
Permalink
Browse files
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 0b36ee3aac8b6de1142f0a807a02aad07292d61f
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 )
@@ -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;
@@ -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 )
{
}
@@ -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;

@@ -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 );

@@ -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" );

@@ -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; }

@@ -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
@@ -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
@@ -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.
@@ -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;
@@ -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
@@ -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()

0 comments on commit 0b36ee3

Please sign in to comment.