Make layer tree implementation independent from QgsProject::instance()

Another bit in the project refactoring work to get rid of dependencies
on QgsProject singleton.

Reading of layer trees from XML is now split into two phases:
1. read XML and keep layer IDs
2. resolve layer IDs to QgsMapLayer instances (using QgsProject)

There are convenience methods to do both phases in one go.
This commit is contained in:
Martin Dobias 2017-01-26 16:32:58 +08:00
parent d259cdf5d6
commit 137eb3a0f9
21 changed files with 281 additions and 117 deletions

View File

@ -1194,6 +1194,7 @@ QgsLayerTreeGroup {#qgis_api_break_3_0_QgsLayerTreeGroup}
- isVisible() is moved to QgsLayerTreeNode
- setVisible() is replaced by QgsLayerTreeNode::setItemVisibilityChecked()
- protected methods updateVisibilityFromChildren() and updateChildVisibility() removed
- readXml() and readChildrenFromXml() do not resolve layers from the layer IDs anymore. Call resolveReferences() or use readXml() override with QgsProject as the second argument.
QgsLayerTreeLayer {#qgis_api_break_3_0_QgsLayerTreeLayer}
-----------------
@ -1201,6 +1202,12 @@ QgsLayerTreeLayer {#qgis_api_break_3_0_QgsLayerTreeLayer}
- setLayerName(), layerName() were renamed to setName(), name()
- isVisible() is moved to QgsLayerTreeNode
- setVisible() is replaced by QgsLayerTreeNode::setItemVisibilityChecked()
- readXml() does not resolve layer from the layer ID anymore. Call resolveReferences() or use readXml() override with QgsProject as the second argument.
QgsLayerTreeNode {#qgis_api_break_3_0_QgsLayerTreeNode}
----------------
- readXml() does not resolve layers from the layer IDs anymore. Call resolveReferences() or use readXml() override with QgsProject as the second argument.
QgsLayerTreeModel {#qgis_api_break_3_0_QgsLayerTreeMode}

View File

@ -58,11 +58,18 @@ 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)
//! Read group (tree) from XML element <layer-tree-group> and return the newly created group (or null on error).
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
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).
//! Also resolves textual references to layers from the project (calls resolveReferences() internally).
//! @note added in 3.0
static QgsLayerTreeGroup* readXml( QDomElement& element, const QgsProject* project ) /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.
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
void readChildrenFromXml( QDomElement& element );
//! Return text representation of the tree. For debugging purposes only.
@ -71,6 +78,10 @@ class QgsLayerTreeGroup : QgsLayerTreeNode
//! Return a clone of the group. The children are cloned too.
virtual QgsLayerTreeGroup* clone() const /Factory/;
//! Calls resolveReferences() on child tree nodes
//! @note added in 3.0
virtual void resolveReferences( const QgsProject* project );
//! Return whether the group is mutually exclusive (only one child can be checked at a time)
//! @note added in 2.12
bool isMutuallyExclusive() const;

View File

@ -1,14 +1,10 @@
/**
* Layer tree node points to a map layer.
*
* When using with existing QgsMapLayer instance, it is expected that the layer
* has been registered in QgsProject earlier.
*
* The node can exist also without a valid instance of a layer (just ID). That
* means the referenced layer does not need to be loaded in order to use it
* in layer tree. In such case, the node will start listening to map layer
* registry updates in expectation that the layer (identified by its ID) will
* be loaded later.
* in layer tree. In such case, resolveReferences() method can be called
* once the layer is loaded.
*
* A map layer is supposed to be present in one layer tree just once. It is
* however possible that temporarily a layer exists in one tree more than just
@ -38,19 +34,23 @@ class QgsLayerTreeLayer : QgsLayerTreeNode
//! @note added in 3.0
void setName( const QString& n );
//! Read layer node from XML. Returns new instance.
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
static QgsLayerTreeLayer* readXml( QDomElement& element ) /Factory/;
//! Read layer node from XML. Returns new instance.
//! Also resolves textual references to layers from the project (calls resolveReferences() internally).
//! @note added in 3.0
static QgsLayerTreeLayer* readXml( QDomElement& element, const QgsProject* project ) /Factory/;
virtual void writeXml( QDomElement& parentElement );
virtual QString dump() const;
virtual QgsLayerTreeLayer* clone() const /Factory/;
protected slots:
void registryLayersAdded( const QList<QgsMapLayer*>& layers );
void registryLayersWillBeRemoved( const QStringList& layerIds );
//! Emits a nameChanged() signal if layer's name has changed
//! Resolves reference to layer from stored layer ID (if it has not been resolved already)
//! @note added in 3.0
void layerNameChanged();
virtual void resolveReferences( const QgsProject* project );
signals:
//! emitted when a previously unavailable layer got loaded

View File

@ -83,8 +83,14 @@ class QgsLayerTreeNode : QObject
//! @note added in 3.0
virtual void setName( const QString& name ) = 0;
//! Read layer tree from XML. Returns new instance
static QgsLayerTreeNode* readXml( QDomElement& element ) /Factory/;
//! Read layer tree from XML. Returns new instance.
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
static QgsLayerTreeNode *readXml( QDomElement &element ) /Factory/;
//! Read layer tree from XML. Returns new instance.
//! Also resolves textual references to layers from the project (calls resolveReferences() internally).
//! @note added in 3.0
static QgsLayerTreeNode* readXml( QDomElement& element, const QgsProject* project ) /Factory/;
//! Write layer tree to XML
virtual void writeXml( QDomElement &parentElement ) = 0;
@ -94,6 +100,11 @@ 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;
//! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
//! @note added in 3.0
bool isVisible() const;

View File

@ -11573,7 +11573,7 @@ void QgisApp::writeProject( QDomDocument &doc )
QgsLayerTreeNode* clonedRoot = QgsProject::instance()->layerTreeRoot()->clone();
QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ) ); // convert absolute paths to relative paths if required
QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), QgsProject::instance() ); // convert absolute paths to relative paths if required
QDomElement oldLegendElem = QgsLayerTreeUtils::writeOldLegend( doc, QgsLayerTree::toGroup( clonedRoot ),
mLayerTreeCanvasBridge->hasCustomLayerOrder(), mLayerTreeCanvasBridge->customLayerOrder() );
delete clonedRoot;

View File

@ -537,7 +537,7 @@ bool QgsComposerLegend::readXml( const QDomElement& itemElem, const QDomDocument
{
// QGIS >= 2.6
QDomElement layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree-group" ) );
setCustomLayerTree( QgsLayerTreeGroup::readXml( layerTreeElem ) );
setCustomLayerTree( QgsLayerTreeGroup::readXml( layerTreeElem, mComposition->project() ) );
}
//restore general composer item properties

View File

@ -18,7 +18,6 @@
#include "qgslayertree.h"
#include "qgslayertreeutils.h"
#include "qgsmaplayer.h"
#include "qgsproject.h"
#include <QDomElement>
#include <QStringList>
@ -73,9 +72,9 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::addGroup( const QString &name )
return grp;
}
QgsLayerTreeLayer*QgsLayerTreeGroup::insertLayer( int index, QgsMapLayer* layer )
QgsLayerTreeLayer* QgsLayerTreeGroup::insertLayer( int index, QgsMapLayer* layer )
{
if ( !layer || QgsProject::instance()->mapLayer( layer->id() ) != layer )
if ( !layer )
return nullptr;
QgsLayerTreeLayer* ll = new QgsLayerTreeLayer( layer );
@ -85,7 +84,7 @@ QgsLayerTreeLayer*QgsLayerTreeGroup::insertLayer( int index, QgsMapLayer* layer
QgsLayerTreeLayer* QgsLayerTreeGroup::addLayer( QgsMapLayer* layer )
{
if ( !layer || QgsProject::instance()->mapLayer( layer->id() ) != layer )
if ( !layer )
return nullptr;
QgsLayerTreeLayer* ll = new QgsLayerTreeLayer( layer );
@ -276,6 +275,14 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::readXml( QDomElement& element )
return groupNode;
}
QgsLayerTreeGroup* QgsLayerTreeGroup::readXml( QDomElement& element, const QgsProject* project )
{
QgsLayerTreeGroup* node = readXml( element );
if ( node )
node->resolveReferences( project );
return node;
}
void QgsLayerTreeGroup::writeXml( QDomElement& parentElement )
{
QDomDocument doc = parentElement.ownerDocument();
@ -329,6 +336,12 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::clone() const
return new QgsLayerTreeGroup( *this );
}
void QgsLayerTreeGroup::resolveReferences( const QgsProject* project )
{
Q_FOREACH ( QgsLayerTreeNode* node, mChildren )
node->resolveReferences( project );
}
static bool _nodeIsChecked( QgsLayerTreeNode* node )
{
return node->itemVisibilityChecked();

View File

@ -81,11 +81,18 @@ 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)
//! Read group (tree) from XML element <layer-tree-group> and return the newly created group (or null on error).
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
static QgsLayerTreeGroup* readXml( QDomElement& element );
//! Read group (tree) from XML element <layer-tree-group> and return the newly created group (or null on error).
//! Also resolves textual references to layers from the project (calls resolveReferences() internally).
//! @note added in 3.0
static QgsLayerTreeGroup* readXml( QDomElement& element, const QgsProject* project );
//! 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.
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
void readChildrenFromXml( QDomElement& element );
//! Return text representation of the tree. For debugging purposes only.
@ -94,6 +101,10 @@ class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode
//! Return a clone of the group. The children are cloned too.
virtual QgsLayerTreeGroup* clone() const override;
//! Calls resolveReferences() on child tree nodes
//! @note added in 3.0
virtual void resolveReferences( const QgsProject* project ) override;
//! Check or uncheck a node and all its children (taking into account exclusion rules)
virtual void setItemVisibilityCheckedRecursive( bool checked ) override;

View File

@ -22,64 +22,64 @@
QgsLayerTreeLayer::QgsLayerTreeLayer( QgsMapLayer *layer )
: QgsLayerTreeNode( NodeLayer, true )
, mLayerId( layer->id() )
, mLayer( nullptr )
, mRef( layer )
, mLayerName( layer->name() )
{
Q_ASSERT( QgsProject::instance()->mapLayer( mLayerId ) == layer );
attachToLayer();
}
QgsLayerTreeLayer::QgsLayerTreeLayer( const QString& layerId, const QString& name )
: QgsLayerTreeNode( NodeLayer, true )
, mLayerId( layerId )
, mLayerName( name )
, mLayer( nullptr )
, mRef( layerId )
, mLayerName( name.isEmpty() ? QStringLiteral( "(?)" ) : name )
{
attachToLayer();
}
QgsLayerTreeLayer::QgsLayerTreeLayer( const QgsLayerTreeLayer& other )
: QgsLayerTreeNode( other )
, mLayerId( other.mLayerId )
, mRef( other.mRef )
, mLayerName( other.mLayerName )
, mLayer( nullptr )
{
attachToLayer();
}
void QgsLayerTreeLayer::resolveReferences( const QgsProject* project )
{
if ( mRef.layer )
return; // already assigned
QgsMapLayer* layer = project->mapLayer( mRef.layerId );
if ( !layer )
return;
mRef.layer = layer;
mRef.layerId = layer->id();
attachToLayer();
emit layerLoaded();
}
void QgsLayerTreeLayer::attachToLayer()
{
// layer is not necessarily already loaded
QgsMapLayer* l = QgsProject::instance()->mapLayer( mLayerId );
if ( l )
{
mLayer = l;
mLayerName = l->name();
connect( l, SIGNAL( nameChanged() ), this, SLOT( layerNameChanged() ) );
// make sure we are notified if the layer is removed
connect( QgsProject::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ), this, SLOT( registryLayersWillBeRemoved( QStringList ) ) );
}
else
{
if ( mLayerName.isEmpty() )
mLayerName = QStringLiteral( "(?)" );
// wait for the layer to be eventually loaded
connect( QgsProject::instance(), SIGNAL( layersAdded( QList<QgsMapLayer*> ) ), this, SLOT( registryLayersAdded( QList<QgsMapLayer*> ) ) );
}
if ( !mRef.layer )
return;
connect( mRef.layer, &QgsMapLayer::nameChanged, this, &QgsLayerTreeLayer::layerNameChanged );
connect( mRef.layer, &QgsMapLayer::willBeDeleted, this, &QgsLayerTreeLayer::layerWillBeDeleted );
}
QString QgsLayerTreeLayer::name() const
{
return mLayer ? mLayer->name() : mLayerName;
return mRef.layer ? mRef.layer->name() : mLayerName;
}
void QgsLayerTreeLayer::setName( const QString& n )
{
if ( mLayer )
if ( mRef.layer )
{
if ( mLayer->name() == n )
if ( mRef.layer->name() == n )
return;
mLayer->setName( n );
mRef.layer->setName( n );
// no need to emit signal: we will be notified from layer's nameChanged() signal
}
else
@ -101,14 +101,8 @@ QgsLayerTreeLayer* QgsLayerTreeLayer::readXml( QDomElement& element )
Qt::CheckState checked = QgsLayerTreeUtils::checkStateFromXml( element.attribute( QStringLiteral( "checked" ) ) );
bool isExpanded = ( element.attribute( QStringLiteral( "expanded" ), QStringLiteral( "1" ) ) == QLatin1String( "1" ) );
QgsLayerTreeLayer* nodeLayer = nullptr;
QgsMapLayer* layer = QgsProject::instance()->mapLayer( layerID );
if ( layer )
nodeLayer = new QgsLayerTreeLayer( layer );
else
nodeLayer = new QgsLayerTreeLayer( layerID, layerName );
// needs to have the layer reference resolved later
QgsLayerTreeLayer* nodeLayer = new QgsLayerTreeLayer( layerID, layerName );
nodeLayer->readCommonXml( element );
@ -117,11 +111,19 @@ QgsLayerTreeLayer* QgsLayerTreeLayer::readXml( QDomElement& element )
return nodeLayer;
}
QgsLayerTreeLayer* QgsLayerTreeLayer::readXml( QDomElement& element, const QgsProject* project )
{
QgsLayerTreeLayer* node = readXml( element );
if ( node )
node->resolveReferences( project );
return node;
}
void QgsLayerTreeLayer::writeXml( QDomElement& parentElement )
{
QDomDocument doc = parentElement.ownerDocument();
QDomElement elem = doc.createElement( QStringLiteral( "layer-tree-layer" ) );
elem.setAttribute( QStringLiteral( "id" ), mLayerId );
elem.setAttribute( QStringLiteral( "id" ), layerId() );
elem.setAttribute( QStringLiteral( "name" ), name() );
elem.setAttribute( QStringLiteral( "checked" ), mChecked ? QStringLiteral( "Qt::Checked" ) : QStringLiteral( "Qt::Unchecked" ) );
elem.setAttribute( QStringLiteral( "expanded" ), mExpanded ? "1" : "0" );
@ -141,36 +143,21 @@ QgsLayerTreeLayer* QgsLayerTreeLayer::clone() const
return new QgsLayerTreeLayer( *this );
}
void QgsLayerTreeLayer::registryLayersAdded( const QList<QgsMapLayer*>& layers )
void QgsLayerTreeLayer::layerWillBeDeleted()
{
Q_FOREACH ( QgsMapLayer* l, layers )
{
if ( l->id() == mLayerId )
{
disconnect( QgsProject::instance(), SIGNAL( layersAdded( QList<QgsMapLayer*> ) ), this, SLOT( registryLayersAdded( QList<QgsMapLayer*> ) ) );
attachToLayer();
emit layerLoaded();
break;
}
}
Q_ASSERT( mRef.layer );
mLayerName = mRef.layer->name();
// in theory we do not even need to do this - the weak ref should clear itself
mRef.layer.clear();
// layerId stays in the reference
emit layerWillBeUnloaded();
}
void QgsLayerTreeLayer::registryLayersWillBeRemoved( const QStringList& layerIds )
{
if ( layerIds.contains( mLayerId ) )
{
emit layerWillBeUnloaded();
// stop listening to removal signals and start hoping that the layer may be added again
disconnect( QgsProject::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ), this, SLOT( registryLayersWillBeRemoved( QStringList ) ) );
connect( QgsProject::instance(), SIGNAL( layersAdded( QList<QgsMapLayer*> ) ), this, SLOT( registryLayersAdded( QList<QgsMapLayer*> ) ) );
mLayer = nullptr;
}
}
void QgsLayerTreeLayer::layerNameChanged()
{
Q_ASSERT( mLayer );
emit nameChanged( this, mLayer->name() );
Q_ASSERT( mRef.layer );
emit nameChanged( this, mRef.layer->name() );
}

View File

@ -18,20 +18,17 @@
#include "qgis_core.h"
#include "qgslayertreenode.h"
#include "qgsmaplayerref.h"
class QgsMapLayer;
/** \ingroup core
* Layer tree node points to a map layer.
*
* When using with existing QgsMapLayer instance, it is expected that the layer
* has been registered in QgsProject earlier.
*
* The node can exist also without a valid instance of a layer (just ID). That
* means the referenced layer does not need to be loaded in order to use it
* in layer tree. In such case, the node will start listening to map layer
* registry updates in expectation that the layer (identified by its ID) will
* be loaded later.
* in layer tree. In such case, resolveReferences() method can be called
* once the layer is loaded.
*
* A map layer is supposed to be present in one layer tree just once. It is
* however possible that temporarily a layer exists in one tree more than just
@ -48,9 +45,9 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
explicit QgsLayerTreeLayer( const QString& layerId, const QString& name = QString() );
QString layerId() const { return mLayerId; }
QString layerId() const { return mRef.layerId; }
QgsMapLayer* layer() const { return mLayer; }
QgsMapLayer* layer() const { return mRef.layer.data(); }
//! Get layer's name
//! @note added in 3.0
@ -59,19 +56,31 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
//! @note added in 3.0
void setName( const QString& n ) override;
//! Read layer node from XML. Returns new instance.
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
static QgsLayerTreeLayer* readXml( QDomElement& element );
//! Read layer node from XML. Returns new instance.
//! Also resolves textual references to layers from the project (calls resolveReferences() internally).
//! @note added in 3.0
static QgsLayerTreeLayer* readXml( QDomElement& element, const QgsProject* project );
virtual void writeXml( QDomElement& parentElement ) override;
virtual QString dump() const override;
virtual QgsLayerTreeLayer* clone() const override;
protected slots:
void registryLayersAdded( const QList<QgsMapLayer*>& layers );
void registryLayersWillBeRemoved( const QStringList& layerIds );
//! 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 ) override;
private slots:
//! Emits a nameChanged() signal if layer's name has changed
//! @note added in 3.0
void layerNameChanged();
//! Handles the event of deletion of the referenced layer
//! @note added in 3.0
void layerWillBeDeleted();
signals:
//! emitted when a previously unavailable layer got loaded
@ -83,9 +92,10 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
protected:
void attachToLayer();
QString mLayerId;
QString mLayerName; // only used if layer does not exist
QgsMapLayer* mLayer; // not owned! may be null
//! Weak reference to the layer (or just it's ID if the reference is not resolved yet)
QgsMapLayerRef mRef;
//! Layer name - only used if layer does not exist
QString mLayerName;
};

View File

@ -18,6 +18,7 @@
#include "qgslayertree.h"
#include "qgslayertreeutils.h"
#include "qgslayertreemodellegendnode.h"
#include "qgsproject.h"
#include <QMimeData>
#include <QTextStream>
@ -1057,7 +1058,7 @@ bool QgsLayerTreeModel::dropMimeData( const QMimeData* data, Qt::DropAction acti
QDomElement elem = rootElem.firstChildElement();
while ( !elem.isNull() )
{
QgsLayerTreeNode* node = QgsLayerTreeNode::readXml( elem );
QgsLayerTreeNode* node = QgsLayerTreeNode::readXml( elem, QgsProject::instance() );
if ( node )
nodes << node;

View File

@ -60,6 +60,14 @@ QgsLayerTreeNode* QgsLayerTreeNode::readXml( QDomElement& element )
return node;
}
QgsLayerTreeNode* QgsLayerTreeNode::readXml( QDomElement& element, const QgsProject* project )
{
QgsLayerTreeNode* node = readXml( element );
if ( node )
node->resolveReferences( project );
return node;
}
void QgsLayerTreeNode::setItemVisibilityChecked( bool checked )
{

View File

@ -23,6 +23,8 @@
class QDomElement;
class QgsProject;
/** \ingroup core
* This class is a base class for nodes in a layer tree.
* Layer tree is a hierarchical structure consisting of group and layer nodes:
@ -93,8 +95,14 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
//! @note added in 3.0
virtual void setName( const QString& name ) = 0;
//! Read layer tree from XML. Returns new instance
//! Read layer tree from XML. Returns new instance.
//! Does not resolve textual references to layers. Call resolveReferences() afterwards to do it.
static QgsLayerTreeNode *readXml( QDomElement &element );
//! Read layer tree from XML. Returns new instance.
//! Also resolves textual references to layers from the project (calls resolveReferences() internally).
//! @note added in 3.0
static QgsLayerTreeNode* readXml( QDomElement& element, const QgsProject* project );
//! Write layer tree to XML
virtual void writeXml( QDomElement &parentElement ) = 0;
@ -104,6 +112,11 @@ 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()
//! @note added in 3.0
virtual void resolveReferences( const QgsProject* project ) = 0;
//! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
//! @note added in 3.0
bool isVisible() const;

View File

@ -348,20 +348,20 @@ void QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTreeGroup* grou
}
void QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTreeGroup* group )
void QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTreeGroup* group, const QgsProject* project )
{
Q_FOREACH ( QgsLayerTreeNode* node, group->children() )
{
if ( !node->customProperty( QStringLiteral( "embedded_project" ) ).toString().isEmpty() )
{
// may change from absolute path to relative path
QString newPath = QgsProject::instance()->writePath( node->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
QString newPath = project->writePath( node->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
node->setCustomProperty( QStringLiteral( "embedded_project" ), newPath );
}
if ( QgsLayerTree::isGroup( node ) )
{
updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( node ) );
updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( node ), project );
}
}
}

View File

@ -29,6 +29,7 @@ class QgsLayerTreeNode;
class QgsLayerTreeGroup;
class QgsLayerTreeLayer;
class QgsMapLayer;
class QgsProject;
/** \ingroup core
* Assorted functions for dealing with layer trees.
@ -63,7 +64,7 @@ class CORE_EXPORT QgsLayerTreeUtils
static void replaceChildrenOfEmbeddedGroups( QgsLayerTreeGroup* group );
//! @note not available in python bindings
static void updateEmbeddedGroupsProjectPath( QgsLayerTreeGroup* group );
static void updateEmbeddedGroupsProjectPath( QgsLayerTreeGroup* group, const QgsProject* project );
//! get invisible layers
static QStringList invisibleLayerList( QgsLayerTreeNode *node );

View File

@ -23,6 +23,7 @@
#include "qgsvectorlayer.h"
#include "qgslayertree.h"
#include "qgslayerdefinition.h"
#include "qgsproject.h"
bool QgsLayerDefinition::loadLayerDefinition( const QString &path, QgsLayerTreeGroup *rootGroup, QString &errorMessage )
{
@ -149,6 +150,8 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
}
}
root->resolveReferences( QgsProject::instance() );
QList<QgsLayerTreeNode*> nodes = root->children();
Q_FOREACH ( QgsLayerTreeNode *node, nodes )
root->takeChild( node );

36
src/core/qgsmaplayerref.h Normal file
View File

@ -0,0 +1,36 @@
/***************************************************************************
qgsmaplayerref.h
--------------------------------------
Date : January 2017
Copyright : (C) 2017 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSMAPLAYERREF_H
#define QGSMAPLAYERREF_H
#include <QPointer>
#include "qgsmaplayer.h"
/** Internal structure to keep weak pointer to QgsMapLayer or layerId
* if the layer is not available yet.
* @note not available in python bindings
*/
struct QgsMapLayerRef
{
QgsMapLayerRef( QgsMapLayer* l = nullptr ): layer( l ), layerId( l ? l->id() : QString() ) {}
QgsMapLayerRef( const QString& id ): layer(), layerId( id ) {}
QPointer<QgsMapLayer> layer;
QString layerId;
};
#endif // QGSMAPLAYERREF_H

View File

@ -341,6 +341,7 @@ QgsProject::~QgsProject()
{
delete mBadLayerHandler;
delete mRelationManager;
delete mLayerTreeRegistryBridge;
delete mRootGroup;
removeAllMapLayers();
@ -867,6 +868,7 @@ bool QgsProject::read()
QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
if ( !layerTreeElem.isNull() )
{
// read the tree but do not resolve the references (we have not loaded the layers yet)
mRootGroup->readChildrenFromXml( layerTreeElem );
}
else
@ -897,6 +899,9 @@ bool QgsProject::read()
mBadLayerHandler->handleBadLayers( brokenNodes );
}
// now that layers are loaded, we can resolve layer tree's references to the layers
mRootGroup->resolveReferences( this );
mLayerTreeRegistryBridge->setEnabled( true );
// load embedded groups and layers
@ -1211,7 +1216,7 @@ bool QgsProject::write()
// write layer tree - make sure it is without embedded subgroups
QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ) ); // convert absolute paths to relative paths if required
QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), this ); // convert absolute paths to relative paths if required
clonedRoot->writeXml( qgisNode );
delete clonedRoot;

View File

@ -1253,6 +1253,7 @@ QList<QDomElement> QgsServerProjectParser::findLegendGroupElements() const
QDomElement layerTreeElem = mXMLDoc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
if ( !layerTreeElem.isNull() )
{
// this is apparently only used to retrieve groups - layers do not need to be resolved
rootLayerTreeGroup = QgsLayerTreeGroup::readXml( layerTreeElem );
}

View File

@ -2362,7 +2362,7 @@ QgsLayerTreeGroup* QgsWmsProjectParser::projectLayerTreeGroup() const
QgsLayerTreeUtils::readOldLegend( rootGroup, mProjectParser->legendElem() );
return rootGroup;
}
return QgsLayerTreeGroup::readXml( layerTreeElem );
return QgsLayerTreeGroup::readXml( layerTreeElem, QgsProject::instance() );
}
bool QgsWmsProjectParser::annotationPosition( const QDomElement& elem, double scaleFactor, double& xPos, double& yPos )

View File

@ -45,6 +45,7 @@ class TestQgsLayerTree : public QObject
void testLegendSymbolCategorized();
void testLegendSymbolGraduated();
void testLegendSymbolRuleBased();
void testResolveReferences();
private:
@ -124,7 +125,9 @@ void TestQgsLayerTree::testLayerNameChanged()
QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n );
QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 1" ) );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer*>() << vl );
QgsProject project;
project.addMapLayer( vl );
n->resolveReferences( &project );
// set name via map layer
vl->setName( "changed 2" );
@ -142,8 +145,6 @@ void TestQgsLayerTree::testLayerNameChanged()
QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n );
QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 3" ) );
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
mRoot->removeChildNode( n );
}
@ -286,7 +287,8 @@ void TestQgsLayerTree::testShowHideAllSymbolNodes()
QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer*>() << vl );
QgsProject project;
project.addMapLayer( vl );
//create a categorized renderer for layer
QgsCategorizedSymbolRenderer* renderer = new QgsCategorizedSymbolRenderer();
@ -327,7 +329,6 @@ void TestQgsLayerTree::testShowHideAllSymbolNodes()
//cleanup
delete m;
delete root;
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
}
void TestQgsLayerTree::testFindLegendNode()
@ -336,7 +337,8 @@ void TestQgsLayerTree::testFindLegendNode()
QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer*>() << vl );
QgsProject project;
project.addMapLayer( vl );
//create a categorized renderer for layer
QgsCategorizedSymbolRenderer* renderer = new QgsCategorizedSymbolRenderer();
@ -369,7 +371,6 @@ void TestQgsLayerTree::testFindLegendNode()
//cleanup
delete m;
delete root;
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
}
void TestQgsLayerTree::testLegendSymbolCategorized()
@ -419,6 +420,50 @@ void TestQgsLayerTree::testLegendSymbolRuleBased()
testRendererLegend( renderer );
}
void TestQgsLayerTree::testResolveReferences()
{
QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() );
QString n1id = vl->id();
QString n2id = "XYZ";
QgsMapLayer* nullLayer = nullptr; // QCOMPARE does not like nullptr directly
QgsLayerTreeGroup* root = new QgsLayerTreeGroup();
QgsLayerTreeLayer* n1 = new QgsLayerTreeLayer( n1id, vl->name() );
QgsLayerTreeLayer* n2 = new QgsLayerTreeLayer( n2id, "invalid layer" );
root->addChildNode( n1 );
root->addChildNode( n2 );
// layer object not yet accessible
QCOMPARE( n1->layer(), nullLayer );
QCOMPARE( n1->layerId(), n1id );
QCOMPARE( n2->layer(), nullLayer );
QCOMPARE( n2->layerId(), n2id );
QgsProject project;
project.addMapLayer( vl );
root->resolveReferences( &project );
// now the layer should be accessible
QCOMPARE( n1->layer(), vl );
QCOMPARE( n1->layerId(), n1id );
QCOMPARE( n2->layer(), nullLayer );
QCOMPARE( n2->layerId(), n2id );
project.removeMapLayer( vl ); // deletes the layer
// layer object not accessible anymore
QCOMPARE( n1->layer(), nullLayer );
QCOMPARE( n1->layerId(), n1id );
QCOMPARE( n2->layer(), nullLayer );
QCOMPARE( n2->layerId(), n2id );
delete root;
}
void TestQgsLayerTree::testRendererLegend( QgsFeatureRenderer* renderer )
{
// runs renderer legend through a bunch of legend symbol tests
@ -430,7 +475,9 @@ void TestQgsLayerTree::testRendererLegend( QgsFeatureRenderer* renderer )
QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer*>() << vl );
QgsProject project;
project.addMapLayer( vl );
vl->setRenderer( renderer );
//create legend with symbology nodes for renderer
@ -471,7 +518,6 @@ void TestQgsLayerTree::testRendererLegend( QgsFeatureRenderer* renderer )
//cleanup
delete m;
delete root;
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
}