Skip to content

Commit

Permalink
Visibility groups: also record visibility states of renderer categori…
Browse files Browse the repository at this point in the history
…es (legend nodes) into groups

Visibility of a layer is not stored within the map layer, but visibility
of renderer categories (which turn into legend nodes) is stored inside renderer and
that one in turn inside map layer. This fact may result in surprising behavior
when change of visibility group for a composer map will affect other composer maps (and main canvas).
The only way to overcome the problem would be to have the possibility to use multiple renderers with one layer.
  • Loading branch information
wonder-sk committed Sep 5, 2014
1 parent 4baaad6 commit 4cac2f7
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 65 deletions.
9 changes: 9 additions & 0 deletions python/core/layertree/qgslayertreemodellegendnode.sip
Expand Up @@ -17,6 +17,11 @@ class QgsLayerTreeModelLegendNode : QObject
public:
~QgsLayerTreeModelLegendNode();

enum LegendNodeRoles
{
RuleKeyRole
};

/** Return pointer to the parent layer node */
QgsLayerTreeLayer* parent() const;

Expand Down Expand Up @@ -78,6 +83,10 @@ class QgsLayerTreeModelLegendNode : QObject
*/
virtual QSizeF drawSymbolText( const QgsLegendSettings& settings, ItemContext* ctx, const QSizeF& symbolSize ) const;

signals:
//! Emitted on internal data change so the layer tree model can forward the signal to views
void dataChanged();

protected:
/** Construct the node with pointer to its parent layer node */
explicit QgsLayerTreeModelLegendNode( QgsLayerTreeLayer* nodeL, QObject* parent /TransferThis/ = 0 );
Expand Down
5 changes: 5 additions & 0 deletions src/app/composer/qgscomposermapwidget.cpp
Expand Up @@ -286,6 +286,11 @@ void QgsComposerMapWidget::visibilityGroupSelected()
{
mKeepLayerListCheckBox->setChecked( true );
mComposerMap->setLayerSet( lst );

// also apply legend node check states
foreach ( QString layerID, lst )
QgsVisibilityGroups::instance()->applyGroupCheckedLegendNodesToLayer( action->text(), layerID );

mComposerMap->cache();
mComposerMap->update();
}
Expand Down
175 changes: 117 additions & 58 deletions src/app/qgsvisibilitygroups.cpp
Expand Up @@ -17,7 +17,13 @@

#include "qgslayertree.h"
#include "qgslayertreemapcanvasbridge.h"
#include "qgslayertreemodel.h"
#include "qgslayertreemodellegendnode.h"
#include "qgslayertreeview.h"
#include "qgsmaplayerregistry.h"
#include "qgsproject.h"
#include "qgsrendererv2.h"
#include "qgsvectorlayer.h"
#include "qgisapp.h"

#include <QInputDialog>
Expand All @@ -28,7 +34,6 @@ QgsVisibilityGroups* QgsVisibilityGroups::sInstance;

QgsVisibilityGroups::QgsVisibilityGroups()
: mMenu( new QMenu )
, mMenuDirty( false )
{

mMenu->addAction( QgisApp::instance()->actionShowAllLayers() );
Expand All @@ -42,14 +47,6 @@ QgsVisibilityGroups::QgsVisibilityGroups()

connect( mMenu, SIGNAL( aboutToShow() ), this, SLOT( menuAboutToShow() ) );

QgsLayerTreeGroup* root = QgsProject::instance()->layerTreeRoot();
connect( root, SIGNAL( visibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ),
this, SLOT( layerTreeVisibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ) );
connect( root, SIGNAL( addedChildren( QgsLayerTreeNode*, int, int ) ),
this, SLOT( layerTreeAddedChildren( QgsLayerTreeNode*, int, int ) ) );
connect( root, SIGNAL( willRemoveChildren( QgsLayerTreeNode*, int, int ) ),
this, SLOT( layerTreeWillRemoveChildren( QgsLayerTreeNode*, int, int ) ) );

connect( QgsProject::instance(), SIGNAL( readProject( const QDomDocument & ) ),
this, SLOT( readProject( const QDomDocument & ) ) );
connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ),
Expand All @@ -66,7 +63,29 @@ void QgsVisibilityGroups::addVisibleLayersToGroup( QgsLayerTreeGroup* parent, Qg
{
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
if ( nodeLayer->isVisible() )
{
rec.mVisibleLayerIDs << nodeLayer->layerId();

QgsLayerTreeModel* model = QgisApp::instance()->layerTreeView()->layerTreeModel();
bool hasCheckableItems = false;
bool someItemsUnchecked = false;
QSet<QString> checkedItems;
foreach ( QgsLayerTreeModelLegendNode* legendNode, model->layerLegendNodes( nodeLayer ) )
{
if ( legendNode->flags() & Qt::ItemIsUserCheckable )
{
hasCheckableItems = true;

if ( legendNode->data( Qt::CheckStateRole ).toInt() == Qt::Checked )
checkedItems << legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
else
someItemsUnchecked = true;
}
}

if ( hasCheckableItems && someItemsUnchecked )
rec.mPerLayerCheckedLegendSymbols.insert( nodeLayer->layerId(), checkedItems );
}
}
}
}
Expand All @@ -91,8 +110,6 @@ QgsVisibilityGroups* QgsVisibilityGroups::instance()
void QgsVisibilityGroups::addGroup( const QString& name )
{
mGroups.insert( name, currentState() );

mMenuDirty = true;
}

void QgsVisibilityGroups::updateGroup( const QString& name )
Expand All @@ -101,22 +118,16 @@ void QgsVisibilityGroups::updateGroup( const QString& name )
return;

mGroups[name] = currentState();

mMenuDirty = true;
}

void QgsVisibilityGroups::removeGroup( const QString& name )
{
mGroups.remove( name );

mMenuDirty = true;
}

void QgsVisibilityGroups::clear()
{
mGroups.clear();

mMenuDirty = true;
}

QStringList QgsVisibilityGroups::groups() const
Expand All @@ -141,6 +152,31 @@ QStringList QgsVisibilityGroups::groupVisibleLayers( const QString& name ) const
return order2;
}


void QgsVisibilityGroups::applyGroupCheckedLegendNodesToLayer( const QString& name, const QString& layerID )
{
if ( !mGroups.contains( name ) )
return;

QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) );
if ( !vlayer || !vlayer->rendererV2() )
return;

if ( !vlayer->rendererV2()->legendSymbolItemsCheckable() )
return; // no need to do anything

const GroupRecord& rec = mGroups[name];
bool someNodesUnchecked = rec.mPerLayerCheckedLegendSymbols.contains( layerID );

foreach ( const QgsLegendSymbolItemV2& item, vlayer->rendererV2()->legendSymbolItemsV2() )
{
bool checked = vlayer->rendererV2()->legendSymbolItemChecked( item.ruleKey() );
bool shouldBeChecked = someNodesUnchecked ? rec.mPerLayerCheckedLegendSymbols[layerID].contains( item.ruleKey() ) : true;
if ( checked != shouldBeChecked )
vlayer->rendererV2()->checkLegendSymbolItem( item.ruleKey(), shouldBeChecked );
}
}

QMenu* QgsVisibilityGroups::menu()
{
return mMenu;
Expand Down Expand Up @@ -168,16 +204,44 @@ void QgsVisibilityGroups::groupTriggerred()
}


void QgsVisibilityGroups::applyStateToLayerTreeGroup( QgsLayerTreeGroup* parent, const QSet<QString>& visibleLayerIDs )
void QgsVisibilityGroups::applyStateToLayerTreeGroup( QgsLayerTreeGroup* parent, const GroupRecord& rec )
{
foreach ( QgsLayerTreeNode* node, parent->children() )
{
if ( QgsLayerTree::isGroup( node ) )
applyStateToLayerTreeGroup( QgsLayerTree::toGroup( node ), visibleLayerIDs );
applyStateToLayerTreeGroup( QgsLayerTree::toGroup( node ), rec );
else if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
nodeLayer->setVisible( visibleLayerIDs.contains( nodeLayer->layerId() ) ? Qt::Checked : Qt::Unchecked );
bool isVisible = rec.mVisibleLayerIDs.contains( nodeLayer->layerId() );
nodeLayer->setVisible( isVisible ? Qt::Checked : Qt::Unchecked );

if ( isVisible )
{
QgsLayerTreeModel* model = QgisApp::instance()->layerTreeView()->layerTreeModel();
if ( rec.mPerLayerCheckedLegendSymbols.contains( nodeLayer->layerId() ) )
{
const QSet<QString>& checkedNodes = rec.mPerLayerCheckedLegendSymbols[nodeLayer->layerId()];
// some nodes are not checked
foreach ( QgsLayerTreeModelLegendNode* legendNode, model->layerLegendNodes( nodeLayer ) )
{
Qt::CheckState shouldHaveState = checkedNodes.contains( legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() ) ? Qt::Checked : Qt::Unchecked;
if ( ( legendNode->flags() & Qt::ItemIsUserCheckable ) &&
legendNode->data( Qt::CheckStateRole ).toInt() != shouldHaveState )
legendNode->setData( shouldHaveState, Qt::CheckStateRole );
}
}
else
{
// all nodes should be checked
foreach ( QgsLayerTreeModelLegendNode* legendNode, model->layerLegendNodes( nodeLayer ) )
{
if ( ( legendNode->flags() & Qt::ItemIsUserCheckable ) &&
legendNode->data( Qt::CheckStateRole ).toInt() != Qt::Checked )
legendNode->setData( Qt::Checked, Qt::CheckStateRole );
}
}
}
}
}
}
Expand All @@ -188,10 +252,7 @@ void QgsVisibilityGroups::applyState( const QString& groupName )
if ( !mGroups.contains( groupName ) )
return;

const GroupRecord& rec = mGroups[groupName];
applyStateToLayerTreeGroup( QgsProject::instance()->layerTreeRoot(), rec.mVisibleLayerIDs );

mMenuDirty = true;
applyStateToLayerTreeGroup( QgsProject::instance()->layerTreeRoot(), mGroups[groupName] );
}


Expand All @@ -210,11 +271,6 @@ void QgsVisibilityGroups::removeCurrentGroup()

void QgsVisibilityGroups::menuAboutToShow()
{
if ( !mMenuDirty )
return;

// lazy update of the menu only when necessary - so that we do not do too much work when it is not necessary

qDeleteAll( mMenuGroupActions );
mMenuGroupActions.clear();

Expand All @@ -236,36 +292,8 @@ void QgsVisibilityGroups::menuAboutToShow()
mMenu->insertActions( mMenuSeparator, mMenuGroupActions );

mActionRemoveCurrentGroup->setEnabled( hasCurrent );

mMenuDirty = false;
}


void QgsVisibilityGroups::layerTreeVisibilityChanged( QgsLayerTreeNode* node, Qt::CheckState state )
{
Q_UNUSED( node );
Q_UNUSED( state );

mMenuDirty = true;
}

void QgsVisibilityGroups::layerTreeAddedChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
{
Q_UNUSED( node );
Q_UNUSED( indexFrom );
Q_UNUSED( indexTo );

mMenuDirty = true;
}

void QgsVisibilityGroups::layerTreeWillRemoveChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
{
Q_UNUSED( node );
Q_UNUSED( indexFrom );
Q_UNUSED( indexTo );

mMenuDirty = true;
}

void QgsVisibilityGroups::readProject( const QDomDocument& doc )
{
Expand All @@ -286,6 +314,23 @@ void QgsVisibilityGroups::readProject( const QDomDocument& doc )
rec.mVisibleLayerIDs << visGroupLayerElem.attribute( "id" );
visGroupLayerElem = visGroupLayerElem.nextSiblingElement( "layer" );
}

QDomElement checkedLegendNodesElem = visGroupElem.firstChildElement( "checked-legend-nodes" );
while ( !checkedLegendNodesElem.isNull() )
{
QSet<QString> checkedLegendNodes;

QDomElement checkedLegendNodeElem = checkedLegendNodesElem.firstChildElement( "checked-legend-node" );
while ( !checkedLegendNodeElem.isNull() )
{
checkedLegendNodes << checkedLegendNodeElem.attribute( "id" );
checkedLegendNodeElem = checkedLegendNodeElem.nextSiblingElement( "checked-legend-node" );
}

rec.mPerLayerCheckedLegendSymbols.insert( checkedLegendNodesElem.attribute( "id" ), checkedLegendNodes );
checkedLegendNodesElem = checkedLegendNodesElem.nextSiblingElement( "checked-legend-nodes" );
}

mGroups.insert( groupName, rec );

visGroupElem = visGroupElem.nextSiblingElement( "visibility-group" );
Expand All @@ -297,15 +342,29 @@ void QgsVisibilityGroups::writeProject( QDomDocument& doc )
QDomElement visGroupsElem = doc.createElement( "visibility-groups" );
foreach ( const QString& grpName, mGroups.keys() )
{
const GroupRecord& rec = mGroups[grpName];
QDomElement visGroupElem = doc.createElement( "visibility-group" );
visGroupElem.setAttribute( "name", grpName );
foreach ( QString layerID, mGroups[grpName].mVisibleLayerIDs )
foreach ( QString layerID, rec.mVisibleLayerIDs )
{
QDomElement layerElem = doc.createElement( "layer" );
layerElem.setAttribute( "id", layerID );
visGroupElem.appendChild( layerElem );
}

foreach ( QString layerID, rec.mPerLayerCheckedLegendSymbols.keys() )
{
QDomElement checkedLegendNodesElem = doc.createElement( "checked-legend-nodes" );
checkedLegendNodesElem.setAttribute( "id", layerID );
foreach ( QString checkedLegendNode, rec.mPerLayerCheckedLegendSymbols[layerID] )
{
QDomElement checkedLegendNodeElem = doc.createElement( "checked-legend-node" );
checkedLegendNodeElem.setAttribute( "id", checkedLegendNode );
checkedLegendNodesElem.appendChild( checkedLegendNodeElem );
}
visGroupElem.appendChild( checkedLegendNodesElem );
}

visGroupsElem.appendChild( visGroupElem );
}

Expand Down
13 changes: 6 additions & 7 deletions src/app/qgsvisibilitygroups.h
Expand Up @@ -54,6 +54,9 @@ class QgsVisibilityGroups : public QObject
//! Return list of layer IDs that should be visible for particular group
QStringList groupVisibleLayers( const QString& name ) const;

//! Apply check states of legend nodes of a given layer as defined in the group
void applyGroupCheckedLegendNodesToLayer( const QString& name, const QString& layerID );

//! Convenience menu that lists available groups and actions for management
QMenu* menu();

Expand All @@ -65,9 +68,6 @@ class QgsVisibilityGroups : public QObject
void groupTriggerred();
void removeCurrentGroup();
void menuAboutToShow();
void layerTreeVisibilityChanged( QgsLayerTreeNode* node, Qt::CheckState state );
void layerTreeAddedChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo );
void layerTreeWillRemoveChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo );

void readProject( const QDomDocument& doc );
void writeProject( QDomDocument& doc );
Expand All @@ -79,19 +79,19 @@ class QgsVisibilityGroups : public QObject
{
bool operator==( const GroupRecord& other ) const
{
return mVisibleLayerIDs == other.mVisibleLayerIDs;
return mVisibleLayerIDs == other.mVisibleLayerIDs && mPerLayerCheckedLegendSymbols == other.mPerLayerCheckedLegendSymbols;
}

//! List of layers that are visible
QSet<QString> mVisibleLayerIDs;
//! For layers that have checkable legend symbols and not all symbols are checked - list which ones are
//QMap<QString, QStringList> mPerLayerCheckedLegendSymbols;
QMap<QString, QSet<QString> > mPerLayerCheckedLegendSymbols;
} GroupRecord;

typedef QMap<QString, GroupRecord> GroupRecordMap;

void addVisibleLayersToGroup( QgsLayerTreeGroup* parent, GroupRecord& rec );
void applyStateToLayerTreeGroup( QgsLayerTreeGroup* parent, const QSet<QString>& visibleLayerIDs );
void applyStateToLayerTreeGroup( QgsLayerTreeGroup* parent, const GroupRecord& rec );

GroupRecord currentState();
void applyState( const QString& groupName );
Expand All @@ -101,7 +101,6 @@ class QgsVisibilityGroups : public QObject
GroupRecordMap mGroups;

QMenu* mMenu;
bool mMenuDirty;
QAction* mMenuSeparator;
QAction* mActionRemoveCurrentGroup;
QList<QAction*> mMenuGroupActions;
Expand Down

0 comments on commit 4cac2f7

Please sign in to comment.