mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-12 00:02:25 -04:00
1575 lines
47 KiB
C++
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 ¤tIndex )
|
|
{
|
|
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
|
|
///////////////////////////////////////////////////////////////////////////////
|