QGIS/src/core/layertree/qgslayertreemodel.cpp
2019-04-02 17:18:40 +02:00

1575 lines
47 KiB
C++

/***************************************************************************
qgslayertreemodel.cpp
--------------------------------------
Date : May 2014
Copyright : (C) 2014 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslayertreemodel.h"
#include "qgslayertree.h"
#include "qgslayertreeutils.h"
#include "qgslayertreemodellegendnode.h"
#include "qgsproject.h"
#include "qgsapplication.h"
#include <QMimeData>
#include <QTextStream>
#include "qgsdataitem.h"
#include "qgsmaphittest.h"
#include "qgsmaplayer.h"
#include "qgsmaplayerlegend.h"
#include "qgsmaplayerstylemanager.h"
#include "qgspluginlayer.h"
#include "qgsrasterlayer.h"
#include "qgsrenderer.h"
#include "qgssymbollayerutils.h"
#include "qgsvectorlayer.h"
QgsLayerTreeModel::QgsLayerTreeModel( QgsLayerTree *rootNode, QObject *parent )
: QAbstractItemModel( parent )
, mRootNode( rootNode )
, mFlags( ShowLegend | AllowLegendChangeState | DeferredLegendInvalidation )
, mAutoCollapseLegendNodesCount( -1 )
, mLegendFilterByScale( 0 )
, mLegendFilterUsesExtent( false )
, mLegendMapViewMupp( 0 )
, mLegendMapViewDpi( 0 )
, mLegendMapViewScale( 0 )
{
connectToRootNode();
mFontLayer.setBold( true );
connect( &mDeferLegendInvalidationTimer, &QTimer::timeout, this, &QgsLayerTreeModel::invalidateLegendMapBasedData );
mDeferLegendInvalidationTimer.setSingleShot( true );
}
QgsLayerTreeModel::~QgsLayerTreeModel()
{
legendCleanup();
}
QgsLayerTreeNode *QgsLayerTreeModel::index2node( const QModelIndex &index ) const
{
if ( !index.isValid() )
return mRootNode;
QObject *obj = reinterpret_cast<QObject *>( index.internalPointer() );
return qobject_cast<QgsLayerTreeNode *>( obj );
}
int QgsLayerTreeModel::rowCount( const QModelIndex &parent ) const
{
if ( QgsLayerTreeModelLegendNode *nodeLegend = index2legendNode( parent ) )
return legendNodeRowCount( nodeLegend );
QgsLayerTreeNode *n = index2node( parent );
if ( !n )
return 0;
if ( QgsLayerTree::isLayer( n ) )
{
if ( !testFlag( ShowLegend ) )
return 0;
return legendRootRowCount( QgsLayerTree::toLayer( n ) );
}
return n->children().count();
}
int QgsLayerTreeModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent );
return 1;
}
QModelIndex QgsLayerTreeModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( column < 0 || column >= columnCount( parent ) ||
row < 0 || row >= rowCount( parent ) )
return QModelIndex();
if ( QgsLayerTreeModelLegendNode *nodeLegend = index2legendNode( parent ) )
return legendNodeIndex( row, column, nodeLegend );
QgsLayerTreeNode *n = index2node( parent );
if ( !n )
return QModelIndex(); // have no children
if ( testFlag( ShowLegend ) && QgsLayerTree::isLayer( n ) )
{
return legendRootIndex( row, column, QgsLayerTree::toLayer( n ) );
}
return createIndex( row, column, static_cast<QObject *>( n->children().at( row ) ) );
}
QModelIndex QgsLayerTreeModel::parent( const QModelIndex &child ) const
{
if ( !child.isValid() )
return QModelIndex();
if ( QgsLayerTreeNode *n = index2node( child ) )
{
return indexOfParentLayerTreeNode( n->parent() ); // must not be null
}
else if ( QgsLayerTreeModelLegendNode *legendNode = index2legendNode( child ) )
{
return legendParent( legendNode );
}
else
{
Q_ASSERT( false ); // no other node types!
return QModelIndex();
}
}
QModelIndex QgsLayerTreeModel::indexOfParentLayerTreeNode( QgsLayerTreeNode *parentNode ) const
{
Q_ASSERT( parentNode );
QgsLayerTreeNode *grandParentNode = parentNode->parent();
if ( !grandParentNode )
return QModelIndex(); // root node -> invalid index
int row = grandParentNode->children().indexOf( parentNode );
Q_ASSERT( row >= 0 );
return createIndex( row, 0, static_cast<QObject *>( parentNode ) );
}
QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() || index.column() > 1 )
return QVariant();
if ( QgsLayerTreeModelLegendNode *sym = index2legendNode( index ) )
return legendNodeData( sym, role );
QgsLayerTreeNode *node = index2node( index );
if ( role == Qt::DisplayRole || role == Qt::EditRole )
{
if ( QgsLayerTree::isGroup( node ) )
return QgsLayerTree::toGroup( node )->name();
if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
QString name = nodeLayer->name();
if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() && role == Qt::DisplayRole )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
if ( vlayer && vlayer->featureCount() >= 0 )
name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
}
return name;
}
}
else if ( role == Qt::DecorationRole && index.column() == 0 )
{
if ( QgsLayerTree::isGroup( node ) )
return iconGroup();
if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
QgsMapLayer *layer = nodeLayer->layer();
if ( !layer )
return QVariant();
// icons possibly overriding default icon
switch ( layer->type() )
{
case QgsMapLayerType::RasterLayer:
return QgsLayerItem::iconRaster();
case QgsMapLayerType::MeshLayer:
return QgsLayerItem::iconMesh();
case QgsMapLayerType::VectorLayer:
case QgsMapLayerType::PluginLayer:
break;
}
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
QIcon icon;
// if there's just on legend entry that should be embedded in layer - do that!
if ( testFlag( ShowLegend ) && legendEmbeddedInParent( nodeLayer ) )
{
icon = legendIconEmbeddedInParent( nodeLayer );
}
else if ( vlayer && layer->type() == QgsMapLayerType::VectorLayer )
{
if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry )
icon = QgsLayerItem::iconPoint();
else if ( vlayer->geometryType() == QgsWkbTypes::LineGeometry )
icon = QgsLayerItem::iconLine();
else if ( vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
icon = QgsLayerItem::iconPolygon();
else if ( vlayer->geometryType() == QgsWkbTypes::NullGeometry )
icon = QgsLayerItem::iconTable();
else
icon = QgsLayerItem::iconDefault();
}
if ( vlayer && vlayer->isEditable() )
{
const int iconSize = scaleIconSize( 16 );
QPixmap pixmap( icon.pixmap( iconSize, iconSize ) );
QPainter painter( &pixmap );
painter.drawPixmap( 0, 0, iconSize, iconSize, QgsApplication::getThemePixmap( vlayer->isModified() ? QStringLiteral( "/mIconEditableEdits.svg" ) : QStringLiteral( "/mActionToggleEditing.svg" ) ) );
painter.end();
icon = QIcon( pixmap );
}
return icon;
}
}
else if ( role == Qt::CheckStateRole )
{
if ( !testFlag( AllowNodeChangeVisibility ) )
return QVariant();
if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
if ( nodeLayer->layer() && !nodeLayer->layer()->isSpatial() )
return QVariant(); // do not show checkbox for non-spatial tables
return nodeLayer->itemVisibilityChecked() ? Qt::Checked : Qt::Unchecked;
}
else if ( QgsLayerTree::isGroup( node ) )
{
QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
return nodeGroup->itemVisibilityChecked() ? Qt::Checked : Qt::Unchecked;
}
}
else if ( role == Qt::FontRole )
{
QFont f( QgsLayerTree::isLayer( node ) ? mFontLayer : ( QgsLayerTree::isGroup( node ) ? mFontGroup : QFont() ) );
if ( index == mCurrentIndex )
f.setUnderline( true );
if ( QgsLayerTree::isLayer( node ) )
{
const QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer();
if ( ( !node->isVisible() && ( !layer || layer->isSpatial() ) ) || ( layer && !layer->isInScaleRange( mLegendMapViewScale ) ) )
{
f.setItalic( !f.italic() );
}
}
return f;
}
else if ( role == Qt::ForegroundRole )
{
QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
if ( QgsLayerTree::isLayer( node ) )
{
const QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer();
if ( ( !node->isVisible() && ( !layer || layer->isSpatial() ) ) || ( layer && !layer->isInScaleRange( mLegendMapViewScale ) ) )
{
QColor fadedTextColor = brush.color();
fadedTextColor.setAlpha( 128 );
brush.setColor( fadedTextColor );
}
}
return brush;
}
else if ( role == Qt::ToolTipRole )
{
if ( QgsLayerTree::isLayer( node ) )
{
if ( QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer() )
{
QString title =
!layer->title().isEmpty() ? layer->title() :
!layer->shortName().isEmpty() ? layer->shortName() :
layer->name();
title = "<b>" + title.toHtmlEscaped() + "</b>";
if ( layer->isSpatial() && layer->crs().isValid() )
{
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
title += tr( " (%1 - %2)" ).arg( QgsWkbTypes::displayString( vl->wkbType() ), layer->crs().authid() ).toHtmlEscaped();
else
title += tr( " (%1)" ).arg( layer->crs().authid() ).toHtmlEscaped();
}
QStringList parts;
parts << title;
if ( !layer->abstract().isEmpty() )
{
parts << QString();
const QStringList abstractLines = layer->abstract().split( '\n' );
for ( const auto &l : abstractLines )
{
parts << l.toHtmlEscaped();
}
parts << QString();
}
QString source( layer->publicSource() );
if ( source.size() > 1024 )
{
source = source.left( 1023 ) + QStringLiteral( "" );
}
parts << "<i>" + source.toHtmlEscaped() + "</i>";
return parts.join( QStringLiteral( "<br/>" ) );
}
}
}
return QVariant();
}
Qt::ItemFlags QgsLayerTreeModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() )
{
Qt::ItemFlags rootFlags = Qt::ItemFlags();
if ( testFlag( AllowNodeReorder ) )
rootFlags |= Qt::ItemIsDropEnabled;
return rootFlags;
}
if ( QgsLayerTreeModelLegendNode *symn = index2legendNode( index ) )
return legendNodeFlags( symn );
Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
if ( testFlag( AllowNodeRename ) )
f |= Qt::ItemIsEditable;
QgsLayerTreeNode *node = index2node( index );
bool isEmbedded = node->customProperty( QStringLiteral( "embedded" ) ).toInt();
if ( testFlag( AllowNodeReorder ) )
{
// only root embedded nodes can be reordered
if ( !isEmbedded || ( isEmbedded && node->parent() && !node->parent()->customProperty( QStringLiteral( "embedded" ) ).toInt() ) )
f |= Qt::ItemIsDragEnabled;
}
if ( testFlag( AllowNodeChangeVisibility ) && ( QgsLayerTree::isLayer( node ) || QgsLayerTree::isGroup( node ) ) )
f |= Qt::ItemIsUserCheckable;
if ( testFlag( AllowNodeReorder ) && QgsLayerTree::isGroup( node ) && !isEmbedded )
f |= Qt::ItemIsDropEnabled;
return f;
}
bool QgsLayerTreeModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
QgsLayerTreeModelLegendNode *sym = index2legendNode( index );
if ( sym )
{
if ( role == Qt::CheckStateRole && !testFlag( AllowLegendChangeState ) )
return false;
bool res = sym->setData( value, role );
if ( res )
emit dataChanged( index, index );
return res;
}
QgsLayerTreeNode *node = index2node( index );
if ( !node )
return QAbstractItemModel::setData( index, value, role );
if ( role == Qt::CheckStateRole )
{
if ( !testFlag( AllowNodeChangeVisibility ) )
return false;
bool checked = static_cast< Qt::CheckState >( value.toInt() ) == Qt::Checked;
if ( checked && node->children().isEmpty() )
{
node->setItemVisibilityCheckedParentRecursive( checked );
}
else if ( testFlag( ActionHierarchical ) )
{
if ( node->children().isEmpty() )
node->setItemVisibilityCheckedParentRecursive( checked );
else
node->setItemVisibilityCheckedRecursive( checked );
}
else
{
node->setItemVisibilityChecked( checked );
}
recursivelyEmitDataChanged( index );
return true;
}
else if ( role == Qt::EditRole )
{
if ( !testFlag( AllowNodeRename ) )
return false;
if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer *layer = QgsLayerTree::toLayer( node );
layer->setName( value.toString() );
emit dataChanged( index, index );
}
else if ( QgsLayerTree::isGroup( node ) )
{
QgsLayerTree::toGroup( node )->setName( value.toString() );
emit dataChanged( index, index );
}
}
return QAbstractItemModel::setData( index, value, role );
}
QModelIndex QgsLayerTreeModel::node2index( QgsLayerTreeNode *node ) const
{
if ( !node || !node->parent() )
return QModelIndex(); // this is the only root item -> invalid index
QModelIndex parentIndex = node2index( node->parent() );
int row = node->parent()->children().indexOf( node );
Q_ASSERT( row >= 0 );
return index( row, 0, parentIndex );
}
static bool _isChildOfNode( QgsLayerTreeNode *child, QgsLayerTreeNode *node )
{
if ( !child->parent() )
return false;
if ( child->parent() == node )
return true;
return _isChildOfNode( child->parent(), node );
}
static bool _isChildOfNodes( QgsLayerTreeNode *child, const QList<QgsLayerTreeNode *> &nodes )
{
Q_FOREACH ( QgsLayerTreeNode *n, nodes )
{
if ( _isChildOfNode( child, n ) )
return true;
}
return false;
}
QList<QgsLayerTreeNode *> QgsLayerTreeModel::indexes2nodes( const QModelIndexList &list, bool skipInternal ) const
{
QList<QgsLayerTreeNode *> nodes;
Q_FOREACH ( const QModelIndex &index, list )
{
QgsLayerTreeNode *node = index2node( index );
if ( !node )
continue;
nodes << node;
}
if ( !skipInternal )
return nodes;
// remove any children of nodes if both parent node and children are selected
QList<QgsLayerTreeNode *> nodesFinal;
Q_FOREACH ( QgsLayerTreeNode *node, nodes )
{
if ( !_isChildOfNodes( node, nodes ) )
nodesFinal << node;
}
return nodesFinal;
}
QgsLayerTree *QgsLayerTreeModel::rootGroup() const
{
return mRootNode;
}
void QgsLayerTreeModel::setRootGroup( QgsLayerTree *newRootGroup )
{
beginResetModel();
disconnectFromRootNode();
Q_ASSERT( mLegend.isEmpty() );
mRootNode = newRootGroup;
endResetModel();
connectToRootNode();
}
void QgsLayerTreeModel::refreshLayerLegend( QgsLayerTreeLayer *nodeLayer )
{
// update title
QModelIndex idx = node2index( nodeLayer );
emit dataChanged( idx, idx );
// update children
int oldNodeCount = rowCount( idx );
beginRemoveRows( idx, 0, std::max( oldNodeCount - 1, 0 ) );
removeLegendFromLayer( nodeLayer );
endRemoveRows();
addLegendToLayer( nodeLayer );
int newNodeCount = rowCount( idx );
// automatic collapse of legend nodes - useful if a layer has many legend nodes
if ( mAutoCollapseLegendNodesCount != -1 && oldNodeCount != newNodeCount && newNodeCount >= mAutoCollapseLegendNodesCount )
nodeLayer->setExpanded( false );
}
QModelIndex QgsLayerTreeModel::currentIndex() const
{
return mCurrentIndex;
}
void QgsLayerTreeModel::setCurrentIndex( const QModelIndex &currentIndex )
{
QModelIndex oldIndex = mCurrentIndex;
mCurrentIndex = currentIndex;
if ( oldIndex.isValid() )
emit dataChanged( oldIndex, oldIndex );
if ( currentIndex.isValid() )
emit dataChanged( currentIndex, currentIndex );
}
void QgsLayerTreeModel::setLayerTreeNodeFont( int nodeType, const QFont &font )
{
if ( nodeType == QgsLayerTreeNode::NodeGroup )
{
if ( mFontGroup != font )
{
mFontGroup = font;
recursivelyEmitDataChanged();
}
}
else if ( nodeType == QgsLayerTreeNode::NodeLayer )
{
if ( mFontLayer != font )
{
mFontLayer = font;
recursivelyEmitDataChanged();
}
}
else
{
QgsDebugMsgLevel( QStringLiteral( "invalid node type" ), 4 );
}
}
QFont QgsLayerTreeModel::layerTreeNodeFont( int nodeType ) const
{
if ( nodeType == QgsLayerTreeNode::NodeGroup )
return mFontGroup;
else if ( nodeType == QgsLayerTreeNode::NodeLayer )
return mFontLayer;
else
{
QgsDebugMsgLevel( QStringLiteral( "invalid node type" ), 4 );
return QFont();
}
}
void QgsLayerTreeModel::setLegendFilterByScale( double scale )
{
mLegendFilterByScale = scale;
// this could be later done in more efficient way
// by just updating active legend nodes, without refreshing original legend nodes
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mRootNode->findLayers() )
refreshLayerLegend( nodeLayer );
}
void QgsLayerTreeModel::setLegendFilterByMap( const QgsMapSettings *settings )
{
setLegendFilter( settings, /* useExtent = */ true );
}
void QgsLayerTreeModel::setLegendFilter( const QgsMapSettings *settings, bool useExtent, const QgsGeometry &polygon, bool useExpressions )
{
if ( settings && settings->hasValidSettings() )
{
mLegendFilterMapSettings.reset( new QgsMapSettings( *settings ) );
mLegendFilterMapSettings->setLayerStyleOverrides( mLayerStyleOverrides );
QgsMapHitTest::LayerFilterExpression exprs;
mLegendFilterUsesExtent = useExtent;
// collect expression filters
if ( useExpressions )
{
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mRootNode->findLayers() )
{
bool enabled;
QString expr = QgsLayerTreeUtils::legendFilterByExpression( *nodeLayer, &enabled );
if ( enabled && !expr.isEmpty() )
{
exprs[ nodeLayer->layerId()] = expr;
}
}
}
bool polygonValid = !polygon.isNull() && polygon.type() == QgsWkbTypes::PolygonGeometry;
if ( useExpressions && !useExtent && !polygonValid ) // only expressions
{
mLegendFilterHitTest.reset( new QgsMapHitTest( *mLegendFilterMapSettings, exprs ) );
}
else
{
mLegendFilterHitTest.reset( new QgsMapHitTest( *mLegendFilterMapSettings, polygon, exprs ) );
}
mLegendFilterHitTest->run();
}
else
{
if ( !mLegendFilterMapSettings )
return; // no change
mLegendFilterMapSettings.reset();
mLegendFilterHitTest.reset();
}
// temporarily disable autocollapse so that legend nodes stay visible
int bkAutoCollapse = autoCollapseLegendNodes();
setAutoCollapseLegendNodes( -1 );
// this could be later done in more efficient way
// by just updating active legend nodes, without refreshing original legend nodes
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mRootNode->findLayers() )
refreshLayerLegend( nodeLayer );
setAutoCollapseLegendNodes( bkAutoCollapse );
}
void QgsLayerTreeModel::setLegendMapViewData( double mapUnitsPerPixel, int dpi, double scale )
{
if ( mLegendMapViewDpi == dpi && qgsDoubleNear( mLegendMapViewMupp, mapUnitsPerPixel ) && qgsDoubleNear( mLegendMapViewScale, scale ) )
return;
mLegendMapViewMupp = mapUnitsPerPixel;
mLegendMapViewDpi = dpi;
mLegendMapViewScale = scale;
// now invalidate legend nodes!
legendInvalidateMapBasedData();
refreshScaleBasedLayers();
}
void QgsLayerTreeModel::legendMapViewData( double *mapUnitsPerPixel, int *dpi, double *scale ) const
{
if ( mapUnitsPerPixel ) *mapUnitsPerPixel = mLegendMapViewMupp;
if ( dpi ) *dpi = mLegendMapViewDpi;
if ( scale ) *scale = mLegendMapViewScale;
}
QMap<QString, QString> QgsLayerTreeModel::layerStyleOverrides() const
{
return mLayerStyleOverrides;
}
void QgsLayerTreeModel::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
{
mLayerStyleOverrides = overrides;
}
int QgsLayerTreeModel::scaleIconSize( int standardSize )
{
QFontMetrics fm( ( QFont() ) );
const double scale = 1.1 * standardSize / 24;
return static_cast< int >( std::floor( std::max( Qgis::UI_SCALE_FACTOR * fm.height() * scale, static_cast< double >( standardSize ) ) ) );
}
void QgsLayerTreeModel::nodeWillAddChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo )
{
Q_ASSERT( node );
beginInsertRows( node2index( node ), indexFrom, indexTo );
}
static QList<QgsLayerTreeLayer *> _layerNodesInSubtree( QgsLayerTreeNode *node, int indexFrom, int indexTo )
{
QList<QgsLayerTreeNode *> children = node->children();
QList<QgsLayerTreeLayer *> newLayerNodes;
for ( int i = indexFrom; i <= indexTo; ++i )
{
QgsLayerTreeNode *child = children.at( i );
if ( QgsLayerTree::isLayer( child ) )
newLayerNodes << QgsLayerTree::toLayer( child );
else if ( QgsLayerTree::isGroup( child ) )
newLayerNodes << QgsLayerTree::toGroup( child )->findLayers();
}
return newLayerNodes;
}
void QgsLayerTreeModel::nodeAddedChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo )
{
Q_ASSERT( node );
endInsertRows();
Q_FOREACH ( QgsLayerTreeLayer *newLayerNode, _layerNodesInSubtree( node, indexFrom, indexTo ) )
connectToLayer( newLayerNode );
}
void QgsLayerTreeModel::nodeWillRemoveChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo )
{
Q_ASSERT( node );
beginRemoveRows( node2index( node ), indexFrom, indexTo );
// disconnect from layers and remove their legend
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, _layerNodesInSubtree( node, indexFrom, indexTo ) )
disconnectFromLayer( nodeLayer );
}
void QgsLayerTreeModel::nodeRemovedChildren()
{
endRemoveRows();
}
void QgsLayerTreeModel::nodeVisibilityChanged( QgsLayerTreeNode *node )
{
Q_ASSERT( node );
QModelIndex index = node2index( node );
emit dataChanged( index, index );
}
void QgsLayerTreeModel::nodeNameChanged( QgsLayerTreeNode *node, const QString &name )
{
Q_UNUSED( name );
Q_ASSERT( node );
QModelIndex index = node2index( node );
emit dataChanged( index, index );
}
void QgsLayerTreeModel::nodeCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key )
{
if ( QgsLayerTree::isLayer( node ) && key == QLatin1String( "showFeatureCount" ) )
refreshLayerLegend( QgsLayerTree::toLayer( node ) );
}
void QgsLayerTreeModel::nodeLayerLoaded()
{
QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( sender() );
if ( !nodeLayer )
return;
// deferred connection to the layer
connectToLayer( nodeLayer );
}
void QgsLayerTreeModel::nodeLayerWillBeUnloaded()
{
QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( sender() );
if ( !nodeLayer )
return;
disconnectFromLayer( nodeLayer );
// wait for the layer to appear again
connect( nodeLayer, &QgsLayerTreeLayer::layerLoaded, this, &QgsLayerTreeModel::nodeLayerLoaded );
}
void QgsLayerTreeModel::layerLegendChanged()
{
if ( !mRootNode )
return;
if ( !testFlag( ShowLegend ) )
return;
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
if ( !layer )
return;
QgsLayerTreeLayer *nodeLayer = mRootNode->findLayer( layer->id() );
if ( !nodeLayer )
return;
refreshLayerLegend( nodeLayer );
}
void QgsLayerTreeModel::layerNeedsUpdate()
{
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
if ( !layer )
return;
QgsLayerTreeLayer *nodeLayer = mRootNode->findLayer( layer->id() );
if ( !nodeLayer )
return;
QModelIndex index = node2index( nodeLayer );
emit dataChanged( index, index );
if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ) ).toInt() )
refreshLayerLegend( nodeLayer );
}
void QgsLayerTreeModel::legendNodeDataChanged()
{
QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( sender() );
if ( !legendNode )
return;
QModelIndex index = legendNode2index( legendNode );
if ( index.isValid() )
emit dataChanged( index, index );
}
void QgsLayerTreeModel::connectToLayer( QgsLayerTreeLayer *nodeLayer )
{
if ( !nodeLayer->layer() )
{
// in order to connect to layer, we need to have it loaded.
// keep an eye on the layer ID: once loaded, we will use it
connect( nodeLayer, &QgsLayerTreeLayer::layerLoaded, this, &QgsLayerTreeModel::nodeLayerLoaded );
return;
}
// watch if the layer is getting removed
connect( nodeLayer, &QgsLayerTreeLayer::layerWillBeUnloaded, this, &QgsLayerTreeModel::nodeLayerWillBeUnloaded );
if ( testFlag( ShowLegend ) )
{
addLegendToLayer( nodeLayer );
// automatic collapse of legend nodes - useful if a layer has many legend nodes
if ( !mRootNode->customProperty( QStringLiteral( "loading" ) ).toBool() )
{
if ( mAutoCollapseLegendNodesCount != -1 && rowCount( node2index( nodeLayer ) ) >= mAutoCollapseLegendNodesCount )
nodeLayer->setExpanded( false );
}
}
QgsMapLayer *layer = nodeLayer->layer();
connect( layer, &QgsMapLayer::legendChanged, this, &QgsLayerTreeModel::layerLegendChanged, Qt::UniqueConnection );
if ( layer->type() == QgsMapLayerType::VectorLayer )
{
// using unique connection because there may be temporarily more nodes for a layer than just one
// which would create multiple connections, however disconnect() would disconnect all multiple connections
// even if we wanted to disconnect just one connection in each call.
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
connect( vl, &QgsVectorLayer::editingStarted, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection );
connect( vl, &QgsVectorLayer::editingStopped, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection );
connect( vl, &QgsVectorLayer::layerModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection );
}
}
// try to find out if the layer ID is present in the tree multiple times
static int _numLayerCount( QgsLayerTreeGroup *group, const QString &layerId )
{
int count = 0;
Q_FOREACH ( QgsLayerTreeNode *child, group->children() )
{
if ( QgsLayerTree::isLayer( child ) )
{
if ( QgsLayerTree::toLayer( child )->layerId() == layerId )
count++;
}
else if ( QgsLayerTree::isGroup( child ) )
{
count += _numLayerCount( QgsLayerTree::toGroup( child ), layerId );
}
}
return count;
}
void QgsLayerTreeModel::disconnectFromLayer( QgsLayerTreeLayer *nodeLayer )
{
disconnect( nodeLayer, nullptr, this, nullptr ); // disconnect from delayed load of layer
if ( !nodeLayer->layer() )
return; // we were never connected
if ( testFlag( ShowLegend ) )
{
removeLegendFromLayer( nodeLayer );
}
if ( _numLayerCount( mRootNode, nodeLayer->layerId() ) == 1 )
{
// last instance of the layer in the tree: disconnect from all signals from layer!
disconnect( nodeLayer->layer(), nullptr, this, nullptr );
}
}
void QgsLayerTreeModel::connectToLayers( QgsLayerTreeGroup *parentGroup )
{
Q_FOREACH ( QgsLayerTreeNode *node, parentGroup->children() )
{
if ( QgsLayerTree::isGroup( node ) )
connectToLayers( QgsLayerTree::toGroup( node ) );
else if ( QgsLayerTree::isLayer( node ) )
connectToLayer( QgsLayerTree::toLayer( node ) );
}
}
void QgsLayerTreeModel::disconnectFromLayers( QgsLayerTreeGroup *parentGroup )
{
Q_FOREACH ( QgsLayerTreeNode *node, parentGroup->children() )
{
if ( QgsLayerTree::isGroup( node ) )
disconnectFromLayers( QgsLayerTree::toGroup( node ) );
else if ( QgsLayerTree::isLayer( node ) )
disconnectFromLayer( QgsLayerTree::toLayer( node ) );
}
}
void QgsLayerTreeModel::connectToRootNode()
{
Q_ASSERT( mRootNode );
connect( mRootNode, &QgsLayerTreeNode::willAddChildren, this, &QgsLayerTreeModel::nodeWillAddChildren );
connect( mRootNode, &QgsLayerTreeNode::addedChildren, this, &QgsLayerTreeModel::nodeAddedChildren );
connect( mRootNode, &QgsLayerTreeNode::willRemoveChildren, this, &QgsLayerTreeModel::nodeWillRemoveChildren );
connect( mRootNode, &QgsLayerTreeNode::removedChildren, this, &QgsLayerTreeModel::nodeRemovedChildren );
connect( mRootNode, &QgsLayerTreeNode::visibilityChanged, this, &QgsLayerTreeModel::nodeVisibilityChanged );
connect( mRootNode, &QgsLayerTreeNode::nameChanged, this, &QgsLayerTreeModel::nodeNameChanged );
connect( mRootNode, &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayerTreeModel::nodeCustomPropertyChanged );
connectToLayers( mRootNode );
}
void QgsLayerTreeModel::disconnectFromRootNode()
{
disconnect( mRootNode, nullptr, this, nullptr );
disconnectFromLayers( mRootNode );
}
void QgsLayerTreeModel::recursivelyEmitDataChanged( const QModelIndex &idx )
{
QgsLayerTreeNode *node = index2node( idx );
if ( !node )
return;
int count = node->children().count();
if ( count == 0 )
return;
emit dataChanged( index( 0, 0, idx ), index( count - 1, 0, idx ) );
for ( int i = 0; i < count; ++i )
recursivelyEmitDataChanged( index( i, 0, idx ) );
}
void QgsLayerTreeModel::refreshScaleBasedLayers( const QModelIndex &idx )
{
QgsLayerTreeNode *node = index2node( idx );
if ( !node )
return;
if ( node->nodeType() == QgsLayerTreeNode::NodeLayer )
{
const QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer();
if ( layer && layer->hasScaleBasedVisibility() )
{
emit dataChanged( idx, idx );
}
}
int count = node->children().count();
for ( int i = 0; i < count; ++i )
refreshScaleBasedLayers( index( i, 0, idx ) );
}
Qt::DropActions QgsLayerTreeModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList QgsLayerTreeModel::mimeTypes() const
{
QStringList types;
types << QStringLiteral( "application/qgis.layertreemodeldata" );
return types;
}
QMimeData *QgsLayerTreeModel::mimeData( const QModelIndexList &indexes ) const
{
// Sort the indexes. Depending on how the user selected the items, the indexes may be unsorted.
QModelIndexList sortedIndexes = indexes;
std::sort( sortedIndexes.begin(), sortedIndexes.end(), std::less<QModelIndex>() );
QList<QgsLayerTreeNode *> nodesFinal = indexes2nodes( sortedIndexes, true );
if ( nodesFinal.isEmpty() )
return nullptr;
QMimeData *mimeData = new QMimeData();
QDomDocument doc;
QDomElement rootElem = doc.createElement( QStringLiteral( "layer_tree_model_data" ) );
Q_FOREACH ( QgsLayerTreeNode *node, nodesFinal )
node->writeXml( rootElem, QgsReadWriteContext() );
doc.appendChild( rootElem );
QString txt = doc.toString();
mimeData->setData( QStringLiteral( "application/qgis.layertreemodeldata" ), txt.toUtf8() );
mimeData->setData( QStringLiteral( "application/x-vnd.qgis.qgis.uri" ), QgsMimeDataUtils::layerTreeNodesToUriList( nodesFinal ) );
return mimeData;
}
bool QgsLayerTreeModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
if ( action == Qt::IgnoreAction )
return true;
if ( !data->hasFormat( QStringLiteral( "application/qgis.layertreemodeldata" ) ) )
return false;
if ( column >= columnCount( parent ) )
return false;
QgsLayerTreeNode *nodeParent = index2node( parent );
if ( !QgsLayerTree::isGroup( nodeParent ) )
return false;
QByteArray encodedData = data->data( QStringLiteral( "application/qgis.layertreemodeldata" ) );
QDomDocument doc;
if ( !doc.setContent( QString::fromUtf8( encodedData ) ) )
return false;
QDomElement rootElem = doc.documentElement();
if ( rootElem.tagName() != QLatin1String( "layer_tree_model_data" ) )
return false;
QList<QgsLayerTreeNode *> nodes;
QDomElement elem = rootElem.firstChildElement();
while ( !elem.isNull() )
{
QgsLayerTreeNode *node = QgsLayerTreeNode::readXml( elem, QgsProject::instance() );
if ( node )
nodes << node;
elem = elem.nextSiblingElement();
}
if ( nodes.isEmpty() )
return false;
if ( parent.isValid() && row == -1 )
row = 0; // if dropped directly onto group item, insert at first position
QgsLayerTree::toGroup( nodeParent )->insertChildNodes( row, nodes );
return true;
}
bool QgsLayerTreeModel::removeRows( int row, int count, const QModelIndex &parent )
{
QgsLayerTreeNode *parentNode = index2node( parent );
if ( QgsLayerTree::isGroup( parentNode ) )
{
QgsLayerTree::toGroup( parentNode )->removeChildren( row, count );
return true;
}
return false;
}
void QgsLayerTreeModel::setFlags( QgsLayerTreeModel::Flags f )
{
mFlags = f;
}
void QgsLayerTreeModel::setFlag( QgsLayerTreeModel::Flag f, bool on )
{
if ( on )
mFlags |= f;
else
mFlags &= ~f;
}
QgsLayerTreeModel::Flags QgsLayerTreeModel::flags() const
{
return mFlags;
}
bool QgsLayerTreeModel::testFlag( QgsLayerTreeModel::Flag f ) const
{
return mFlags.testFlag( f );
}
QIcon QgsLayerTreeModel::iconGroup()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mActionFolder.svg" ) );
}
QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeModel::filterLegendNodes( const QList<QgsLayerTreeModelLegendNode *> &nodes )
{
QList<QgsLayerTreeModelLegendNode *> filtered;
if ( mLegendFilterByScale > 0 )
{
Q_FOREACH ( QgsLayerTreeModelLegendNode *node, nodes )
{
if ( node->isScaleOK( mLegendFilterByScale ) )
filtered << node;
}
}
else if ( mLegendFilterMapSettings )
{
if ( !nodes.isEmpty() && mLegendFilterMapSettings->layers().contains( nodes.at( 0 )->layerNode()->layer() ) )
{
Q_FOREACH ( QgsLayerTreeModelLegendNode *node, nodes )
{
QString ruleKey = node->data( QgsSymbolLegendNode::RuleKeyRole ).toString();
bool checked = mLegendFilterUsesExtent || node->data( Qt::CheckStateRole ).toInt() == Qt::Checked;
if ( checked )
{
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( node->layerNode()->layer() ) )
{
if ( mLegendFilterHitTest->legendKeyVisible( ruleKey, vl ) )
filtered << node;
}
else
{
filtered << node;
}
}
else // unknown node type or unchecked
filtered << node;
}
}
}
else
{
return nodes;
}
return filtered;
}
///////////////////////////////////////////////////////////////////////////////
// Legend nodes routines - start
void QgsLayerTreeModel::legendCleanup()
{
Q_FOREACH ( const LayerLegendData &data, mLegend )
{
qDeleteAll( data.originalNodes );
delete data.tree;
}
mLegend.clear();
}
void QgsLayerTreeModel::removeLegendFromLayer( QgsLayerTreeLayer *nodeLayer )
{
if ( mLegend.contains( nodeLayer ) )
{
qDeleteAll( mLegend[nodeLayer].originalNodes );
delete mLegend[nodeLayer].tree;
mLegend.remove( nodeLayer );
}
}
void QgsLayerTreeModel::addLegendToLayer( QgsLayerTreeLayer *nodeL )
{
if ( !nodeL || !nodeL->layer() )
return;
QgsMapLayer *ml = nodeL->layer();
QgsMapLayerLegend *layerLegend = ml->legend();
if ( !layerLegend )
return;
QgsMapLayerStyleOverride styleOverride( ml );
if ( mLayerStyleOverrides.contains( ml->id() ) )
styleOverride.setOverrideStyle( mLayerStyleOverrides.value( ml->id() ) );
QList<QgsLayerTreeModelLegendNode *> lstNew = layerLegend->createLayerTreeModelLegendNodes( nodeL );
// apply filtering defined in layer node's custom properties (reordering, filtering, custom labels)
QgsMapLayerLegendUtils::applyLayerNodeProperties( nodeL, lstNew );
if ( testFlag( UseEmbeddedWidgets ) )
{
// generate placeholder legend nodes that will be replaced by widgets in QgsLayerTreeView
int widgetsCount = ml->customProperty( QStringLiteral( "embeddedWidgets/count" ), 0 ).toInt();
while ( widgetsCount > 0 )
{
lstNew.insert( 0, new EmbeddedWidgetLegendNode( nodeL ) );
--widgetsCount;
}
}
QList<QgsLayerTreeModelLegendNode *> filteredLstNew = filterLegendNodes( lstNew );
Q_FOREACH ( QgsLayerTreeModelLegendNode *n, lstNew )
{
n->setParent( this );
connect( n, &QgsLayerTreeModelLegendNode::dataChanged, this, &QgsLayerTreeModel::legendNodeDataChanged );
}
// See if we have an embedded node - if we do, we will not use it among active nodes.
// Legend node embedded in parent does not have to be the first one,
// there can be also nodes generated for embedded widgets
QgsLayerTreeModelLegendNode *embeddedNode = nullptr;
Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, filteredLstNew )
{
if ( legendNode->isEmbeddedInParent() )
{
embeddedNode = legendNode;
filteredLstNew.removeOne( legendNode );
break;
}
}
LayerLegendTree *legendTree = nullptr;
// maybe the legend nodes form a tree - try to create a tree structure from the list
if ( testFlag( ShowLegendAsTree ) )
legendTree = tryBuildLegendTree( filteredLstNew );
int count = legendTree ? legendTree->children[nullptr].count() : filteredLstNew.count();
if ( !filteredLstNew.isEmpty() ) beginInsertRows( node2index( nodeL ), 0, count - 1 );
LayerLegendData data;
data.originalNodes = lstNew;
data.activeNodes = filteredLstNew;
data.embeddedNodeInParent = embeddedNode;
data.tree = legendTree;
mLegend[nodeL] = data;
if ( !filteredLstNew.isEmpty() ) endInsertRows();
// invalidate map based data even if the data is not map-based to make sure
// the symbol sizes are computed at least once
legendInvalidateMapBasedData();
}
QgsLayerTreeModel::LayerLegendTree *QgsLayerTreeModel::tryBuildLegendTree( const QList<QgsLayerTreeModelLegendNode *> &nodes )
{
// first check whether there are any legend nodes that are not top-level
bool hasParentKeys = false;
Q_FOREACH ( QgsLayerTreeModelLegendNode *n, nodes )
{
if ( !n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString().isEmpty() )
{
hasParentKeys = true;
break;
}
}
if ( !hasParentKeys )
return nullptr; // 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()] = nullptr;
Q_FOREACH ( QgsLayerTreeModelLegendNode *n, nodes )
{
QString ruleKey = n->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
if ( ruleKey.isEmpty() ) // in tree all nodes must have key
return nullptr;
if ( rule2node.contains( ruleKey ) ) // and they must be unique
return nullptr;
rule2node[ruleKey] = n;
}
// create the tree structure
LayerLegendTree *tree = new LayerLegendTree;
Q_FOREACH ( QgsLayerTreeModelLegendNode *n, nodes )
{
QString parentRuleKey = n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString();
QgsLayerTreeModelLegendNode *parent = rule2node.value( parentRuleKey, nullptr );
tree->parents[n] = parent;
tree->children[parent] << n;
}
return tree;
}
QgsRenderContext *QgsLayerTreeModel::createTemporaryRenderContext() const
{
double scale = 0.0;
double mupp = 0.0;
int dpi = 0;
legendMapViewData( &mupp, &dpi, &scale );
bool validData = !qgsDoubleNear( mupp, 0.0 ) && dpi != 0 && !qgsDoubleNear( scale, 0.0 );
// setup temporary render context
std::unique_ptr<QgsRenderContext> context( new QgsRenderContext );
context->setScaleFactor( dpi / 25.4 );
context->setRendererScale( scale );
context->setMapToPixel( QgsMapToPixel( mupp ) );
return validData ? context.release() : nullptr;
}
QgsLayerTreeModelLegendNode *QgsLayerTreeModel::index2legendNode( const QModelIndex &index )
{
return qobject_cast<QgsLayerTreeModelLegendNode *>( reinterpret_cast<QObject *>( index.internalPointer() ) );
}
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[nullptr].indexOf( legendNode );
return index( row, 0, parentIndex );
}
}
QModelIndex parentIndex = node2index( legendNode->layerNode() );
Q_ASSERT( parentIndex.isValid() );
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 );
}
int QgsLayerTreeModel::legendNodeRowCount( QgsLayerTreeModelLegendNode *node ) const
{
const LayerLegendData &data = mLegend[node->layerNode()];
if ( data.tree )
return data.tree->children[node].count();
return 0; // they are leaves
}
int QgsLayerTreeModel::legendRootRowCount( QgsLayerTreeLayer *nL ) const
{
if ( !mLegend.contains( nL ) )
return 0;
const LayerLegendData &data = mLegend[nL];
if ( data.tree )
return data.tree->children[nullptr].count();
int count = data.activeNodes.count();
return count;
}
QModelIndex QgsLayerTreeModel::legendRootIndex( int row, int column, QgsLayerTreeLayer *nL ) const
{
Q_ASSERT( mLegend.contains( nL ) );
const LayerLegendData &data = mLegend[nL];
if ( data.tree )
return createIndex( row, column, static_cast<QObject *>( data.tree->children[nullptr].at( row ) ) );
return createIndex( row, column, static_cast<QObject *>( data.activeNodes.at( row ) ) );
}
QModelIndex QgsLayerTreeModel::legendNodeIndex( int row, int column, QgsLayerTreeModelLegendNode *node ) const
{
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 ) )
return QVariant();
return node->data( role );
}
Qt::ItemFlags QgsLayerTreeModel::legendNodeFlags( QgsLayerTreeModelLegendNode *node ) const
{
Qt::ItemFlags f = node->flags();
if ( !testFlag( AllowLegendChangeState ) )
f &= ~Qt::ItemIsUserCheckable;
return f;
}
bool QgsLayerTreeModel::legendEmbeddedInParent( QgsLayerTreeLayer *nodeLayer ) const
{
return static_cast< bool >( mLegend[nodeLayer].embeddedNodeInParent );
}
QgsLayerTreeModelLegendNode *QgsLayerTreeModel::legendNodeEmbeddedInParent( QgsLayerTreeLayer *nodeLayer ) const
{
return mLegend[nodeLayer].embeddedNodeInParent;
}
QIcon QgsLayerTreeModel::legendIconEmbeddedInParent( QgsLayerTreeLayer *nodeLayer ) const
{
QgsLayerTreeModelLegendNode *legendNode = mLegend[nodeLayer].embeddedNodeInParent;
if ( !legendNode )
return QIcon();
return QIcon( qvariant_cast<QPixmap>( legendNode->data( Qt::DecorationRole ) ) );
}
QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeModel::layerLegendNodes( QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent )
{
if ( !mLegend.contains( nodeLayer ) )
return QList<QgsLayerTreeModelLegendNode *>();
const LayerLegendData &data = mLegend[nodeLayer];
QList<QgsLayerTreeModelLegendNode *> lst( data.activeNodes );
if ( !skipNodeEmbeddedInParent && data.embeddedNodeInParent )
lst.prepend( data.embeddedNodeInParent );
return lst;
}
QList<QgsLayerTreeModelLegendNode *> QgsLayerTreeModel::layerOriginalLegendNodes( QgsLayerTreeLayer *nodeLayer )
{
return mLegend.value( nodeLayer ).originalNodes;
}
QgsLayerTreeModelLegendNode *QgsLayerTreeModel::findLegendNode( const QString &layerId, const QString &ruleKey ) const
{
QMap<QgsLayerTreeLayer *, LayerLegendData>::const_iterator it = mLegend.constBegin();
for ( ; it != mLegend.constEnd(); ++it )
{
QgsLayerTreeLayer *layer = it.key();
if ( layer->layerId() == layerId )
{
Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, mLegend.value( layer ).activeNodes )
{
if ( legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() == ruleKey )
{
//found it!
return legendNode;
}
}
}
}
return nullptr;
}
void QgsLayerTreeModel::legendInvalidateMapBasedData()
{
if ( !testFlag( DeferredLegendInvalidation ) )
invalidateLegendMapBasedData();
else
mDeferLegendInvalidationTimer.start( 1000 );
}
void QgsLayerTreeModel::invalidateLegendMapBasedData()
{
// we have varying icon sizes, and we want icon to be centered and
// text to be left aligned, so we have to compute the max width of icons
//
// we do that for nodes who share a common parent
//
// we do that here because for symbols with size defined in map units
// the symbol sizes changes depends on the zoom level
std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
Q_FOREACH ( const LayerLegendData &data, mLegend )
{
QList<QgsSymbolLegendNode *> symbolNodes;
QMap<QString, int> widthMax;
Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, data.originalNodes )
{
QgsSymbolLegendNode *n = qobject_cast<QgsSymbolLegendNode *>( legendNode );
if ( n )
{
const QSize sz( n->minimumIconSize( context.get() ) );
const QString parentKey( n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString() );
widthMax[parentKey] = std::max( sz.width(), widthMax.contains( parentKey ) ? widthMax[parentKey] : 0 );
n->setIconSize( sz );
symbolNodes.append( n );
}
}
Q_FOREACH ( QgsSymbolLegendNode *n, symbolNodes )
{
const QString parentKey( n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString() );
Q_ASSERT( widthMax[parentKey] > 0 );
const int twiceMarginWidth = 2; // a one pixel margin avoids hugly rendering of icon
n->setIconSize( QSize( widthMax[parentKey] + twiceMarginWidth, n->iconSize().rheight() + twiceMarginWidth ) );
}
Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, data.originalNodes )
legendNode->invalidateMapBasedData();
}
}
// Legend nodes routines - end
///////////////////////////////////////////////////////////////////////////////