Skip to content
Permalink
Browse files

[FEATURE] Map themes: store also expanded/collapsed state of nodes

Each map theme will also record which layers, groups and legend items are expanded,
so when a map theme is selected, the expanded/collapsed states get applied in the layer tree.
  • Loading branch information
wonder-sk committed Apr 11, 2018
1 parent 29b080f commit 3400199eb850828981bcb1b84f313b8629502df5
@@ -59,6 +59,10 @@ Set the map layer for this record
QString currentStyle;
bool usingLegendItems;
QSet<QString> checkedLegendItems;

QSet<QString> expandedLegendItems;

bool expandedLayerNode;
};

class MapThemeRecord
@@ -98,6 +102,38 @@ Add a new record for a layer.
%End


bool hasExpandedStateInfo() const;
%Docstring
Returns whether information about expanded/collapsed state of nodes has been recorded
and thus whether expandedGroupNodes() and expandedLegendItems + expandedLayerNode from layer records are valid.

.. versionadded:: 3.2
%End

void setHasExpandedStateInfo( bool hasInfo );
%Docstring
Sets whether the map theme contains valid expanded/collapsed state of nodes

.. versionadded:: 3.2
%End

QSet<QString> expandedGroupNodes() const;
%Docstring
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.
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.2
%End

void setExpandedGroupNodes( const QSet<QString> &expandedGroupNodes );
%Docstring
Sets a set of group identifiers for group nodes that should have expanded state. See expandedGroupNodes().

.. versionadded:: 3.2
%End

};

QgsMapThemeCollection( QgsProject *project = 0 );
@@ -37,6 +37,8 @@ QgsMapThemeCollection::MapThemeLayerRecord QgsMapThemeCollection::createThemeLay
MapThemeLayerRecord layerRec( nodeLayer->layer() );
layerRec.usingCurrentStyle = true;
layerRec.currentStyle = nodeLayer->layer()->styleManager()->currentStyle();
layerRec.expandedLayerNode = nodeLayer->isExpanded();
layerRec.expandedLegendItems = nodeLayer->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList().toSet();

// get checked legend items
bool hasCheckableItems = false;
@@ -63,12 +65,27 @@ QgsMapThemeCollection::MapThemeLayerRecord QgsMapThemeCollection::createThemeLay
return layerRec;
}

static QString _groupId( QgsLayerTreeNode *node )
{
QStringList lst;
while ( node->parent() )
{
lst.prepend( node->name() );
node = node->parent();
}
return lst.join( '/' );
}

void QgsMapThemeCollection::createThemeFromCurrentState( QgsLayerTreeGroup *parent, QgsLayerTreeModel *model, QgsMapThemeCollection::MapThemeRecord &rec )
{
Q_FOREACH ( QgsLayerTreeNode *node, parent->children() )
{
if ( QgsLayerTree::isGroup( node ) )
{
createThemeFromCurrentState( QgsLayerTree::toGroup( node ), model, rec );
if ( node->isExpanded() )
rec.mExpandedGroupNodes.insert( _groupId( node ) );
}
else if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
@@ -81,6 +98,7 @@ void QgsMapThemeCollection::createThemeFromCurrentState( QgsLayerTreeGroup *pare
QgsMapThemeCollection::MapThemeRecord QgsMapThemeCollection::createThemeFromCurrentState( QgsLayerTreeGroup *root, QgsLayerTreeModel *model )
{
QgsMapThemeCollection::MapThemeRecord rec;
rec.setHasExpandedStateInfo( true ); // all newly created theme records have expanded state info
createThemeFromCurrentState( root, model, rec );
return rec;
}
@@ -140,6 +158,13 @@ void QgsMapThemeCollection::applyThemeToLayer( QgsLayerTreeLayer *nodeLayer, Qgs
legendNode->setData( Qt::Checked, Qt::CheckStateRole );
}
}

// apply expanded/collapsed state to the layer and its legend nodes
if ( rec.hasExpandedStateInfo() )
{
nodeLayer->setExpanded( layerRec.expandedLayerNode );
nodeLayer->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), QStringList( layerRec.expandedLegendItems.toList() ) );
}
}


@@ -148,7 +173,11 @@ void QgsMapThemeCollection::applyThemeToGroup( QgsLayerTreeGroup *parent, QgsLay
Q_FOREACH ( QgsLayerTreeNode *node, parent->children() )
{
if ( QgsLayerTree::isGroup( node ) )
{
applyThemeToGroup( QgsLayerTree::toGroup( node ), model, rec );
if ( rec.hasExpandedStateInfo() )
node->setExpanded( rec.expandedGroupNodes().contains( _groupId( node ) ) );
}
else if ( QgsLayerTree::isLayer( node ) )
applyThemeToLayer( QgsLayerTree::toLayer( node ), model, rec );
}
@@ -385,6 +414,10 @@ void QgsMapThemeCollection::readXml( const QDomDocument &doc )
{
QHash<QString, MapThemeLayerRecord> layerRecords; // key = layer ID

bool expandedStateInfo = false;
if ( visPresetElem.hasAttribute( QStringLiteral( "has-expanded-info" ) ) )
expandedStateInfo = visPresetElem.attribute( QStringLiteral( "has-expanded-info" ) ).toInt();

QString presetName = visPresetElem.attribute( QStringLiteral( "name" ) );
QDomElement visPresetLayerElem = visPresetElem.firstChildElement( QStringLiteral( "layer" ) );
while ( !visPresetLayerElem.isNull() )
@@ -399,6 +432,9 @@ void QgsMapThemeCollection::readXml( const QDomDocument &doc )
layerRecords[layerID].usingCurrentStyle = true;
layerRecords[layerID].currentStyle = visPresetLayerElem.attribute( QStringLiteral( "style" ) );
}

if ( visPresetLayerElem.hasAttribute( QStringLiteral( "expanded" ) ) )
layerRecords[layerID].expandedLayerNode = visPresetLayerElem.attribute( QStringLiteral( "expanded" ) ).toInt();
}
visPresetLayerElem = visPresetLayerElem.nextSiblingElement( QStringLiteral( "layer" ) );
}
@@ -424,8 +460,47 @@ void QgsMapThemeCollection::readXml( const QDomDocument &doc )
checkedLegendNodesElem = checkedLegendNodesElem.nextSiblingElement( QStringLiteral( "checked-legend-nodes" ) );
}

QSet<QString> expandedGroupNodes;
if ( expandedStateInfo )
{
// expanded state of legend nodes
QDomElement expandedLegendNodesElem = visPresetElem.firstChildElement( QStringLiteral( "expanded-legend-nodes" ) );
while ( !expandedLegendNodesElem.isNull() )
{
QSet<QString> expandedLegendNodes;

QDomElement expandedLegendNodeElem = expandedLegendNodesElem.firstChildElement( QStringLiteral( "expanded-legend-node" ) );
while ( !expandedLegendNodeElem.isNull() )
{
expandedLegendNodes << expandedLegendNodeElem.attribute( QStringLiteral( "id" ) );
expandedLegendNodeElem = expandedLegendNodeElem.nextSiblingElement( QStringLiteral( "expanded-legend-node" ) );
}

QString layerID = expandedLegendNodesElem.attribute( QStringLiteral( "id" ) );
if ( mProject->mapLayer( layerID ) ) // only use valid IDs
{
layerRecords[layerID].expandedLegendItems = expandedLegendNodes;
}
expandedLegendNodesElem = expandedLegendNodesElem.nextSiblingElement( QStringLiteral( "expanded-legend-nodes" ) );
}

// expanded state of group nodes
QDomElement expandedGroupNodesElem = visPresetElem.firstChildElement( QStringLiteral( "expanded-group-nodes" ) );
if ( !expandedGroupNodesElem.isNull() )
{
QDomElement expandedGroupNodeElem = expandedGroupNodesElem.firstChildElement( QStringLiteral( "expanded-group-node" ) );
while ( !expandedGroupNodeElem.isNull() )
{
expandedGroupNodes << expandedGroupNodeElem.attribute( QStringLiteral( "id" ) );
expandedGroupNodeElem = expandedGroupNodeElem.nextSiblingElement( QStringLiteral( "expanded-group-node" ) );
}
}
}

MapThemeRecord rec;
rec.setLayerRecords( layerRecords.values() );
rec.setHasExpandedStateInfo( expandedStateInfo );
rec.setExpandedGroupNodes( expandedGroupNodes );
mMapThemes.insert( presetName, rec );
emit mapThemeChanged( presetName );

@@ -446,6 +521,8 @@ void QgsMapThemeCollection::writeXml( QDomDocument &doc )
const MapThemeRecord &rec = it.value();
QDomElement visPresetElem = doc.createElement( QStringLiteral( "visibility-preset" ) );
visPresetElem.setAttribute( QStringLiteral( "name" ), grpName );
if ( rec.hasExpandedStateInfo() )
visPresetElem.setAttribute( QStringLiteral( "has-expanded-info" ), QStringLiteral( "1" ) );
Q_FOREACH ( const MapThemeLayerRecord &layerRec, rec.mLayerRecords )
{
if ( !layerRec.layer() )
@@ -469,6 +546,34 @@ void QgsMapThemeCollection::writeXml( QDomDocument &doc )
}
visPresetElem.appendChild( checkedLegendNodesElem );
}

if ( rec.hasExpandedStateInfo() )
{
layerElem.setAttribute( QStringLiteral( "expanded" ), layerRec.expandedLayerNode ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );

QDomElement expandedLegendNodesElem = doc.createElement( QStringLiteral( "expanded-legend-nodes" ) );
expandedLegendNodesElem.setAttribute( QStringLiteral( "id" ), layerID );
for ( const QString &expandedLegendNode : qgis::as_const( layerRec.expandedLegendItems ) )
{
QDomElement expandedLegendNodeElem = doc.createElement( QStringLiteral( "expanded-legend-node" ) );
expandedLegendNodeElem.setAttribute( QStringLiteral( "id" ), expandedLegendNode );
expandedLegendNodesElem.appendChild( expandedLegendNodeElem );
}
visPresetElem.appendChild( expandedLegendNodesElem );
}
}

if ( rec.hasExpandedStateInfo() )
{
QDomElement expandedGroupElems = doc.createElement( QStringLiteral( "expanded-group-nodes" ) );
const QSet<QString> expandedGroupNodes = rec.expandedGroupNodes();
for ( const QString &groupId : expandedGroupNodes )
{
QDomElement expandedGroupElem = doc.createElement( QStringLiteral( "expanded-group-node" ) );
expandedGroupElem.setAttribute( QStringLiteral( "id" ), groupId );
expandedGroupElems.appendChild( expandedGroupElem );
}
visPresetElem.appendChild( expandedGroupElems );
}

visPresetsElem.appendChild( visPresetElem );
@@ -65,7 +65,8 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
{
return mLayer == other.mLayer &&
usingCurrentStyle == other.usingCurrentStyle && currentStyle == other.currentStyle &&
usingLegendItems == other.usingLegendItems && checkedLegendItems == other.checkedLegendItems;
usingLegendItems == other.usingLegendItems && checkedLegendItems == other.checkedLegendItems &&
expandedLegendItems == other.expandedLegendItems && expandedLayerNode == other.expandedLayerNode;
}
bool operator!=( const QgsMapThemeCollection::MapThemeLayerRecord &other ) const
{
@@ -86,6 +87,19 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
bool usingLegendItems = false;
//! Rule keys of check legend items in layer tree model
QSet<QString> checkedLegendItems;

/**
* Rule keys of expanded legend items in layer tree view.
* \since QGIS 3.2
*/
QSet<QString> expandedLegendItems;

/**
* Whether the layer's tree node is expanded
* (only to be applied if the parent MapThemeRecord has the information about expanded nodes stored)
* \since QGIS 3.2
*/
bool expandedLayerNode = false;
private:
//! Weak pointer to the layer
QgsWeakMapLayerPointer mLayer;
@@ -103,7 +117,8 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject

bool operator==( const QgsMapThemeCollection::MapThemeRecord &other ) const
{
return validLayerRecords() == other.validLayerRecords();
return validLayerRecords() == other.validLayerRecords() &&
mHasExpandedStateInfo == other.mHasExpandedStateInfo && mExpandedGroupNodes == other.mExpandedGroupNodes;
}
bool operator!=( const QgsMapThemeCollection::MapThemeRecord &other ) const
{
@@ -128,10 +143,47 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
*/
QHash<QgsMapLayer *, QgsMapThemeCollection::MapThemeLayerRecord> validLayerRecords() const SIP_SKIP;

/**
* Returns whether information about expanded/collapsed state of nodes has been recorded
* and thus whether expandedGroupNodes() and expandedLegendItems + expandedLayerNode from layer records are valid.
* \since QGIS 3.2
*/
bool hasExpandedStateInfo() const { return mHasExpandedStateInfo; }

/**
* Sets whether the map theme contains valid expanded/collapsed state of nodes
* \since QGIS 3.2
*/
void setHasExpandedStateInfo( bool hasInfo ) { mHasExpandedStateInfo = hasInfo; }

/**
* 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.
* 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.2
*/
QSet<QString> expandedGroupNodes() const { return mExpandedGroupNodes; }

/**
* 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; }

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;

/**
* Which groups should be expanded. 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> mExpandedGroupNodes;

friend class QgsMapThemeCollection;
};

@@ -69,6 +69,7 @@ void QgsLayerTreeView::setModel( QAbstractItemModel *model )
QTreeView::setModel( model );

connect( layerTreeModel()->rootGroup(), &QgsLayerTreeNode::expandedChanged, this, &QgsLayerTreeView::onExpandedChanged );
connect( layerTreeModel()->rootGroup(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayerTreeView::onCustomPropertyChanged );

connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayerTreeView::onCurrentChanged );

@@ -244,6 +245,22 @@ void QgsLayerTreeView::onExpandedChanged( QgsLayerTreeNode *node, bool expanded
setExpanded( idx, expanded );
}

void QgsLayerTreeView::onCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key )
{
if ( key != QStringLiteral( "expandedLegendNodes" ) || !QgsLayerTree::isLayer( node ) )
return;

QSet<QString> expandedLegendNodes = node->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList().toSet();

const QList<QgsLayerTreeModelLegendNode *> legendNodes = layerTreeModel()->layerLegendNodes( QgsLayerTree::toLayer( node ), true );
for ( QgsLayerTreeModelLegendNode *legendNode : legendNodes )
{
QString key = legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
if ( !key.isEmpty() )
setExpanded( layerTreeModel()->legendNode2index( legendNode ), expandedLegendNodes.contains( key ) );
}
}

void QgsLayerTreeView::onModelReset()
{
updateExpandedStateFromNode( layerTreeModel()->rootGroup() );
@@ -192,6 +192,9 @@ class GUI_EXPORT QgsLayerTreeView : public QTreeView
void onExpandedChanged( QgsLayerTreeNode *node, bool expanded );
void onModelReset();

private slots:
void onCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key );

protected:
//! helper class with default actions. Lazily initialized.
QgsLayerTreeViewDefaultActions *mDefaultActions = nullptr;
@@ -141,6 +141,7 @@ SET(TESTS
testqgsmaprotation.cpp
testqgsmapsettings.cpp
testqgsmapsettingsutils.cpp
testqgsmapthemecollection.cpp
testqgsmaptopixelgeometrysimplifier.cpp
testqgsmaptopixel.cpp
testqgsmarkerlinesymbol.cpp

0 comments on commit 3400199

Please sign in to comment.
You can’t perform that action at this time.