Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[composer] Restore legend customisation from composer templates
This change allows customised legends within composer templates
to be correctly restored when creating a new composition from
the template in a different project.

The legend layers will be attached to any loaded layers with a
matching data source as the layer from the original template
composition.

Fix #2738

Sponsored by ENEL, on behalf of Faunalia
  • Loading branch information
nyalldawson committed Mar 22, 2017
1 parent e8deab2 commit 22b3307
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 23 deletions.
19 changes: 15 additions & 4 deletions python/core/layertree/qgslayertreegroup.sip
Expand Up @@ -55,12 +55,23 @@ class QgsLayerTreeGroup : QgsLayerTreeNode
//! Find group node with specified name. Searches recursively the whole sub-tree.
QgsLayerTreeGroup* findGroup( const QString& name );

//! Read group (tree) from XML element <layer-tree-group> and return the newly created group (or null on error)
static QgsLayerTreeGroup* readXML( QDomElement& element ) /Factory/;
/**
* Read group (tree) from XML element <layer-tree-group> and return the newly
* created group (or null on error). If the looseMatch
* parameter is true then child legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
static QgsLayerTreeGroup* readXML( QDomElement& element, bool looseMatch = false ) /Factory/;

//! Write group (tree) as XML element <layer-tree-group> and add it to the given parent element
virtual void writeXML( QDomElement& parentElement );
//! Read children from XML and append them to the group.
void readChildrenFromXML( QDomElement& element );

/**
* Read children from XML and append them to the group. If the looseMatch
* parameter is true then legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
void readChildrenFromXML( QDomElement& element, bool looseMatch = false );

//! Return text representation of the tree. For debugging purposes only.
virtual QString dump() const;
Expand Down
24 changes: 23 additions & 1 deletion python/core/layertree/qgslayertreelayer.sip
Expand Up @@ -25,6 +25,14 @@ class QgsLayerTreeLayer : QgsLayerTreeNode
public:
explicit QgsLayerTreeLayer( QgsMapLayer* layer );

/**
* Creates a layer node which will attach to a layer with a matching
* QgsMapLayer::publicSource(). This can be used for "looser" layer matching,
* avoiding the usual layer id check in favour of attaching to any layer
* with an equal source.
*/
static QgsLayerTreeLayer* createLayerFromSource( const QString& source ) /Factory/;

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

QString layerId() const;
Expand All @@ -38,13 +46,27 @@ class QgsLayerTreeLayer : QgsLayerTreeNode
//! @note added in 2.18.1
void setName( const QString& n );

/**
* Attempts to attach this layer node to a layer with a matching
* QgsMapLayer::publicSource(). This can be used for "looser" layer matching,
* avoiding the usual layer id check in favour of attaching to any layer
* with an equal source.
*/
void attachToSource( const QString& source );

QString layerName() const;
void setLayerName( const QString& n );

Qt::CheckState isVisible() const;
void setVisible( Qt::CheckState visible );

static QgsLayerTreeLayer* readXML( QDomElement& element ) /Factory/;
/**
* Creates a new layer from an XML definition. If the looseMatch
* parameter is true then legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
static QgsLayerTreeLayer* readXML( QDomElement& element, bool looseMatch = false ) /Factory/;

virtual void writeXML( QDomElement& parentElement );

virtual QString dump() const;
Expand Down
9 changes: 7 additions & 2 deletions python/core/layertree/qgslayertreenode.sip
Expand Up @@ -83,8 +83,13 @@ class QgsLayerTreeNode : QObject
//! @note added in 2.18.1
virtual void setName( const QString& name ) = 0;

//! Read layer tree from XML. Returns new instance
static QgsLayerTreeNode *readXML( QDomElement &element );
/**
* Read layer tree from XML. Returns new instance. If the looseMatch
* parameter is true then child legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
static QgsLayerTreeNode *readXML( QDomElement &element, bool looseMatch = false ) /Factory/;

//! Write layer tree to XML
virtual void writeXML( QDomElement &parentElement ) = 0;

Expand Down
2 changes: 1 addition & 1 deletion src/core/composer/qgscomposerlegend.cpp
Expand Up @@ -535,7 +535,7 @@ bool QgsComposerLegend::readXML( const QDomElement& itemElem, const QDomDocument
{
// QGIS >= 2.6
QDomElement layerTreeElem = itemElem.firstChildElement( "layer-tree-group" );
setCustomLayerTree( QgsLayerTreeGroup::readXML( layerTreeElem ) );
setCustomLayerTree( QgsLayerTreeGroup::readXML( layerTreeElem, true ) );
}

//restore general composer item properties
Expand Down
8 changes: 4 additions & 4 deletions src/core/layertree/qgslayertreegroup.cpp
Expand Up @@ -254,7 +254,7 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::findGroup( const QString& name )
return nullptr;
}

QgsLayerTreeGroup* QgsLayerTreeGroup::readXML( QDomElement& element )
QgsLayerTreeGroup* QgsLayerTreeGroup::readXML( QDomElement& element, bool looseMatch )
{
if ( element.tagName() != "layer-tree-group" )
return nullptr;
Expand All @@ -270,7 +270,7 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::readXML( QDomElement& element )

groupNode->readCommonXML( element );

groupNode->readChildrenFromXML( element );
groupNode->readChildrenFromXML( element, looseMatch );

groupNode->setIsMutuallyExclusive( isMutuallyExclusive, mutuallyExclusiveChildIndex );

Expand Down Expand Up @@ -298,13 +298,13 @@ void QgsLayerTreeGroup::writeXML( QDomElement& parentElement )
parentElement.appendChild( elem );
}

void QgsLayerTreeGroup::readChildrenFromXML( QDomElement& element )
void QgsLayerTreeGroup::readChildrenFromXML( QDomElement& element, bool looseMatch )
{
QList<QgsLayerTreeNode*> nodes;
QDomElement childElem = element.firstChildElement();
while ( !childElem.isNull() )
{
QgsLayerTreeNode* newNode = QgsLayerTreeNode::readXML( childElem );
QgsLayerTreeNode* newNode = QgsLayerTreeNode::readXML( childElem, looseMatch );
if ( newNode )
nodes << newNode;

Expand Down
18 changes: 14 additions & 4 deletions src/core/layertree/qgslayertreegroup.h
Expand Up @@ -76,12 +76,22 @@ class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode
//! Find group node with specified name. Searches recursively the whole sub-tree.
QgsLayerTreeGroup* findGroup( const QString& name );

//! Read group (tree) from XML element <layer-tree-group> and return the newly created group (or null on error)
static QgsLayerTreeGroup* readXML( QDomElement& element );
/**
* Read group (tree) from XML element <layer-tree-group> and return the newly
* created group (or null on error). If the looseMatch
* parameter is true then child legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
static QgsLayerTreeGroup* readXML( QDomElement& element, bool looseMatch = false );

//! Write group (tree) as XML element <layer-tree-group> and add it to the given parent element
virtual void writeXML( QDomElement& parentElement ) override;
//! Read children from XML and append them to the group.
void readChildrenFromXML( QDomElement& element );
/**
* Read children from XML and append them to the group. If the looseMatch
* parameter is true then legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
void readChildrenFromXML( QDomElement& element, bool looseMatch = false );

//! Return text representation of the tree. For debugging purposes only.
virtual QString dump() const override;
Expand Down
46 changes: 45 additions & 1 deletion src/core/layertree/qgslayertreelayer.cpp
Expand Up @@ -50,6 +50,13 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( const QgsLayerTreeLayer& other )
attachToLayer();
}

QgsLayerTreeLayer *QgsLayerTreeLayer::createLayerFromSource( const QString &source )
{
QgsLayerTreeLayer* l = new QgsLayerTreeLayer( QString() );
l->attachToSource( source );
return l;
}

void QgsLayerTreeLayer::attachToLayer()
{
// layer is not necessarily already loaded
Expand Down Expand Up @@ -81,6 +88,29 @@ void QgsLayerTreeLayer::setName( const QString& n )
setLayerName( n );
}

void QgsLayerTreeLayer::attachToSource( const QString &source )
{
// check if matching source already open
bool foundMatch = false;
Q_FOREACH ( QgsMapLayer* layer, QgsMapLayerRegistry::instance()->mapLayers() )
{
if ( layer->publicSource() == source )
{
// found a source! need to disconnect from layersAdded signal as original attachToLayer call
// will have set this up
disconnect( QgsMapLayerRegistry::instance(), SIGNAL( layersAdded( QList<QgsMapLayer*> ) ), this, SLOT( registryLayersAdded( QList<QgsMapLayer*> ) ) );
mLayerId = layer->id();
attachToLayer();
emit layerLoaded();
foundMatch = true;
break;
}
}

if ( !foundMatch )
mLayerSource = source; // no need to store source if match already made
}

QString QgsLayerTreeLayer::layerName() const
{
return mLayer ? mLayer->name() : mLayerName;
Expand Down Expand Up @@ -113,13 +143,14 @@ void QgsLayerTreeLayer::setVisible( Qt::CheckState state )
emit visibilityChanged( this, state );
}

QgsLayerTreeLayer* QgsLayerTreeLayer::readXML( QDomElement& element )
QgsLayerTreeLayer* QgsLayerTreeLayer::readXML( QDomElement& element , bool looseMatch )
{
if ( element.tagName() != "layer-tree-layer" )
return nullptr;

QString layerID = element.attribute( "id" );
QString layerName = element.attribute( "name" );
QString source = element.attribute( "source" );
Qt::CheckState checked = QgsLayerTreeUtils::checkStateFromXml( element.attribute( "checked" ) );
bool isExpanded = ( element.attribute( "expanded", "1" ) == "1" );

Expand All @@ -129,6 +160,10 @@ QgsLayerTreeLayer* QgsLayerTreeLayer::readXML( QDomElement& element )

if ( layer )
nodeLayer = new QgsLayerTreeLayer( layer );
else if ( looseMatch && !source.isEmpty() )
{
nodeLayer = QgsLayerTreeLayer::createLayerFromSource( source );
}
else
nodeLayer = new QgsLayerTreeLayer( layerID, layerName );

Expand All @@ -144,6 +179,9 @@ void QgsLayerTreeLayer::writeXML( QDomElement& parentElement )
QDomDocument doc = parentElement.ownerDocument();
QDomElement elem = doc.createElement( "layer-tree-layer" );
elem.setAttribute( "id", mLayerId );
if ( mLayer )
elem.setAttribute( "source", mLayer->publicSource() );

elem.setAttribute( "name", layerName() );
elem.setAttribute( "checked", QgsLayerTreeUtils::checkStateToXml( mVisible ) );
elem.setAttribute( "expanded", mExpanded ? "1" : "0" );
Expand All @@ -167,6 +205,12 @@ void QgsLayerTreeLayer::registryLayersAdded( const QList<QgsMapLayer*>& layers )
{
Q_FOREACH ( QgsMapLayer* l, layers )
{
if ( !mLayerSource.isEmpty() && l->publicSource() == mLayerSource )
{
// we are loosely matching, and found a layer with a matching source.
// Attach to this!
mLayerId = l->id();
}
if ( l->id() == mLayerId )
{
disconnect( QgsMapLayerRegistry::instance(), SIGNAL( layersAdded( QList<QgsMapLayer*> ) ), this, SLOT( registryLayersAdded( QList<QgsMapLayer*> ) ) );
Expand Down
27 changes: 26 additions & 1 deletion src/core/layertree/qgslayertreelayer.h
Expand Up @@ -45,6 +45,14 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
explicit QgsLayerTreeLayer( QgsMapLayer* layer );
QgsLayerTreeLayer( const QgsLayerTreeLayer& other );

/**
* Creates a layer node which will attach to a layer with a matching
* QgsMapLayer::publicSource(). This can be used for "looser" layer matching,
* avoiding the usual layer id check in favour of attaching to any layer
* with an equal source.
*/
static QgsLayerTreeLayer* createLayerFromSource( const QString& source );

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

QString layerId() const { return mLayerId; }
Expand All @@ -58,13 +66,27 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
//! @note added in 2.18.1
void setName( const QString& n ) override;

/**
* Attempts to attach this layer node to a layer with a matching
* QgsMapLayer::publicSource(). This can be used for "looser" layer matching,
* avoiding the usual layer id check in favour of attaching to any layer
* with an equal source.
*/
void attachToSource( const QString& source );

QString layerName() const;
void setLayerName( const QString& n );

Qt::CheckState isVisible() const { return mVisible; }
void setVisible( Qt::CheckState visible );

static QgsLayerTreeLayer* readXML( QDomElement& element );
/**
* Creates a new layer from an XML definition. If the looseMatch
* parameter is true then legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
static QgsLayerTreeLayer* readXML( QDomElement& element, bool looseMatch = false );

virtual void writeXML( QDomElement& parentElement ) override;

virtual QString dump() const override;
Expand All @@ -90,6 +112,9 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode

QString mLayerId;
QString mLayerName; // only used if layer does not exist
//! Only used when loosely matching to layers - eg when creating a composer legend from template
//! If set this will attach to the first matching layer with an equal source
QString mLayerSource;
QgsMapLayer* mLayer; // not owned! may be null
Qt::CheckState mVisible;
};
Expand Down
6 changes: 3 additions & 3 deletions src/core/layertree/qgslayertreenode.cpp
Expand Up @@ -47,13 +47,13 @@ QgsLayerTreeNode::~QgsLayerTreeNode()
qDeleteAll( mChildren );
}

QgsLayerTreeNode* QgsLayerTreeNode::readXML( QDomElement& element )
QgsLayerTreeNode* QgsLayerTreeNode::readXML( QDomElement& element, bool looseMatch )
{
QgsLayerTreeNode* node = nullptr;
if ( element.tagName() == "layer-tree-group" )
node = QgsLayerTreeGroup::readXML( element );
node = QgsLayerTreeGroup::readXML( element, looseMatch );
else if ( element.tagName() == "layer-tree-layer" )
node = QgsLayerTreeLayer::readXML( element );
node = QgsLayerTreeLayer::readXML( element, looseMatch );

return node;
}
Expand Down
9 changes: 7 additions & 2 deletions src/core/layertree/qgslayertreenode.h
Expand Up @@ -90,8 +90,13 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
//! @note added in 2.18.1
virtual void setName( const QString& name ) = 0;

//! Read layer tree from XML. Returns new instance
static QgsLayerTreeNode *readXML( QDomElement &element );
/**
* Read layer tree from XML. Returns new instance. If the looseMatch
* parameter is true then child legend layers will use looser matching criteria,
* eg testing layer source instead of layer IDs.
*/
static QgsLayerTreeNode *readXML( QDomElement &element, bool looseMatch = false );

//! Write layer tree to XML
virtual void writeXML( QDomElement &parentElement ) = 0;

Expand Down

0 comments on commit 22b3307

Please sign in to comment.