Skip to content

Commit 0b36ee3

Browse files
committedApr 18, 2017
Use weak layer references and loose matching for composer legend
customisation Allows legend customisation to be restored when loading a composer template Fix #2738
1 parent 7b202ed commit 0b36ee3

File tree

11 files changed

+136
-25
lines changed

11 files changed

+136
-25
lines changed
 

‎python/core/layertree/qgslayertreegroup.sip

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,11 @@ class QgsLayerTreeGroup : QgsLayerTreeNode
7979
virtual QgsLayerTreeGroup* clone() const /Factory/;
8080

8181
//! Calls resolveReferences() on child tree nodes
82-
//! @note added in 3.0
83-
virtual void resolveReferences( const QgsProject* project );
82+
//! \since QGIS 3.0
83+
virtual void resolveReferences( const QgsProject *project, bool looseMatching = false );
84+
85+
//! Check or uncheck a node and all its children (taking into account exclusion rules)
86+
virtual void setItemVisibilityCheckedRecursive( bool checked );
8487

8588
//! Return whether the group is mutually exclusive (only one child can be checked at a time)
8689
//! @note added in 2.12

‎python/core/layertree/qgslayertreelayer.sip

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ class QgsLayerTreeLayer : QgsLayerTreeNode
4949
virtual QgsLayerTreeLayer* clone() const /Factory/;
5050

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

5556
signals:
5657
//! emitted when a previously unavailable layer got loaded

‎python/core/layertree/qgslayertreenode.sip

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,16 @@ class QgsLayerTreeNode : QObject
102102
//! Create a copy of the node. Returns new instance
103103
virtual QgsLayerTreeNode *clone() const = 0 /Factory/;
104104

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

110116
//! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
111117
//! @note added in 3.0

‎src/core/composer/qgscomposerlegend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ bool QgsComposerLegend::readXml( const QDomElement &itemElem, const QDomDocument
489489
{
490490
std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem ) );
491491
if ( mComposition )
492-
tree->resolveReferences( mComposition->project() );
492+
tree->resolveReferences( mComposition->project(), true );
493493
setCustomLayerTree( tree.release() );
494494
}
495495
else

‎src/core/layertree/qgslayertreegroup.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,10 @@ QgsLayerTreeGroup *QgsLayerTreeGroup::clone() const
336336
return new QgsLayerTreeGroup( *this );
337337
}
338338

339-
void QgsLayerTreeGroup::resolveReferences( const QgsProject *project )
339+
void QgsLayerTreeGroup::resolveReferences( const QgsProject *project, bool looseMatching )
340340
{
341341
Q_FOREACH ( QgsLayerTreeNode *node, mChildren )
342-
node->resolveReferences( project );
342+
node->resolveReferences( project, looseMatching );
343343
}
344344

345345
static bool _nodeIsChecked( QgsLayerTreeNode *node )

‎src/core/layertree/qgslayertreegroup.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode
103103

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

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

‎src/core/layertree/qgslayertreelayer.cpp

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( QgsMapLayer *layer )
2828
attachToLayer();
2929
}
3030

31-
QgsLayerTreeLayer::QgsLayerTreeLayer( const QString &layerId, const QString &name )
31+
QgsLayerTreeLayer::QgsLayerTreeLayer( const QString &layerId, const QString &name, const QString &source, const QString &provider )
3232
: QgsLayerTreeNode( NodeLayer, true )
33-
, mRef( layerId )
33+
, mRef( layerId, name, source, provider )
3434
, mLayerName( name.isEmpty() ? QStringLiteral( "(?)" ) : name )
3535
{
3636
}
@@ -43,12 +43,25 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( const QgsLayerTreeLayer &other )
4343
attachToLayer();
4444
}
4545

46-
void QgsLayerTreeLayer::resolveReferences( const QgsProject *project )
46+
void QgsLayerTreeLayer::resolveReferences( const QgsProject *project, bool looseMatching )
4747
{
4848
if ( mRef.layer )
4949
return; // already assigned
5050

5151
QgsMapLayer *layer = project->mapLayer( mRef.layerId );
52+
53+
if ( !layer && looseMatching && !mRef.name.isEmpty() )
54+
{
55+
Q_FOREACH ( QgsMapLayer *l, project->mapLayersByName( mRef.name ) )
56+
{
57+
if ( mRef.layerMatchesSource( l ) )
58+
{
59+
layer = l;
60+
break;
61+
}
62+
}
63+
}
64+
5265
if ( !layer )
5366
return;
5467

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

99112
QString layerID = element.attribute( QStringLiteral( "id" ) );
100113
QString layerName = element.attribute( QStringLiteral( "name" ) );
114+
115+
QString providerKey = element.attribute( "providerKey" );
116+
QString source = element.attribute( "source" );
117+
101118
Qt::CheckState checked = QgsLayerTreeUtils::checkStateFromXml( element.attribute( QStringLiteral( "checked" ) ) );
102119
bool isExpanded = ( element.attribute( QStringLiteral( "expanded" ), QStringLiteral( "1" ) ) == QLatin1String( "1" ) );
103120

104121
// needs to have the layer reference resolved later
105-
QgsLayerTreeLayer *nodeLayer = new QgsLayerTreeLayer( layerID, layerName );
122+
QgsLayerTreeLayer *nodeLayer = new QgsLayerTreeLayer( layerID, layerName, source, providerKey );
106123

107124
nodeLayer->readCommonXml( element );
108125

@@ -125,6 +142,31 @@ void QgsLayerTreeLayer::writeXml( QDomElement &parentElement )
125142
QDomElement elem = doc.createElement( QStringLiteral( "layer-tree-layer" ) );
126143
elem.setAttribute( QStringLiteral( "id" ), layerId() );
127144
elem.setAttribute( QStringLiteral( "name" ), name() );
145+
146+
if ( mRef.layer )
147+
{
148+
elem.setAttribute( "source", mRef.layer->publicSource() );
149+
QString providerKey;
150+
switch ( mRef.layer->type() )
151+
{
152+
case QgsMapLayer::VectorLayer:
153+
{
154+
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mRef.layer );
155+
providerKey = vl->dataProvider()->name();
156+
break;
157+
}
158+
case QgsMapLayer::RasterLayer:
159+
{
160+
QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( mRef.layer );
161+
providerKey = rl->dataProvider()->name();
162+
break;
163+
}
164+
case QgsMapLayer::PluginLayer:
165+
break;
166+
}
167+
elem.setAttribute( "providerKey", providerKey );
168+
}
169+
128170
elem.setAttribute( QStringLiteral( "checked" ), mChecked ? QStringLiteral( "Qt::Checked" ) : QStringLiteral( "Qt::Unchecked" ) );
129171
elem.setAttribute( QStringLiteral( "expanded" ), mExpanded ? "1" : "0" );
130172

‎src/core/layertree/qgslayertreelayer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
4343
explicit QgsLayerTreeLayer( QgsMapLayer *layer );
4444
QgsLayerTreeLayer( const QgsLayerTreeLayer &other );
4545

46-
explicit QgsLayerTreeLayer( const QString &layerId, const QString &name = QString() );
46+
explicit QgsLayerTreeLayer( const QString &layerId, const QString &name = QString(), const QString &source = QString(), const QString &provider = QString() );
4747

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

@@ -72,7 +72,7 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
7272

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

7777
private slots:
7878
//! Emits a nameChanged() signal if layer's name has changed

‎src/core/layertree/qgslayertreenode.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,16 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
113113
//! Create a copy of the node. Returns new instance
114114
virtual QgsLayerTreeNode *clone() const = 0;
115115

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

121127
//! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
122128
//! \since QGIS 3.0

‎src/core/qgsmaplayerref.h

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#include <QPointer>
2020

2121
#include "qgsmaplayer.h"
22+
#include "qgsvectorlayer.h"
23+
#include "qgsvectordataprovider.h"
24+
#include "raster/qgsrasterlayer.h"
25+
#include "raster/qgsrasterdataprovider.h"
2226

2327
/** Internal structure to keep weak pointer to QgsMapLayer or layerId
2428
* if the layer is not available yet.
@@ -28,10 +32,61 @@ template<typename TYPE>
2832
struct _LayerRef
2933
{
3034
_LayerRef( TYPE *l = nullptr ): layer( l ), layerId( l ? l->id() : QString() ) {}
31-
_LayerRef( const QString &id ): layer(), layerId( id ) {}
35+
36+
/**
37+
* Constructor for a weak layer reference, using a combination of layer ID,
38+
* \a name, public \a source and \a provider key.
39+
*/
40+
_LayerRef( const QString &id, const QString &name = QString(), const QString &source = QString(), const QString &provider = QString() )
41+
: layer()
42+
, layerId( id )
43+
, source( source )
44+
, name( name )
45+
, provider( provider )
46+
{}
3247

3348
QPointer<TYPE> layer;
3449
QString layerId;
50+
51+
//! Weak reference to layer public source
52+
QString source;
53+
//! Weak reference to layer name
54+
QString name;
55+
//! Weak reference to layer provider
56+
QString provider;
57+
58+
/**
59+
* Returns true if a layer matches the weak references to layer public source,
60+
* layer name and data provider contained in this layer reference.
61+
*/
62+
bool layerMatchesSource( QgsMapLayer *layer ) const
63+
{
64+
if ( layer->publicSource() != source ||
65+
layer->name() != name )
66+
return false;
67+
68+
switch ( layer->type() )
69+
{
70+
case QgsMapLayer::VectorLayer:
71+
{
72+
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
73+
if ( vl->dataProvider()->name() != provider )
74+
return false;
75+
break;
76+
}
77+
case QgsMapLayer::RasterLayer:
78+
{
79+
QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( layer );
80+
if ( rl->dataProvider()->name() != provider )
81+
return false;
82+
break;
83+
}
84+
case QgsMapLayer::PluginLayer:
85+
break;
86+
87+
}
88+
return true;
89+
}
3590
};
3691

3792
typedef _LayerRef<QgsMapLayer> QgsMapLayerRef;

‎tests/src/core/testqgscomposition.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,6 @@ void TestQgsComposition::legendRestoredFromTemplate()
720720
QCOMPARE( layerNode2->layer(), layer );
721721
QCOMPARE( model2->data( model->node2index( layerNode2 ), Qt::DisplayRole ).toString(), QString( "new title!" ) );
722722

723-
#if 0 //expected failure (#2738)
724723
QString oldId = layer->id();
725724
// new test
726725
// remove existing layer
@@ -749,7 +748,6 @@ void TestQgsComposition::legendRestoredFromTemplate()
749748
QVERIFY( layerNode3 );
750749
QCOMPARE( layerNode3->layer(), layer2 );
751750
QCOMPARE( model3->data( model->node2index( layerNode3 ), Qt::DisplayRole ).toString(), QString( "new title!" ) );
752-
#endif
753751
}
754752

755753
void TestQgsComposition::legendRestoredFromTemplateAutoUpdate()

0 commit comments

Comments
 (0)
Please sign in to comment.