mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-28 00:17:30 -05:00
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:
parent
1e9709cf5b
commit
35eea1c083
@ -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
|
||||
|
||||
};
|
||||
|
@ -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 ) )
|
||||
|
@ -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" ) );
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user