[FEATURE] Show rule-based renderer's legend in a tree hierarchy

The functionality can be used in other renderers too, the only requirement
is that legend nodes need to specify their parent rule key via data().

Note that ayer tree model must have ShowLegendAsTree flag enabled, otherwise
the model will keep showing lists instead of trees.
This commit is contained in:
Martin Dobias 2015-01-22 12:23:05 +07:00
parent 012c3478f3
commit 3d539c8cd3
11 changed files with 174 additions and 26 deletions

View File

@ -53,6 +53,7 @@ class QgsLayerTreeModel : QAbstractItemModel
// display flags
ShowLegend, //!< Add legend nodes for layer nodes
ShowSymbology, //!< deprecated - use ShowLegend
ShowLegendAsTree, //!< For legends that support it, will show them in a tree instead of a list (needs also ShowLegend). Added in 2.8
// behavioral flags
AllowNodeReorder, //!< Allow reordering with drag'n'drop

View File

@ -20,7 +20,8 @@ class QgsLayerTreeModelLegendNode : QObject
enum LegendNodeRoles
{
RuleKeyRole, //!< rule key of the node (QString)
SymbolV2LegacyRuleKeyRole //!< for QgsSymbolV2LegendNode only - legacy rule key (void ptr, to be cast to QgsSymbolV2 ptr)
SymbolV2LegacyRuleKeyRole, //!< for QgsSymbolV2LegendNode only - legacy rule key (void ptr, to be cast to QgsSymbolV2 ptr)
ParentRuleKeyRole //!< rule key of the parent legend node - for legends with tree hierarchy (QString). Added in 2.8
};
/** Return pointer to the parent layer node */

View File

@ -15,7 +15,8 @@ class QgsLegendSymbolItemV2
public:
QgsLegendSymbolItemV2();
//! Construct item. Does not take ownership of symbol (makes internal clone)
QgsLegendSymbolItemV2( QgsSymbolV2* symbol, const QString& label, const QString& ruleKey, bool checkable = false, int scaleMinDenom = -1, int scaleMaxDenom = -1, int level = 0 );
//! @note parentRuleKey added in 2.8
QgsLegendSymbolItemV2( QgsSymbolV2* symbol, const QString& label, const QString& ruleKey, bool checkable = false, int scaleMinDenom = -1, int scaleMaxDenom = -1, int level = 0, const QString& parentRuleKey = QString() );
~QgsLegendSymbolItemV2();
QgsLegendSymbolItemV2( const QgsLegendSymbolItemV2& other );
//QgsLegendSymbolItemV2& operator=( const QgsLegendSymbolItemV2& other );
@ -44,6 +45,9 @@ class QgsLegendSymbolItemV2
//! Identation level that tells how deep the item is in a hierarchy of items. For flat lists level is 0
int level() const;
//! Key of the parent legend node. For legends with tree hierarchy
//! @note added in 2.8
QString parentRuleKey() const;
};

View File

@ -2322,6 +2322,7 @@ void QgisApp::initLayerTreeView()
model->setFlag( QgsLayerTreeModel::AllowNodeReorder );
model->setFlag( QgsLayerTreeModel::AllowNodeRename );
model->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );
model->setFlag( QgsLayerTreeModel::ShowLegendAsTree );
model->setAutoCollapseLegendNodes( 10 );
mLayerTreeView->setModel( model );

View File

@ -114,19 +114,25 @@ QModelIndex QgsLayerTreeModel::parent( const QModelIndex &child ) const
if ( !child.isValid() )
return QModelIndex();
QgsLayerTreeNode *parentNode = 0;
QgsLayerTreeNode *n = index2node( child );
if ( !n )
if ( QgsLayerTreeNode *n = index2node( child ) )
{
QgsLayerTreeModelLegendNode* sym = index2legendNode( child );
Q_ASSERT( sym );
parentNode = sym->layerNode();
return indexOfParentLayerTreeNode( n->parent() ); // must not be null
}
else if ( QgsLayerTreeModelLegendNode* legendNode = index2legendNode( child ) )
{
return legendParent( legendNode );
}
else
{
parentNode = n->parent(); // must not be null
Q_ASSERT( false ); // no other node types!
return QModelIndex();
}
}
QModelIndex QgsLayerTreeModel::indexOfParentLayerTreeNode( QgsLayerTreeNode* parentNode ) const
{
Q_ASSERT( parentNode );
QgsLayerTreeNode* grandParentNode = parentNode->parent();
@ -1036,7 +1042,10 @@ QList<QgsLayerTreeModelLegendNode*> QgsLayerTreeModel::filterLegendNodes( const
void QgsLayerTreeModel::legendCleanup()
{
foreach ( const LayerLegendData& data, mLegend )
{
qDeleteAll( data.originalNodes );
delete data.tree;
}
mLegend.clear();
}
@ -1046,6 +1055,7 @@ void QgsLayerTreeModel::removeLegendFromLayer( QgsLayerTreeLayer* nodeLayer )
if ( mLegend.contains( nodeLayer ) )
{
qDeleteAll( mLegend[nodeLayer].originalNodes );
delete mLegend[nodeLayer].tree;
mLegend.remove( nodeLayer );
}
}
@ -1077,13 +1087,60 @@ void QgsLayerTreeModel::addLegendToLayer( QgsLayerTreeLayer* nodeL )
connect( n, SIGNAL( dataChanged() ), this, SLOT( legendNodeDataChanged() ) );
}
mLegend[nodeL].originalNodes = lstNew;
mLegend[nodeL].activeNodes = filteredLstNew;
LayerLegendData& data = mLegend[nodeL];
data.originalNodes = lstNew;
data.activeNodes = filteredLstNew;
data.tree = 0;
// maybe the legend nodes form a tree - try to create a tree structure from the list
if ( testFlag( ShowLegendAsTree ) )
tryBuildLegendTree( data );
if ( ! isEmbedded ) endInsertRows();
}
void QgsLayerTreeModel::tryBuildLegendTree( LayerLegendData& data )
{
// first check whether there are any legend nodes that are not top-level
bool hasParentKeys = false;
foreach ( QgsLayerTreeModelLegendNode* n, data.activeNodes )
{
if ( !n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString().isEmpty() )
{
hasParentKeys = true;
break;
}
}
if ( !hasParentKeys )
return; // all legend nodes are top-level => stick with list representation
// make mapping from rules to nodes and do some sanity checks
QHash<QString, QgsLayerTreeModelLegendNode*> rule2node;
rule2node[QString()] = 0;
foreach ( QgsLayerTreeModelLegendNode* n, data.activeNodes )
{
QString ruleKey = n->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
if ( ruleKey.isEmpty() ) // in tree all nodes must have key
return;
if ( rule2node.contains( ruleKey ) ) // and they must be unique
return;
rule2node[ruleKey] = n;
}
// create the tree structure
data.tree = new LayerLegendTree;
foreach ( QgsLayerTreeModelLegendNode* n, data.activeNodes )
{
QString parentRuleKey = n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString();
QgsLayerTreeModelLegendNode* parent = rule2node.value( parentRuleKey, 0 );
data.tree->parents[n] = parent;
data.tree->children[parent] << n;
}
}
QgsLayerTreeModelLegendNode* QgsLayerTreeModel::index2legendNode( const QModelIndex& index )
{
return qobject_cast<QgsLayerTreeModelLegendNode*>( reinterpret_cast<QObject*>( index.internalPointer() ) );
@ -1092,9 +1149,26 @@ QgsLayerTreeModelLegendNode* QgsLayerTreeModel::index2legendNode( const QModelIn
QModelIndex QgsLayerTreeModel::legendNode2index( QgsLayerTreeModelLegendNode* legendNode )
{
const LayerLegendData& data = mLegend[legendNode->layerNode()];
if ( data.tree )
{
if ( QgsLayerTreeModelLegendNode* parentLegendNode = data.tree->parents[legendNode] )
{
QModelIndex parentIndex = legendNode2index( parentLegendNode );
int row = data.tree->children[parentLegendNode].indexOf( legendNode );
return index( row, 0, parentIndex );
}
else
{
QModelIndex parentIndex = node2index( legendNode->layerNode() );
int row = data.tree->children[0].indexOf( legendNode );
return index( row, 0, parentIndex );
}
}
QModelIndex parentIndex = node2index( legendNode->layerNode() );
Q_ASSERT( parentIndex.isValid() );
int row = mLegend[legendNode->layerNode()].activeNodes.indexOf( legendNode );
int row = data.activeNodes.indexOf( legendNode );
if ( row < 0 ) // legend node may be filtered (exists within the list of original nodes, but not in active nodes)
return QModelIndex();
return index( row, 0, parentIndex );
@ -1103,7 +1177,10 @@ QModelIndex QgsLayerTreeModel::legendNode2index( QgsLayerTreeModelLegendNode* le
int QgsLayerTreeModel::legendNodeRowCount( QgsLayerTreeModelLegendNode* node ) const
{
Q_UNUSED( node );
const LayerLegendData& data = mLegend[node->layerNode()];
if ( data.tree )
return data.tree->children[node].count();
return 0; // they are leaves
}
@ -1113,26 +1190,55 @@ int QgsLayerTreeModel::legendRootRowCount( QgsLayerTreeLayer* nL ) const
if ( legendEmbeddedInParent( nL ) )
return 0;
return mLegend[nL].activeNodes.count();
const LayerLegendData& data = mLegend[nL];
if ( data.tree )
return data.tree->children[0].count();
return data.activeNodes.count();
}
QModelIndex QgsLayerTreeModel::legendRootIndex( int row, int column, QgsLayerTreeLayer* nL ) const
{
Q_ASSERT( mLegend.contains( nL ) );
return createIndex( row, column, static_cast<QObject*>( mLegend[nL].activeNodes.at( row ) ) );
const LayerLegendData& data = mLegend[nL];
if ( data.tree )
return createIndex( row, column, static_cast<QObject*>( data.tree->children[0].at( row ) ) );
return createIndex( row, column, static_cast<QObject*>( data.activeNodes.at( row ) ) );
}
QModelIndex QgsLayerTreeModel::legendNodeIndex( int row, int column, QgsLayerTreeModelLegendNode* node ) const
{
Q_UNUSED( row );
Q_UNUSED( column );
Q_UNUSED( node );
const LayerLegendData& data = mLegend[node->layerNode()];
if ( data.tree )
return createIndex( row, column, static_cast<QObject*>( data.tree->children[node].at( row ) ) );
return QModelIndex(); // have no children
}
QModelIndex QgsLayerTreeModel::legendParent( QgsLayerTreeModelLegendNode* legendNode ) const
{
QgsLayerTreeLayer* layerNode = legendNode->layerNode();
const LayerLegendData& data = mLegend[layerNode];
if ( data.tree )
{
if ( QgsLayerTreeModelLegendNode* parentNode = data.tree->parents[legendNode] )
{
QgsLayerTreeModelLegendNode* grandParentNode = data.tree->parents[parentNode]; // may be null (not a problem)
int row = data.tree->children[grandParentNode].indexOf( parentNode );
return createIndex( row, 0, static_cast<QObject*>( parentNode ) );
}
else
return indexOfParentLayerTreeNode( layerNode );
}
return indexOfParentLayerTreeNode( layerNode );
}
QVariant QgsLayerTreeModel::legendNodeData( QgsLayerTreeModelLegendNode* node, int role ) const
{
if ( role == Qt::CheckStateRole && !testFlag( AllowLegendChangeState ) )
@ -1153,13 +1259,13 @@ Qt::ItemFlags QgsLayerTreeModel::legendNodeFlags( QgsLayerTreeModelLegendNode* n
bool QgsLayerTreeModel::legendEmbeddedInParent( QgsLayerTreeLayer* nodeLayer ) const
{
const LayerLegendData& data = mLegend[nodeLayer];
return data.activeNodes.count() == 1 && data.activeNodes[0]->isEmbeddedInParent();
return data.activeNodes.count() == 1 && data.activeNodes[0]->isEmbeddedInParent();
}
QIcon QgsLayerTreeModel::legendIconEmbeddedInParent( QgsLayerTreeLayer* nodeLayer ) const
{
return QIcon( qvariant_cast<QPixmap>( mLegend[nodeLayer].activeNodes[0]->data( Qt::DecorationRole ) ) );
return QIcon( qvariant_cast<QPixmap>( mLegend[nodeLayer].activeNodes[0]->data( Qt::DecorationRole ) ) );
}

View File

@ -75,6 +75,7 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
ShowLegend = 0x0001, //!< Add legend nodes for layer nodes
ShowSymbology = 0x0001, //!< deprecated - use ShowLegend
ShowRasterPreviewIcon = 0x0002, //!< Will use real preview of raster layer as icon (may be slow)
ShowLegendAsTree = 0x0004, //!< For legends that support it, will show them in a tree instead of a list (needs also ShowLegend). Added in 2.8
// behavioral flags
AllowNodeReorder = 0x1000, //!< Allow reordering with drag'n'drop
@ -216,10 +217,13 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
//! Filter nodes from QgsMapLayerLegend according to the current filtering rules
QList<QgsLayerTreeModelLegendNode*> filterLegendNodes( const QList<QgsLayerTreeModelLegendNode*>& nodes );
QModelIndex indexOfParentLayerTreeNode( QgsLayerTreeNode* parentNode ) const;
int legendRootRowCount( QgsLayerTreeLayer* nL ) const;
int legendNodeRowCount( QgsLayerTreeModelLegendNode* node ) const;
QModelIndex legendRootIndex( int row, int column, QgsLayerTreeLayer* nL ) const;
QModelIndex legendNodeIndex( int row, int column, QgsLayerTreeModelLegendNode* node ) const;
QModelIndex legendParent( QgsLayerTreeModelLegendNode* legendNode ) const;
QVariant legendNodeData( QgsLayerTreeModelLegendNode* node, int role ) const;
Qt::ItemFlags legendNodeFlags( QgsLayerTreeModelLegendNode* node ) const;
bool legendEmbeddedInParent( QgsLayerTreeLayer* nodeLayer ) const;
@ -237,6 +241,20 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
//! Minimal number of nodes when legend should be automatically collapsed. -1 = disabled
int mAutoCollapseLegendNodesCount;
//! Structure that stores tree representation of map layer's legend.
//! This structure is used only when the following requirements are met:
//! 1. tree legend representation is enabled in model (ShowLegendAsTree flag)
//! 2. some legend nodes have non-null parent rule key (accessible via data(ParentRuleKeyRole) method)
//! The tree structure (parents and children of each node) is extracted by analyzing nodes' parent rules.
struct LayerLegendTree
{
//! Pointer to parent for each active node. Top-level nodes have null parent. Pointers are not owned.
QMap<QgsLayerTreeModelLegendNode*, QgsLayerTreeModelLegendNode*> parents;
//! List of children for each active node. Top-level nodes are under null pointer key. Pointers are not owned.
QMap<QgsLayerTreeModelLegendNode*, QList<QgsLayerTreeModelLegendNode*> > children;
};
//! Structure that stores all data associated with one map layer
struct LayerLegendData
{
//! Active legend nodes. May have been filtered.
@ -245,9 +263,12 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
//! Data structure for storage of legend nodes.
//! These are nodes as received from QgsMapLayerLegend
QList<QgsLayerTreeModelLegendNode*> originalNodes;
//LayerLegendTreeNode* treeRoot; // if null using ordinary
//! Optional pointer to a tree structure - see LayerLegendTree for details
LayerLegendTree* tree;
};
void tryBuildLegendTree( LayerLegendData& data );
//! Per layer data about layer's legend nodes
QMap<QgsLayerTreeLayer*, LayerLegendData> mLegend;

View File

@ -195,7 +195,7 @@ QVariant QgsSymbolV2LegendNode::data( int role ) const
pix.fill( Qt::transparent );
}
if ( mItem.level() == 0 )
if ( mItem.level() == 0 || ( model() && model()->testFlag( QgsLayerTreeModel::ShowLegendAsTree ) ) )
mPixmap = pix;
else
{
@ -229,6 +229,10 @@ QVariant QgsSymbolV2LegendNode::data( int role ) const
{
return QVariant::fromValue<void*>( mItem.legacyRuleKey() );
}
else if ( role == ParentRuleKeyRole )
{
return mItem.parentRuleKey();
}
return QVariant();
}

View File

@ -48,7 +48,8 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
enum LegendNodeRoles
{
RuleKeyRole = Qt::UserRole, //!< rule key of the node (QString)
SymbolV2LegacyRuleKeyRole //!< for QgsSymbolV2LegendNode only - legacy rule key (void ptr, to be cast to QgsSymbolV2 ptr)
SymbolV2LegacyRuleKeyRole, //!< for QgsSymbolV2LegendNode only - legacy rule key (void ptr, to be cast to QgsSymbolV2 ptr)
ParentRuleKeyRole //!< rule key of the parent legend node - for legends with tree hierarchy (QString). Added in 2.8
};
/** Return pointer to the parent layer node */

View File

@ -27,7 +27,7 @@ QgsLegendSymbolItemV2::QgsLegendSymbolItemV2()
{
}
QgsLegendSymbolItemV2::QgsLegendSymbolItemV2( QgsSymbolV2* symbol, const QString& label, const QString& ruleKey, bool checkable, int scaleMinDenom, int scaleMaxDenom, int level )
QgsLegendSymbolItemV2::QgsLegendSymbolItemV2( QgsSymbolV2* symbol, const QString& label, const QString& ruleKey, bool checkable, int scaleMinDenom, int scaleMaxDenom, int level, const QString& parentRuleKey )
: mSymbol( symbol ? symbol->clone() : 0 )
, mLabel( label )
, mKey( ruleKey )
@ -36,6 +36,7 @@ QgsLegendSymbolItemV2::QgsLegendSymbolItemV2( QgsSymbolV2* symbol, const QString
, mScaleMinDenom( scaleMinDenom )
, mScaleMaxDenom( scaleMaxDenom )
, mLevel( level )
, mParentKey( parentRuleKey )
{
}
@ -64,6 +65,7 @@ QgsLegendSymbolItemV2& QgsLegendSymbolItemV2::operator=( const QgsLegendSymbolIt
mScaleMinDenom = other.mScaleMinDenom;
mScaleMaxDenom = other.mScaleMaxDenom;
mLevel = other.mLevel;
mParentKey = other.mParentKey;
return *this;
}

View File

@ -32,7 +32,8 @@ class CORE_EXPORT QgsLegendSymbolItemV2
public:
QgsLegendSymbolItemV2();
//! Construct item. Does not take ownership of symbol (makes internal clone)
QgsLegendSymbolItemV2( QgsSymbolV2* symbol, const QString& label, const QString& ruleKey, bool checkable = false, int scaleMinDenom = -1, int scaleMaxDenom = -1, int level = 0 );
//! @note parentRuleKey added in 2.8
QgsLegendSymbolItemV2( QgsSymbolV2* symbol, const QString& label, const QString& ruleKey, bool checkable = false, int scaleMinDenom = -1, int scaleMaxDenom = -1, int level = 0, const QString& parentRuleKey = QString() );
~QgsLegendSymbolItemV2();
QgsLegendSymbolItemV2( const QgsLegendSymbolItemV2& other );
QgsLegendSymbolItemV2& operator=( const QgsLegendSymbolItemV2& other );
@ -61,6 +62,10 @@ class CORE_EXPORT QgsLegendSymbolItemV2
//! Identation level that tells how deep the item is in a hierarchy of items. For flat lists level is 0
int level() const { return mLevel; }
//! Key of the parent legend node. For legends with tree hierarchy
//! @note added in 2.8
QString parentRuleKey() const { return mParentKey; }
protected:
//! Set symbol of the item. Takes ownership of symbol.
void setSymbol( QgsSymbolV2* s );
@ -84,6 +89,8 @@ class CORE_EXPORT QgsLegendSymbolItemV2
//! Identation level that tells how deep the item is in a hierarchy of items. For flat lists level is 0
int mLevel;
//! Key of the parent legend node. For legends with tree hierarchy
QString mParentKey;
};

View File

@ -219,7 +219,7 @@ QgsLegendSymbolListV2 QgsRuleBasedRendererV2::Rule::legendSymbolItemsV2( int cur
QgsLegendSymbolListV2 lst;
if ( currentLevel != -1 ) // root rule should not be shown
{
lst << QgsLegendSymbolItemV2( mSymbol, mLabel, mRuleKey, true, mScaleMinDenom, mScaleMaxDenom, currentLevel );
lst << QgsLegendSymbolItemV2( mSymbol, mLabel, mRuleKey, true, mScaleMinDenom, mScaleMaxDenom, currentLevel, mParent ? mParent->mRuleKey : QString() );
}
for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )