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 - isVisible() is moved to QgsLayerTreeNode
- setVisible() is replaced by QgsLayerTreeNode::setItemVisibilityChecked() - setVisible() is replaced by QgsLayerTreeNode::setItemVisibilityChecked()
- protected methods updateVisibilityFromChildren() and updateChildVisibility() removed - 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} 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() - setLayerName(), layerName() were renamed to setName(), name()
- isVisible() is moved to QgsLayerTreeNode - isVisible() is moved to QgsLayerTreeNode
- setVisible() is replaced by QgsLayerTreeNode::setItemVisibilityChecked() - 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} 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. //! Find group node with specified name. Searches recursively the whole sub-tree.
QgsLayerTreeGroup* findGroup( const QString& name ); 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/; 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 //! Write group (tree) as XML element <layer-tree-group> and add it to the given parent element
virtual void writeXml( QDomElement& parentElement ); virtual void writeXml( QDomElement& parentElement );
//! Read children from XML and append them to the group. //! 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 ); void readChildrenFromXml( QDomElement& element );
//! Return text representation of the tree. For debugging purposes only. //! 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. //! Return a clone of the group. The children are cloned too.
virtual QgsLayerTreeGroup* clone() const /Factory/; 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) //! Return whether the group is mutually exclusive (only one child can be checked at a time)
//! @note added in 2.12 //! @note added in 2.12
bool isMutuallyExclusive() const; bool isMutuallyExclusive() const;

View File

@ -1,14 +1,10 @@
/** /**
* Layer tree node points to a map layer. * 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 * 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 * 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 * in layer tree. In such case, resolveReferences() method can be called
* registry updates in expectation that the layer (identified by its ID) will * once the layer is loaded.
* be loaded later.
* *
* A map layer is supposed to be present in one layer tree just once. It is * 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 * 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 //! @note added in 3.0
void setName( const QString& n ); 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/; 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 void writeXml( QDomElement& parentElement );
virtual QString dump() const; virtual QString dump() const;
virtual QgsLayerTreeLayer* clone() const /Factory/; virtual QgsLayerTreeLayer* clone() const /Factory/;
protected slots: //! Resolves reference to layer from stored layer ID (if it has not been resolved already)
void registryLayersAdded( const QList<QgsMapLayer*>& layers );
void registryLayersWillBeRemoved( const QStringList& layerIds );
//! Emits a nameChanged() signal if layer's name has changed
//! @note added in 3.0 //! @note added in 3.0
void layerNameChanged(); virtual void resolveReferences( const QgsProject* project );
signals: signals:
//! emitted when a previously unavailable layer got loaded //! emitted when a previously unavailable layer got loaded

View File

@ -83,8 +83,14 @@ class QgsLayerTreeNode : QObject
//! @note added in 3.0 //! @note added in 3.0
virtual void setName( const QString& name ) = 0; virtual void setName( const QString& name ) = 0;
//! Read layer tree from XML. Returns new instance //! Read layer tree from XML. Returns new instance.
static QgsLayerTreeNode* readXml( QDomElement& element ) /Factory/; //! 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 //! Write layer tree to XML
virtual void writeXml( QDomElement &parentElement ) = 0; virtual void writeXml( QDomElement &parentElement ) = 0;
@ -94,6 +100,11 @@ class QgsLayerTreeNode : QObject
//! Create a copy of the node. Returns new instance //! Create a copy of the node. Returns new instance
virtual QgsLayerTreeNode *clone() const = 0 /Factory/; 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) //! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
//! @note added in 3.0 //! @note added in 3.0
bool isVisible() const; bool isVisible() const;

View File

@ -11573,7 +11573,7 @@ void QgisApp::writeProject( QDomDocument &doc )
QgsLayerTreeNode* clonedRoot = QgsProject::instance()->layerTreeRoot()->clone(); QgsLayerTreeNode* clonedRoot = QgsProject::instance()->layerTreeRoot()->clone();
QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) ); 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 ), QDomElement oldLegendElem = QgsLayerTreeUtils::writeOldLegend( doc, QgsLayerTree::toGroup( clonedRoot ),
mLayerTreeCanvasBridge->hasCustomLayerOrder(), mLayerTreeCanvasBridge->customLayerOrder() ); mLayerTreeCanvasBridge->hasCustomLayerOrder(), mLayerTreeCanvasBridge->customLayerOrder() );
delete clonedRoot; delete clonedRoot;

View File

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

View File

@ -18,7 +18,6 @@
#include "qgslayertree.h" #include "qgslayertree.h"
#include "qgslayertreeutils.h" #include "qgslayertreeutils.h"
#include "qgsmaplayer.h" #include "qgsmaplayer.h"
#include "qgsproject.h"
#include <QDomElement> #include <QDomElement>
#include <QStringList> #include <QStringList>
@ -73,9 +72,9 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::addGroup( const QString &name )
return grp; 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; return nullptr;
QgsLayerTreeLayer* ll = new QgsLayerTreeLayer( layer ); QgsLayerTreeLayer* ll = new QgsLayerTreeLayer( layer );
@ -85,7 +84,7 @@ QgsLayerTreeLayer*QgsLayerTreeGroup::insertLayer( int index, QgsMapLayer* layer
QgsLayerTreeLayer* QgsLayerTreeGroup::addLayer( QgsMapLayer* layer ) QgsLayerTreeLayer* QgsLayerTreeGroup::addLayer( QgsMapLayer* layer )
{ {
if ( !layer || QgsProject::instance()->mapLayer( layer->id() ) != layer ) if ( !layer )
return nullptr; return nullptr;
QgsLayerTreeLayer* ll = new QgsLayerTreeLayer( layer ); QgsLayerTreeLayer* ll = new QgsLayerTreeLayer( layer );
@ -276,6 +275,14 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::readXml( QDomElement& element )
return groupNode; 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 ) void QgsLayerTreeGroup::writeXml( QDomElement& parentElement )
{ {
QDomDocument doc = parentElement.ownerDocument(); QDomDocument doc = parentElement.ownerDocument();
@ -329,6 +336,12 @@ QgsLayerTreeGroup* QgsLayerTreeGroup::clone() const
return new QgsLayerTreeGroup( *this ); return new QgsLayerTreeGroup( *this );
} }
void QgsLayerTreeGroup::resolveReferences( const QgsProject* project )
{
Q_FOREACH ( QgsLayerTreeNode* node, mChildren )
node->resolveReferences( project );
}
static bool _nodeIsChecked( QgsLayerTreeNode* node ) static bool _nodeIsChecked( QgsLayerTreeNode* node )
{ {
return node->itemVisibilityChecked(); 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. //! Find group node with specified name. Searches recursively the whole sub-tree.
QgsLayerTreeGroup* findGroup( const QString& name ); 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 ); 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 //! Write group (tree) as XML element <layer-tree-group> and add it to the given parent element
virtual void writeXml( QDomElement& parentElement ) override; virtual void writeXml( QDomElement& parentElement ) override;
//! Read children from XML and append them to the group. //! 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 ); void readChildrenFromXml( QDomElement& element );
//! Return text representation of the tree. For debugging purposes only. //! 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. //! Return a clone of the group. The children are cloned too.
virtual QgsLayerTreeGroup* clone() const override; 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) //! Check or uncheck a node and all its children (taking into account exclusion rules)
virtual void setItemVisibilityCheckedRecursive( bool checked ) override; virtual void setItemVisibilityCheckedRecursive( bool checked ) override;

View File

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

View File

@ -18,20 +18,17 @@
#include "qgis_core.h" #include "qgis_core.h"
#include "qgslayertreenode.h" #include "qgslayertreenode.h"
#include "qgsmaplayerref.h"
class QgsMapLayer; class QgsMapLayer;
/** \ingroup core /** \ingroup core
* Layer tree node points to a map layer. * 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 * 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 * 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 * in layer tree. In such case, resolveReferences() method can be called
* registry updates in expectation that the layer (identified by its ID) will * once the layer is loaded.
* be loaded later.
* *
* A map layer is supposed to be present in one layer tree just once. It is * 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 * 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() ); 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 //! Get layer's name
//! @note added in 3.0 //! @note added in 3.0
@ -59,19 +56,31 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
//! @note added in 3.0 //! @note added in 3.0
void setName( const QString& n ) override; 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 ); 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 void writeXml( QDomElement& parentElement ) override;
virtual QString dump() const override; virtual QString dump() const override;
virtual QgsLayerTreeLayer* clone() const override; virtual QgsLayerTreeLayer* clone() const override;
protected slots: //! Resolves reference to layer from stored layer ID (if it has not been resolved already)
void registryLayersAdded( const QList<QgsMapLayer*>& layers ); //! @note added in 3.0
void registryLayersWillBeRemoved( const QStringList& layerIds ); virtual void resolveReferences( const QgsProject* project ) override;
private slots:
//! Emits a nameChanged() signal if layer's name has changed //! Emits a nameChanged() signal if layer's name has changed
//! @note added in 3.0 //! @note added in 3.0
void layerNameChanged(); void layerNameChanged();
//! Handles the event of deletion of the referenced layer
//! @note added in 3.0
void layerWillBeDeleted();
signals: signals:
//! emitted when a previously unavailable layer got loaded //! emitted when a previously unavailable layer got loaded
@ -83,9 +92,10 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
protected: protected:
void attachToLayer(); void attachToLayer();
QString mLayerId; //! Weak reference to the layer (or just it's ID if the reference is not resolved yet)
QString mLayerName; // only used if layer does not exist QgsMapLayerRef mRef;
QgsMapLayer* mLayer; // not owned! may be null //! Layer name - only used if layer does not exist
QString mLayerName;
}; };

View File

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

View File

@ -60,6 +60,14 @@ QgsLayerTreeNode* QgsLayerTreeNode::readXml( QDomElement& element )
return node; 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 ) void QgsLayerTreeNode::setItemVisibilityChecked( bool checked )
{ {

View File

@ -23,6 +23,8 @@
class QDomElement; class QDomElement;
class QgsProject;
/** \ingroup core /** \ingroup core
* This class is a base class for nodes in a layer tree. * This class is a base class for nodes in a layer tree.
* Layer tree is a hierarchical structure consisting of group and layer nodes: * 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 //! @note added in 3.0
virtual void setName( const QString& name ) = 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 ); 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 //! Write layer tree to XML
virtual void writeXml( QDomElement &parentElement ) = 0; 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 //! Create a copy of the node. Returns new instance
virtual QgsLayerTreeNode *clone() const = 0; 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) //! Returns whether a node is really visible (ie checked and all its ancestors checked as well)
//! @note added in 3.0 //! @note added in 3.0
bool isVisible() const; 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() ) Q_FOREACH ( QgsLayerTreeNode* node, group->children() )
{ {
if ( !node->customProperty( QStringLiteral( "embedded_project" ) ).toString().isEmpty() ) if ( !node->customProperty( QStringLiteral( "embedded_project" ) ).toString().isEmpty() )
{ {
// may change from absolute path to relative path // 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 ); node->setCustomProperty( QStringLiteral( "embedded_project" ), newPath );
} }
if ( QgsLayerTree::isGroup( node ) ) if ( QgsLayerTree::isGroup( node ) )
{ {
updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( node ) ); updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( node ), project );
} }
} }
} }

View File

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

View File

@ -23,6 +23,7 @@
#include "qgsvectorlayer.h" #include "qgsvectorlayer.h"
#include "qgslayertree.h" #include "qgslayertree.h"
#include "qgslayerdefinition.h" #include "qgslayerdefinition.h"
#include "qgsproject.h"
bool QgsLayerDefinition::loadLayerDefinition( const QString &path, QgsLayerTreeGroup *rootGroup, QString &errorMessage ) 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(); QList<QgsLayerTreeNode*> nodes = root->children();
Q_FOREACH ( QgsLayerTreeNode *node, nodes ) Q_FOREACH ( QgsLayerTreeNode *node, nodes )
root->takeChild( node ); 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 mBadLayerHandler;
delete mRelationManager; delete mRelationManager;
delete mLayerTreeRegistryBridge;
delete mRootGroup; delete mRootGroup;
removeAllMapLayers(); removeAllMapLayers();
@ -867,6 +868,7 @@ bool QgsProject::read()
QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) ); QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
if ( !layerTreeElem.isNull() ) if ( !layerTreeElem.isNull() )
{ {
// read the tree but do not resolve the references (we have not loaded the layers yet)
mRootGroup->readChildrenFromXml( layerTreeElem ); mRootGroup->readChildrenFromXml( layerTreeElem );
} }
else else
@ -897,6 +899,9 @@ bool QgsProject::read()
mBadLayerHandler->handleBadLayers( brokenNodes ); mBadLayerHandler->handleBadLayers( brokenNodes );
} }
// now that layers are loaded, we can resolve layer tree's references to the layers
mRootGroup->resolveReferences( this );
mLayerTreeRegistryBridge->setEnabled( true ); mLayerTreeRegistryBridge->setEnabled( true );
// load embedded groups and layers // load embedded groups and layers
@ -1211,7 +1216,7 @@ bool QgsProject::write()
// write layer tree - make sure it is without embedded subgroups // write layer tree - make sure it is without embedded subgroups
QgsLayerTreeNode *clonedRoot = mRootGroup->clone(); QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) ); 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 ); clonedRoot->writeXml( qgisNode );
delete clonedRoot; delete clonedRoot;

View File

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

View File

@ -2362,7 +2362,7 @@ QgsLayerTreeGroup* QgsWmsProjectParser::projectLayerTreeGroup() const
QgsLayerTreeUtils::readOldLegend( rootGroup, mProjectParser->legendElem() ); QgsLayerTreeUtils::readOldLegend( rootGroup, mProjectParser->legendElem() );
return rootGroup; return rootGroup;
} }
return QgsLayerTreeGroup::readXml( layerTreeElem ); return QgsLayerTreeGroup::readXml( layerTreeElem, QgsProject::instance() );
} }
bool QgsWmsProjectParser::annotationPosition( const QDomElement& elem, double scaleFactor, double& xPos, double& yPos ) 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 testLegendSymbolCategorized();
void testLegendSymbolGraduated(); void testLegendSymbolGraduated();
void testLegendSymbolRuleBased(); void testLegendSymbolRuleBased();
void testResolveReferences();
private: private:
@ -124,7 +125,9 @@ void TestQgsLayerTree::testLayerNameChanged()
QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n ); QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n );
QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 1" ) ); 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 // set name via map layer
vl->setName( "changed 2" ); vl->setName( "changed 2" );
@ -142,8 +145,6 @@ void TestQgsLayerTree::testLayerNameChanged()
QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n ); QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n );
QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 3" ) ); QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 3" ) );
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
mRoot->removeChildNode( n ); mRoot->removeChildNode( n );
} }
@ -286,7 +287,8 @@ void TestQgsLayerTree::testShowHideAllSymbolNodes()
QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ); QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() ); QVERIFY( vl->isValid() );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer*>() << vl ); QgsProject project;
project.addMapLayer( vl );
//create a categorized renderer for layer //create a categorized renderer for layer
QgsCategorizedSymbolRenderer* renderer = new QgsCategorizedSymbolRenderer(); QgsCategorizedSymbolRenderer* renderer = new QgsCategorizedSymbolRenderer();
@ -327,7 +329,6 @@ void TestQgsLayerTree::testShowHideAllSymbolNodes()
//cleanup //cleanup
delete m; delete m;
delete root; delete root;
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
} }
void TestQgsLayerTree::testFindLegendNode() void TestQgsLayerTree::testFindLegendNode()
@ -336,7 +337,8 @@ void TestQgsLayerTree::testFindLegendNode()
QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ); QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() ); QVERIFY( vl->isValid() );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer*>() << vl ); QgsProject project;
project.addMapLayer( vl );
//create a categorized renderer for layer //create a categorized renderer for layer
QgsCategorizedSymbolRenderer* renderer = new QgsCategorizedSymbolRenderer(); QgsCategorizedSymbolRenderer* renderer = new QgsCategorizedSymbolRenderer();
@ -369,7 +371,6 @@ void TestQgsLayerTree::testFindLegendNode()
//cleanup //cleanup
delete m; delete m;
delete root; delete root;
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
} }
void TestQgsLayerTree::testLegendSymbolCategorized() void TestQgsLayerTree::testLegendSymbolCategorized()
@ -419,6 +420,50 @@ void TestQgsLayerTree::testLegendSymbolRuleBased()
testRendererLegend( renderer ); 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 ) void TestQgsLayerTree::testRendererLegend( QgsFeatureRenderer* renderer )
{ {
// runs renderer legend through a bunch of legend symbol tests // 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" ) ); QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() ); QVERIFY( vl->isValid() );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer*>() << vl ); QgsProject project;
project.addMapLayer( vl );
vl->setRenderer( renderer ); vl->setRenderer( renderer );
//create legend with symbology nodes for renderer //create legend with symbology nodes for renderer
@ -471,7 +518,6 @@ void TestQgsLayerTree::testRendererLegend( QgsFeatureRenderer* renderer )
//cleanup //cleanup
delete m; delete m;
delete root; delete root;
QgsProject::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
} }