Skip to content
Permalink
Browse files
Allow association of a QgsGroupLayer with a QgsLayerTreeGroup
and ensure group layer state correctly reflects the state of the
layer tree group
  • Loading branch information
nyalldawson committed Nov 23, 2021
1 parent 54ad83a commit 921541d0470ced2bb7cf28f4fb2833a2becbd3a5
@@ -16,6 +16,9 @@ Layer tree group node serves as a container for layers and further groups.

Group names do not need to be unique within one tree nor within one parent.

While a layer tree group is typically used for hierarchical organisation of a :py:class:`QgsProject`,
they can optionally be associated with a :py:class:`QgsGroupLayer` for map rendering purposes.

.. versionadded:: 2.4
%End

@@ -206,6 +209,51 @@ The initial child index determines which child should be initially checked. The
of -1 will determine automatically (either first one currently checked or none)

.. versionadded:: 2.12
%End

QgsGroupLayer *groupLayer();
%Docstring
Returns a reference to the associated group layer, if the layer tree group will be treated
as group layer during map rendering.

.. seealso:: :py:func:`setGroupLayer`

.. seealso:: :py:func:`convertToGroupLayer`

.. versionadded:: 3.24
%End

void setGroupLayer( QgsGroupLayer *layer );
%Docstring
Sets the associated group ``layer``, if the layer tree group will be treated
as group layer during map rendering.

This method does not take ownership of the group layer, and only a weak reference
to the layer is stored.

.. seealso:: :py:func:`groupLayer`

.. seealso:: :py:func:`convertToGroupLayer`

.. versionadded:: 3.24
%End

QgsGroupLayer *convertToGroupLayer( const QgsGroupLayer::LayerOptions &options ) /Factory/;
%Docstring
Converts the group to a :py:class:`QgsGroupLayer`.

This method will convert the layer tree group to an equivalent :py:class:`QgsGroupLayer`, and
return the result. The caller takes ownership of the returned layer, and it is the
caller's responsibility to add the layer to the associated :py:class:`QgsProject`.

If the group is already associated with a group layer (see :py:func:`~QgsLayerTreeGroup.groupLayer`), ``None``
will be returned.

.. seealso:: :py:func:`groupLayer`

.. seealso:: :py:func:`setGroupLayer`

.. versionadded:: 3.24
%End

protected slots:
@@ -18,6 +18,7 @@
#include "qgslayertree.h"
#include "qgslayertreeutils.h"
#include "qgsmaplayer.h"
#include "qgsgrouplayer.h"

#include <QDomElement>
#include <QStringList>
@@ -27,7 +28,7 @@ QgsLayerTreeGroup::QgsLayerTreeGroup( const QString &name, bool checked )
: QgsLayerTreeNode( NodeGroup, checked )
, mName( name )
{
connect( this, &QgsLayerTreeNode::visibilityChanged, this, &QgsLayerTreeGroup::nodeVisibilityChanged );
init();
}

QgsLayerTreeGroup::QgsLayerTreeGroup( const QgsLayerTreeGroup &other )
@@ -36,8 +37,17 @@ QgsLayerTreeGroup::QgsLayerTreeGroup( const QgsLayerTreeGroup &other )
, mChangingChildVisibility( other.mChangingChildVisibility )
, mMutuallyExclusive( other.mMutuallyExclusive )
, mMutuallyExclusiveChildIndex( other.mMutuallyExclusiveChildIndex )
, mGroupLayer( other.mGroupLayer )
{
init();
}

void QgsLayerTreeGroup::init()
{
connect( this, &QgsLayerTreeNode::visibilityChanged, this, &QgsLayerTreeGroup::nodeVisibilityChanged );
connect( this, &QgsLayerTreeNode::addedChildren, this, &QgsLayerTreeGroup::updateGroupLayers );
connect( this, &QgsLayerTreeNode::removedChildren, this, &QgsLayerTreeGroup::updateGroupLayers );
connect( this, &QgsLayerTreeNode::visibilityChanged, this, &QgsLayerTreeGroup::updateGroupLayers );
}

QString QgsLayerTreeGroup::name() const
@@ -76,6 +86,8 @@ QgsLayerTreeLayer *QgsLayerTreeGroup::insertLayer( int index, QgsMapLayer *layer

QgsLayerTreeLayer *ll = new QgsLayerTreeLayer( layer );
insertChildNode( index, ll );

updateGroupLayers();
return ll;
}

@@ -86,6 +98,8 @@ QgsLayerTreeLayer *QgsLayerTreeGroup::addLayer( QgsMapLayer *layer )

QgsLayerTreeLayer *ll = new QgsLayerTreeLayer( layer );
addChildNode( ll );

updateGroupLayers();
return ll;
}

@@ -122,18 +136,24 @@ void QgsLayerTreeGroup::insertChildNodes( int index, const QList<QgsLayerTreeNod
}
updateChildVisibilityMutuallyExclusive();
}

updateGroupLayers();
}

void QgsLayerTreeGroup::addChildNode( QgsLayerTreeNode *node )
{
insertChildNode( -1, node );

updateGroupLayers();
}

void QgsLayerTreeGroup::removeChildNode( QgsLayerTreeNode *node )
{
int i = mChildren.indexOf( node );
if ( i >= 0 )
removeChildren( i, 1 );

updateGroupLayers();
}

void QgsLayerTreeGroup::removeLayer( QgsMapLayer *layer )
@@ -150,6 +170,8 @@ void QgsLayerTreeGroup::removeLayer( QgsMapLayer *layer )
}
}
}

updateGroupLayers();
}

void QgsLayerTreeGroup::removeChildren( int from, int count )
@@ -168,6 +190,8 @@ void QgsLayerTreeGroup::removeChildren( int from, int count )
//if ( mMutuallyExclusiveChildIndex == -1 )
// setItemVisibilityChecked( false );
}

updateGroupLayers();
}

void QgsLayerTreeGroup::removeChildrenGroupWithoutLayers()
@@ -185,6 +209,8 @@ void QgsLayerTreeGroup::removeChildrenGroupWithoutLayers()
treeGroup->removeChildrenGroupWithoutLayers();
}
}

updateGroupLayers();
}

void QgsLayerTreeGroup::removeAllChildren()
@@ -290,6 +316,8 @@ QgsLayerTreeGroup *QgsLayerTreeGroup::readXml( QDomElement &element, const QgsRe

groupNode->setIsMutuallyExclusive( isMutuallyExclusive, mutuallyExclusiveChildIndex );

groupNode->mGroupLayer = QgsMapLayerRef( element.attribute( QStringLiteral( "groupLayer" ) ) );

return groupNode;
}

@@ -306,13 +334,14 @@ void QgsLayerTreeGroup::writeXml( QDomElement &parentElement, const QgsReadWrite
QDomDocument doc = parentElement.ownerDocument();
QDomElement elem = doc.createElement( QStringLiteral( "layer-tree-group" ) );
elem.setAttribute( QStringLiteral( "name" ), mName );
elem.setAttribute( QStringLiteral( "expanded" ), mExpanded ? "1" : "0" );
elem.setAttribute( QStringLiteral( "expanded" ), mExpanded ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
elem.setAttribute( QStringLiteral( "checked" ), mChecked ? QStringLiteral( "Qt::Checked" ) : QStringLiteral( "Qt::Unchecked" ) );
if ( mMutuallyExclusive )
{
elem.setAttribute( QStringLiteral( "mutually-exclusive" ), QStringLiteral( "1" ) );
elem.setAttribute( QStringLiteral( "mutually-exclusive-child" ), mMutuallyExclusiveChildIndex );
}
elem.setAttribute( QStringLiteral( "groupLayer" ), mGroupLayer.layerId );

writeCommonXml( elem );

@@ -358,6 +387,8 @@ void QgsLayerTreeGroup::resolveReferences( const QgsProject *project, bool loose
{
for ( QgsLayerTreeNode *node : std::as_const( mChildren ) )
node->resolveReferences( project, looseMatching );

mGroupLayer.resolve( project );
}

static bool _nodeIsChecked( QgsLayerTreeNode *node )
@@ -399,6 +430,29 @@ void QgsLayerTreeGroup::setIsMutuallyExclusive( bool enabled, int initialChildIn
updateChildVisibilityMutuallyExclusive();
}

QgsGroupLayer *QgsLayerTreeGroup::groupLayer()
{
return qobject_cast< QgsGroupLayer * >( mGroupLayer.layer );
}

void QgsLayerTreeGroup::setGroupLayer( QgsGroupLayer *layer )
{
mGroupLayer.setLayer( layer );
}

QgsGroupLayer *QgsLayerTreeGroup::convertToGroupLayer( const QgsGroupLayer::LayerOptions &options )
{
if ( !mGroupLayer.layerId.isEmpty() )
return nullptr;

std::unique_ptr< QgsGroupLayer > res = std::make_unique< QgsGroupLayer >( name(), options );

mGroupLayer.setLayer( res.get() );
updateGroupLayers();

return res.release();
}

QStringList QgsLayerTreeGroup::findLayerIds() const
{
QStringList lst;
@@ -428,6 +482,8 @@ void QgsLayerTreeGroup::nodeVisibilityChanged( QgsLayerTreeNode *node )
// we need to make sure there is only one child node checked
updateChildVisibilityMutuallyExclusive();
}

updateGroupLayers();
}

void QgsLayerTreeGroup::updateChildVisibilityMutuallyExclusive()
@@ -445,6 +501,8 @@ void QgsLayerTreeGroup::updateChildVisibilityMutuallyExclusive()
}

mChangingChildVisibility = false;

updateGroupLayers();
}

void QgsLayerTreeGroup::makeOrphan()
@@ -454,6 +512,36 @@ void QgsLayerTreeGroup::makeOrphan()
connect( this, &QgsLayerTreeNode::visibilityChanged, this, &QgsLayerTreeGroup::nodeVisibilityChanged );
}

void QgsLayerTreeGroup::updateGroupLayers()
{
QgsGroupLayer *groupLayer = qobject_cast< QgsGroupLayer * >( mGroupLayer.get() );
if ( !groupLayer )
return;

QList< QgsMapLayer * > layers;

std::function< void( QgsLayerTreeGroup * ) > findGroupLayerChildren;
findGroupLayerChildren = [&layers, &findGroupLayerChildren]( QgsLayerTreeGroup * group )
{
for ( auto it = group->mChildren.crbegin(); it != group->mChildren.crend(); ++it )
{
if ( QgsLayerTreeLayer *layerTreeLayer = qobject_cast< QgsLayerTreeLayer * >( *it ) )
{
if ( layerTreeLayer->layer() && layerTreeLayer->isVisible() )
layers << layerTreeLayer->layer();
}
else if ( QgsLayerTreeGroup *childGroup = qobject_cast< QgsLayerTreeGroup * >( *it ) )
{
if ( childGroup->isVisible() )
findGroupLayerChildren( childGroup );
}
}
};
findGroupLayerChildren( this );

groupLayer->setChildLayers( layers );
}

void QgsLayerTreeGroup::setItemVisibilityCheckedRecursive( bool checked )
{
QgsLayerTreeNode::setItemVisibilityChecked( checked );
@@ -468,4 +556,6 @@ void QgsLayerTreeGroup::setItemVisibilityCheckedRecursive( bool checked )
}

mChangingChildVisibility = false;

updateGroupLayers();
}
@@ -19,16 +19,22 @@
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgslayertreenode.h"
#include "qgsmaplayerref.h"
#include "qgsgrouplayer.h"

class QgsMapLayer;
class QgsLayerTreeLayer;
class QgsGroupLayer;

/**
* \ingroup core
* \brief Layer tree group node serves as a container for layers and further groups.
*
* Group names do not need to be unique within one tree nor within one parent.
*
* While a layer tree group is typically used for hierarchical organisation of a QgsProject,
* they can optionally be associated with a QgsGroupLayer for map rendering purposes.
*
* \since QGIS 2.4
*/
class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode
@@ -213,6 +219,45 @@ class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode
*/
void setIsMutuallyExclusive( bool enabled, int initialChildIndex = -1 );

/**
* Returns a reference to the associated group layer, if the layer tree group will be treated
* as group layer during map rendering.
*
* \see setGroupLayer()
* \see convertToGroupLayer()
* \since QGIS 3.24
*/
QgsGroupLayer *groupLayer();

/**
* Sets the associated group \a layer, if the layer tree group will be treated
* as group layer during map rendering.
*
* This method does not take ownership of the group layer, and only a weak reference
* to the layer is stored.
*
* \see groupLayer()
* \see convertToGroupLayer()
* \since QGIS 3.24
*/
void setGroupLayer( QgsGroupLayer *layer );

/**
* Converts the group to a QgsGroupLayer.
*
* This method will convert the layer tree group to an equivalent QgsGroupLayer, and
* return the result. The caller takes ownership of the returned layer, and it is the
* caller's responsibility to add the layer to the associated QgsProject.
*
* If the group is already associated with a group layer (see groupLayer()), NULLPTR
* will be returned.
*
* \see groupLayer()
* \see setGroupLayer()
* \since QGIS 3.24
*/
QgsGroupLayer *convertToGroupLayer( const QgsGroupLayer::LayerOptions &options ) SIP_FACTORY;

protected slots:

void nodeVisibilityChanged( QgsLayerTreeNode *node );
@@ -252,7 +297,10 @@ class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode

QgsLayerTreeGroup &operator= ( const QgsLayerTreeGroup & ) = delete;

void init();
void updateGroupLayers();

QgsMapLayerRef mGroupLayer;
};


Loading

0 comments on commit 921541d

Please sign in to comment.