mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
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:
parent
d259cdf5d6
commit
137eb3a0f9
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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() );
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 );
|
||||
|
@ -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
36
src/core/qgsmaplayerref.h
Normal 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
|
@ -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;
|
||||
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
@ -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 )
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user