Visible status for embedded layers in embedded group

- For project : check visible state for embedded layers inside an unchecked group, instead of putting all layers in embedded-invisible-layers
- For theme : Add an 'checked-group-node' to save group visible state independently to layers in it.
Fixes #33097
This commit is contained in:
Peillet Sebastien 2019-12-05 19:54:27 +01:00 committed by Nyall Dawson
parent 1e9709cf5b
commit 35eea1c083
5 changed files with 184 additions and 4 deletions

View File

@ -125,6 +125,16 @@ Group identifiers are built using group names, a sub-group name is prepended by
and a forward slash, e.g. "level1/level2"
.. versionadded:: 3.2
%End
QSet<QString> checkedGroupNodes() const;
%Docstring
Returns a set of group identifiers for group nodes that should have checked state (other group nodes should be unchecked).
The returned value is valid only when hasCheckedStateInfo() returns ``True``.
Group identifiers are built using group names, a sub-group name is prepended by parent group's identifier
and a forward slash, e.g. "level1/level2"
.. versionadded:: 3.10.1
%End
void setExpandedGroupNodes( const QSet<QString> &expandedGroupNodes );
@ -132,6 +142,13 @@ and a forward slash, e.g. "level1/level2"
Sets a set of group identifiers for group nodes that should have expanded state. See expandedGroupNodes().
.. versionadded:: 3.2
%End
void setCheckedGroupNodes( const QSet<QString> &checkedGroupNodes );
%Docstring
Sets a set of group identifiers for group nodes that should have checked state. See checkedGroupNodes().
.. versionadded:: 3.10.1
%End
};

View File

@ -369,7 +369,10 @@ QStringList QgsLayerTreeUtils::invisibleLayerList( QgsLayerTreeNode *node )
const auto constChildren = QgsLayerTree::toGroup( node )->children();
for ( QgsLayerTreeNode *child : constChildren )
{
list << invisibleLayerList( child );
if ( child->itemVisibilityChecked() == Qt::Unchecked )
{
list << invisibleLayerList( child );
}
}
}
else if ( QgsLayerTree::isLayer( node ) )

View File

@ -87,11 +87,13 @@ void QgsMapThemeCollection::createThemeFromCurrentState( QgsLayerTreeGroup *pare
createThemeFromCurrentState( QgsLayerTree::toGroup( node ), model, rec );
if ( node->isExpanded() )
rec.mExpandedGroupNodes.insert( _groupId( node ) );
if ( node->itemVisibilityChecked() != Qt::Unchecked )
rec.mCheckedGroupNodes.insert( _groupId( node ) );
}
else if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
if ( nodeLayer->isVisible() )
if ( node->itemVisibilityChecked() != Qt::Unchecked )
rec.mLayerRecords << createThemeLayerRecord( nodeLayer, model );
}
}
@ -101,6 +103,7 @@ QgsMapThemeCollection::MapThemeRecord QgsMapThemeCollection::createThemeFromCurr
{
QgsMapThemeCollection::MapThemeRecord rec;
rec.setHasExpandedStateInfo( true ); // all newly created theme records have expanded state info
rec.setHasCheckedStateInfo( true ); // all newly created theme records have checked state info
createThemeFromCurrentState( root, model, rec );
return rec;
}
@ -182,6 +185,8 @@ void QgsMapThemeCollection::applyThemeToGroup( QgsLayerTreeGroup *parent, QgsLay
applyThemeToGroup( QgsLayerTree::toGroup( node ), model, rec );
if ( rec.hasExpandedStateInfo() )
node->setExpanded( rec.expandedGroupNodes().contains( _groupId( node ) ) );
if ( rec.hasCheckedStateInfo() )
node->setItemVisibilityChecked( rec.checkedGroupNodes().contains( _groupId( node ) ) );
}
else if ( QgsLayerTree::isLayer( node ) )
applyThemeToLayer( QgsLayerTree::toLayer( node ), model, rec );
@ -431,6 +436,10 @@ void QgsMapThemeCollection::readXml( const QDomDocument &doc )
if ( visPresetElem.hasAttribute( QStringLiteral( "has-expanded-info" ) ) )
expandedStateInfo = visPresetElem.attribute( QStringLiteral( "has-expanded-info" ) ).toInt();
bool checkedStateInfo = false;
if ( visPresetElem.hasAttribute( QStringLiteral( "has-checked-group-info" ) ) )
checkedStateInfo = visPresetElem.attribute( QStringLiteral( "has-checked-group-info" ) ).toInt();
QString presetName = visPresetElem.attribute( QStringLiteral( "name" ) );
QDomElement visPresetLayerElem = visPresetElem.firstChildElement( QStringLiteral( "layer" ) );
while ( !visPresetLayerElem.isNull() )
@ -510,10 +519,28 @@ void QgsMapThemeCollection::readXml( const QDomDocument &doc )
}
}
QSet<QString> checkedGroupNodes;
if ( checkedStateInfo )
{
// expanded state of legend nodes
QDomElement checkedGroupNodesElem = visPresetElem.firstChildElement( QStringLiteral( "checked-group-nodes" ) );
if ( !checkedGroupNodesElem.isNull() )
{
QDomElement checkedGroupNodeElem = checkedGroupNodesElem.firstChildElement( QStringLiteral( "checked-group-node" ) );
while ( !checkedGroupNodeElem.isNull() )
{
checkedGroupNodes << checkedGroupNodeElem.attribute( QStringLiteral( "id" ) );
checkedGroupNodeElem = checkedGroupNodeElem.nextSiblingElement( QStringLiteral( "checked-group-node" ) );
}
}
}
MapThemeRecord rec;
rec.setLayerRecords( layerRecords.values() );
rec.setHasExpandedStateInfo( expandedStateInfo );
rec.setExpandedGroupNodes( expandedGroupNodes );
rec.setHasCheckedStateInfo( checkedStateInfo );
rec.setCheckedGroupNodes( checkedGroupNodes );
mMapThemes.insert( presetName, rec );
emit mapThemeChanged( presetName );
@ -539,6 +566,8 @@ void QgsMapThemeCollection::writeXml( QDomDocument &doc )
visPresetElem.setAttribute( QStringLiteral( "name" ), grpName );
if ( rec.hasExpandedStateInfo() )
visPresetElem.setAttribute( QStringLiteral( "has-expanded-info" ), QStringLiteral( "1" ) );
if ( rec.hasCheckedStateInfo() )
visPresetElem.setAttribute( QStringLiteral( "has-checked-group-info" ), QStringLiteral( "1" ) );
for ( const MapThemeLayerRecord &layerRec : qgis::as_const( rec.mLayerRecords ) )
{
if ( !layerRec.layer() )
@ -579,6 +608,19 @@ void QgsMapThemeCollection::writeXml( QDomDocument &doc )
}
}
if ( rec.hasCheckedStateInfo() )
{
QDomElement checkedGroupElems = doc.createElement( QStringLiteral( "checked-group-nodes" ) );
const QSet<QString> checkedGroupNodes = rec.checkedGroupNodes();
for ( const QString &groupId : checkedGroupNodes )
{
QDomElement checkedGroupElem = doc.createElement( QStringLiteral( "checked-group-node" ) );
checkedGroupElem.setAttribute( QStringLiteral( "id" ), groupId );
checkedGroupElems.appendChild( checkedGroupElem );
}
visPresetElem.appendChild( checkedGroupElems );
}
if ( rec.hasExpandedStateInfo() )
{
QDomElement expandedGroupElems = doc.createElement( QStringLiteral( "expanded-group-nodes" ) );

View File

@ -118,7 +118,8 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
bool operator==( const QgsMapThemeCollection::MapThemeRecord &other ) const
{
return validLayerRecords() == other.validLayerRecords() &&
mHasExpandedStateInfo == other.mHasExpandedStateInfo && mExpandedGroupNodes == other.mExpandedGroupNodes;
mHasExpandedStateInfo == other.mHasExpandedStateInfo &&
mExpandedGroupNodes == other.mExpandedGroupNodes && mCheckedGroupNodes == other.mCheckedGroupNodes;
}
bool operator!=( const QgsMapThemeCollection::MapThemeRecord &other ) const
{
@ -139,7 +140,6 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
/**
* Returns set with only records for valid layers
* \note not available in Python bindings
*/
QHash<QgsMapLayer *, QgsMapThemeCollection::MapThemeLayerRecord> validLayerRecords() const SIP_SKIP;
@ -150,12 +150,27 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
*/
bool hasExpandedStateInfo() const { return mHasExpandedStateInfo; }
/**
* Returns whether information about checked/unchecked state of groups has been recorded
* and thus whether checkedGroupNodes() is valid.
* \note Not available in Python bindings
* \since QGIS 3.10.1
*/
bool hasCheckedStateInfo() const { return mHasCheckedStateInfo; } SIP_SKIP;
/**
* Sets whether the map theme contains valid expanded/collapsed state of nodes
* \since QGIS 3.2
*/
void setHasExpandedStateInfo( bool hasInfo ) { mHasExpandedStateInfo = hasInfo; }
/**
* Sets whether the map theme contains valid checked/unchecked state of group nodes
* \note Not available in Python bindings
* \since QGIS 3.10.1
*/
void setHasCheckedStateInfo( bool hasInfo ) { mHasCheckedStateInfo = hasInfo; } SIP_SKIP;
/**
* Returns a set of group identifiers for group nodes that should have expanded state (other group nodes should be collapsed).
* The returned value is valid only when hasExpandedStateInfo() returns TRUE.
@ -165,18 +180,35 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
*/
QSet<QString> expandedGroupNodes() const { return mExpandedGroupNodes; }
/**
* Returns a set of group identifiers for group nodes that should have checked state (other group nodes should be unchecked).
* The returned value is valid only when hasCheckedStateInfo() returns TRUE.
* Group identifiers are built using group names, a sub-group name is prepended by parent group's identifier
* and a forward slash, e.g. "level1/level2"
* \since QGIS 3.10.1
*/
QSet<QString> checkedGroupNodes() const { return mCheckedGroupNodes; }
/**
* Sets a set of group identifiers for group nodes that should have expanded state. See expandedGroupNodes().
* \since QGIS 3.2
*/
void setExpandedGroupNodes( const QSet<QString> &expandedGroupNodes ) { mExpandedGroupNodes = expandedGroupNodes; }
/**
* Sets a set of group identifiers for group nodes that should have checked state. See checkedGroupNodes().
* \since QGIS 3.10.1
*/
void setCheckedGroupNodes( const QSet<QString> &checkedGroupNodes ) { mCheckedGroupNodes = checkedGroupNodes; }
private:
//! Layer-specific records for the theme. Only visible layers are listed.
QList<MapThemeLayerRecord> mLayerRecords;
//! Whether the information about expanded/collapsed state of groups, layers and legend items has been stored
bool mHasExpandedStateInfo = false;
//! Whether the information about checked/unchecked state of groups, layers and legend items has been stored
bool mHasCheckedStateInfo = false;
/**
* Which groups should be expanded. Each group is identified by its name (sub-groups IDs are prepended with parent
@ -184,6 +216,12 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
*/
QSet<QString> mExpandedGroupNodes;
/**
* Which groups should be checked. Each group is identified by its name (sub-groups IDs are prepended with parent
* group and forward slash - e.g. "level1/level2/level3").
*/
QSet<QString> mCheckedGroupNodes;
friend class QgsMapThemeCollection;
};

View File

@ -34,6 +34,7 @@ class TestQgsMapThemeCollection : public QObject
void cleanupTestCase();// will be called after the last testfunction was executed.
void expandedState();
void checkedState();
private:
QgsVectorLayer *mPointsLayer = nullptr;
@ -206,5 +207,84 @@ void TestQgsMapThemeCollection::expandedState()
QCOMPARE( recExpandedPoints2.expandedLegendItems.count(), 4 );
}
void TestQgsMapThemeCollection::checkedState()
{
QgsMapThemeCollection themes( mProject );
QStringList pointLayerRootLegendNodes;
const QList<QgsLayerTreeModelLegendNode *> legendNodes = mLayerTreeModel->layerLegendNodes( mNodeLayerPoints, true );
for ( QgsLayerTreeModelLegendNode *legendNode : legendNodes )
{
QString key = legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
pointLayerRootLegendNodes << key;
}
QCOMPARE( pointLayerRootLegendNodes.count(), 4 );
// make two themes: 1. all checked, 2. all collapsed
// keep a layer checked inside group3 to check behavior
mNodeGroup1->setItemVisibilityChecked( false );
mNodeGroup2->setItemVisibilityChecked( false );
mNodeGroup3->setItemVisibilityChecked( false );
mNodeLayerLines->setItemVisibilityChecked( false );
mNodeLayerPolys->setItemVisibilityChecked( true );
themes.insert( "all-unchecked", QgsMapThemeCollection::createThemeFromCurrentState( mLayerTree, mLayerTreeModel ) );
mNodeGroup1->setItemVisibilityChecked( true );
mNodeGroup2->setItemVisibilityChecked( true );
mNodeGroup3->setItemVisibilityChecked( true );
// keep a layer checked inside a group to check behavior
mNodeLayerPolys->setItemVisibilityChecked( true );
themes.insert( "all-checked", QgsMapThemeCollection::createThemeFromCurrentState( mLayerTree, mLayerTreeModel ) );
// check theme data
QgsMapThemeCollection::MapThemeRecord recUnchecked = themes.mapThemeState( "all-unchecked" );
QVERIFY( recUnchecked.hasCheckedStateInfo() );
QCOMPARE( recUnchecked.checkedGroupNodes().count(), 0 );
QCOMPARE( mNodeLayerLines->itemVisibilityChecked(), false );
QCOMPARE( mNodeLayerPolys->itemVisibilityChecked(), true );
QgsMapThemeCollection::MapThemeRecord recChecked = themes.mapThemeState( "all-checked" );
QVERIFY( recChecked.hasCheckedStateInfo() );
QCOMPARE( recChecked.checkedGroupNodes().count(), 3 );
QCOMPARE( mNodeLayerLines->itemVisibilityChecked(), false );
QCOMPARE( mNodeLayerPolys->itemVisibilityChecked(), true );
// switch themes
themes.applyTheme( "all-unchecked", mLayerTree, mLayerTreeModel );
QCOMPARE( mNodeGroup1->itemVisibilityChecked(), false );
QCOMPARE( mNodeGroup3->itemVisibilityChecked(), false );
QCOMPARE( mNodeLayerLines->itemVisibilityChecked(), false );
QCOMPARE( mNodeLayerPolys->itemVisibilityChecked(), true );
themes.applyTheme( "all-checked", mLayerTree, mLayerTreeModel );
QCOMPARE( mNodeGroup1->itemVisibilityChecked(), true );
QCOMPARE( mNodeGroup3->itemVisibilityChecked(), true );
QCOMPARE( mNodeLayerLines->itemVisibilityChecked(), false );
QCOMPARE( mNodeLayerPolys->itemVisibilityChecked(), true );
// test read+write
QDomDocument doc;
QDomElement elemRoot = doc.createElement( "qgis" );
doc.appendChild( elemRoot );
themes.writeXml( doc );
QgsMapThemeCollection themes2( mProject );
themes2.readXml( doc );
QgsMapThemeCollection::MapThemeRecord recUnchecked2 = themes2.mapThemeState( "all-unchecked" );
QVERIFY( recUnchecked2.hasCheckedStateInfo() );
QCOMPARE( recUnchecked2.checkedGroupNodes().count(), 0 );
QgsMapThemeCollection::MapThemeRecord recChecked2 = themes2.mapThemeState( "all-checked" );
QVERIFY( recChecked2.hasCheckedStateInfo() );
QCOMPARE( recChecked2.checkedGroupNodes().count(), 3 );
}
QGSTEST_MAIN( TestQgsMapThemeCollection )
#include "testqgsmapthemecollection.moc"