diff --git a/python/core/layertree/qgslayertreemodel.sip b/python/core/layertree/qgslayertreemodel.sip index 5206b90d541..3c70876a92b 100644 --- a/python/core/layertree/qgslayertreemodel.sip +++ b/python/core/layertree/qgslayertreemodel.sip @@ -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 diff --git a/python/core/layertree/qgslayertreemodellegendnode.sip b/python/core/layertree/qgslayertreemodellegendnode.sip index 110068a8f7d..42c789fe8a9 100644 --- a/python/core/layertree/qgslayertreemodellegendnode.sip +++ b/python/core/layertree/qgslayertreemodellegendnode.sip @@ -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 */ diff --git a/python/core/symbology-ng/qgslegendsymbolitemv2.sip b/python/core/symbology-ng/qgslegendsymbolitemv2.sip index c1b26cd053c..27d3f94178a 100644 --- a/python/core/symbology-ng/qgslegendsymbolitemv2.sip +++ b/python/core/symbology-ng/qgslegendsymbolitemv2.sip @@ -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; }; diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index e249dc9d28d..fefa7569df4 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -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 ); diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp index 3afa21f5a65..b69f3f0d432 100644 --- a/src/core/layertree/qgslayertreemodel.cpp +++ b/src/core/layertree/qgslayertreemodel.cpp @@ -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 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 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( reinterpret_cast( 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( mLegend[nL].activeNodes.at( row ) ) ); + const LayerLegendData& data = mLegend[nL]; + if ( data.tree ) + return createIndex( row, column, static_cast( data.tree->children[0].at( row ) ) ); + + return createIndex( row, column, static_cast( 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( 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( 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( mLegend[nodeLayer].activeNodes[0]->data( Qt::DecorationRole ) ) ); + return QIcon( qvariant_cast( mLegend[nodeLayer].activeNodes[0]->data( Qt::DecorationRole ) ) ); } diff --git a/src/core/layertree/qgslayertreemodel.h b/src/core/layertree/qgslayertreemodel.h index c7497c7488f..7a2a634907f 100644 --- a/src/core/layertree/qgslayertreemodel.h +++ b/src/core/layertree/qgslayertreemodel.h @@ -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 filterLegendNodes( const QList& 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 parents; + //! List of children for each active node. Top-level nodes are under null pointer key. Pointers are not owned. + QMap > 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 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 mLegend; diff --git a/src/core/layertree/qgslayertreemodellegendnode.cpp b/src/core/layertree/qgslayertreemodellegendnode.cpp index 887e9864166..1ac1ead304c 100644 --- a/src/core/layertree/qgslayertreemodellegendnode.cpp +++ b/src/core/layertree/qgslayertreemodellegendnode.cpp @@ -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( mItem.legacyRuleKey() ); } + else if ( role == ParentRuleKeyRole ) + { + return mItem.parentRuleKey(); + } return QVariant(); } diff --git a/src/core/layertree/qgslayertreemodellegendnode.h b/src/core/layertree/qgslayertreemodellegendnode.h index b3271fb8df8..fe590b15f0e 100644 --- a/src/core/layertree/qgslayertreemodellegendnode.h +++ b/src/core/layertree/qgslayertreemodellegendnode.h @@ -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 */ diff --git a/src/core/symbology-ng/qgslegendsymbolitemv2.cpp b/src/core/symbology-ng/qgslegendsymbolitemv2.cpp index c85d7a64dfc..793d5735f8c 100644 --- a/src/core/symbology-ng/qgslegendsymbolitemv2.cpp +++ b/src/core/symbology-ng/qgslegendsymbolitemv2.cpp @@ -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; } diff --git a/src/core/symbology-ng/qgslegendsymbolitemv2.h b/src/core/symbology-ng/qgslegendsymbolitemv2.h index 980c6fdfede..6a40e2d1d06 100644 --- a/src/core/symbology-ng/qgslegendsymbolitemv2.h +++ b/src/core/symbology-ng/qgslegendsymbolitemv2.h @@ -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; }; diff --git a/src/core/symbology-ng/qgsrulebasedrendererv2.cpp b/src/core/symbology-ng/qgsrulebasedrendererv2.cpp index 6d282c3f10c..ef506890a0d 100644 --- a/src/core/symbology-ng/qgsrulebasedrendererv2.cpp +++ b/src/core/symbology-ng/qgsrulebasedrendererv2.cpp @@ -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 )