Skip to content
Permalink
Browse files

[FEATURE] Allow overriding the legend patch size on a per-item basis

Allows users to override the symbol patch size for individual legend
nodes, by double clicking the node

Width and height can be individually overridden, with the node falling
back to the default width or height when the override isn't set.

Sponsored by SLYR
  • Loading branch information
nyalldawson committed Apr 27, 2020
1 parent 18547ec commit 0c64fd7907a87005156474cc9adc38c651b53fe4
@@ -153,6 +153,30 @@ Sets the symbol patch ``shape`` to use when rendering the legend node symbol.

.. seealso:: :py:func:`patchShape`

.. versionadded:: 3.14
%End

QSizeF patchSize() const;
%Docstring
Returns the user (overridden) size for the legend node.

If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
symbol width or height from :py:class:`QgsLegendSettings`.

.. seealso:: :py:func:`setPatchSize`

.. versionadded:: 3.14
%End

void setPatchSize( QSizeF size );
%Docstring
Sets the user (overridden) ``size`` for the legend node.

If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
symbol width or height from :py:class:`QgsLegendSettings`.

.. seealso:: :py:func:`patchSize`

.. versionadded:: 3.14
%End

@@ -72,6 +72,30 @@ Sets some data associated with the item. Default implementation does nothing and
virtual QString userLabel() const;
virtual void setUserLabel( const QString &userLabel );

virtual QSizeF userPatchSize() const;
%Docstring
Returns the user (overridden) size for the legend node.

If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
symbol width or height from :py:class:`QgsLegendSettings`.

.. seealso:: :py:func:`setUserPatchSize`

.. versionadded:: 3.14
%End

virtual void setUserPatchSize( QSizeF size );
%Docstring
Sets the user (overridden) ``size`` for the legend node.

If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
symbol width or height from :py:class:`QgsLegendSettings`.

.. seealso:: :py:func:`userPatchSize`

.. versionadded:: 3.14
%End

virtual bool isScaleOK( double scale ) const;

virtual void invalidateMapBasedData();
@@ -100,6 +124,8 @@ Default implementation does nothing. *
double maxSiblingSymbolWidth;

QgsLegendPatchShape patchShape;

QSizeF patchSize;
};

struct ItemMetrics
@@ -111,6 +111,30 @@ Returns the legend patch shape for the legend node belonging to ``nodeLayer`` at

.. seealso:: :py:func:`setLegendNodePatchShape`

.. versionadded:: 3.14
%End

static void setLegendNodeSymbolSize( QgsLayerTreeLayer *nodeLayer, int originalIndex, QSizeF size );
%Docstring
Sets the legend symbol ``size`` for the legend node belonging to ``nodeLayer`` at the specified ``originalIndex``.

If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
symbol width or height from :py:class:`QgsLegendSettings`.

.. seealso:: :py:func:`legendNodeSymbolSize`

.. versionadded:: 3.14
%End

static QSizeF legendNodeSymbolSize( QgsLayerTreeLayer *nodeLayer, int originalIndex );
%Docstring
Returns the legend node symbol size for the legend node belonging to ``nodeLayer`` at the specified ``originalIndex``.

If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
symbol width or height from :py:class:`QgsLegendSettings`.

.. seealso:: :py:func:`setLegendNodeSymbolSize`

.. versionadded:: 3.14
%End

@@ -18,6 +18,7 @@
#include "qgslayertreeutils.h"
#include "qgsmaplayer.h"
#include "qgsproject.h"
#include "qgssymbollayerutils.h"


QgsLayerTreeLayer::QgsLayerTreeLayer( QgsMapLayer *layer )
@@ -40,6 +41,7 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( const QgsLayerTreeLayer &other )
, mRef( other.mRef )
, mLayerName( other.mLayerName )
, mPatchShape( other.mPatchShape )
, mPatchSize( other.mPatchSize )
{
attachToLayer();
}
@@ -130,6 +132,8 @@ QgsLayerTreeLayer *QgsLayerTreeLayer::readXml( QDomElement &element, const QgsRe
nodeLayer->setPatchShape( patch );
}

nodeLayer->setPatchSize( QgsSymbolLayerUtils::decodeSize( element.attribute( QStringLiteral( "patch_size" ) ) ) );

return nodeLayer;
}

@@ -164,6 +168,7 @@ void QgsLayerTreeLayer::writeXml( QDomElement &parentElement, const QgsReadWrite
mPatchShape.writeXml( patchElem, doc, context );
elem.appendChild( patchElem );
}
elem.setAttribute( QStringLiteral( "patch_size" ), QgsSymbolLayerUtils::encodeSize( mPatchSize ) );

writeCommonXml( elem );

@@ -159,6 +159,28 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
*/
void setPatchShape( const QgsLegendPatchShape &shape );

/**
* Returns the user (overridden) size for the legend node.
*
* If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
* symbol width or height from QgsLegendSettings.
*
* \see setPatchSize()
* \since QGIS 3.14
*/
QSizeF patchSize() const { return mPatchSize; }

/**
* Sets the user (overridden) \a size for the legend node.
*
* If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
* symbol width or height from QgsLegendSettings.
*
* \see patchSize()
* \since QGIS 3.14
*/
void setPatchSize( QSizeF size ) { mPatchSize = size; }

signals:

/**
@@ -210,6 +232,7 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
#endif

QgsLegendPatchShape mPatchShape;
QSizeF mPatchSize;
};


@@ -59,6 +59,13 @@ bool QgsLayerTreeModelLegendNode::setData( const QVariant &value, int role )
return false;
}

QSizeF QgsLayerTreeModelLegendNode::userPatchSize() const
{
if ( mEmbeddedInParent )
return mLayerNode->patchSize();

return mUserSize;
}

QgsLayerTreeModelLegendNode::ItemMetrics QgsLayerTreeModelLegendNode::draw( const QgsLegendSettings &settings, ItemContext *ctx )
{
@@ -68,7 +75,7 @@ QgsLayerTreeModelLegendNode::ItemMetrics QgsLayerTreeModelLegendNode::draw( cons
// itemHeight here is not really item height, it is only for symbol
// vertical alignment purpose, i.e. OK take single line height
// if there are more lines, those run under the symbol
double itemHeight = std::max( static_cast< double >( settings.symbolSize().height() ), textHeight );
double itemHeight = std::max( static_cast< double >( ctx && ctx->patchSize.height() > 0 ? ctx->patchSize.height() : settings.symbolSize().height() ), textHeight );

ItemMetrics im;
im.symbolSize = drawSymbol( settings, ctx, itemHeight );
@@ -88,6 +95,15 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbol( const QgsLegendSettings &setting
if ( symbolIcon.isNull() )
return QSizeF();

QSizeF size = settings.symbolSize();
if ( ctx )
{
if ( ctx->patchSize.width() > 0 )
size.setWidth( ctx->patchSize.width( ) );
if ( ctx->patchSize.height() > 0 )
size.setHeight( ctx->patchSize.height( ) );
}

if ( ctx && ctx->painter )
{
switch ( settings.symbolAlignment() )
@@ -96,21 +112,21 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbol( const QgsLegendSettings &setting
default:
symbolIcon.paint( ctx->painter,
static_cast< int >( ctx->columnLeft ),
static_cast< int >( ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2 ),
static_cast< int >( settings.symbolSize().width() ),
static_cast< int >( settings.symbolSize().height() ) );
static_cast< int >( ctx->top + ( itemHeight - size.height() ) / 2 ),
static_cast< int >( size.width() ),
static_cast< int >( size.height() ) );
break;

case Qt::AlignRight:
symbolIcon.paint( ctx->painter,
static_cast< int >( ctx->columnRight - settings.symbolSize().width() ),
static_cast< int >( ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2 ),
static_cast< int >( settings.symbolSize().width() ),
static_cast< int >( settings.symbolSize().height() ) );
static_cast< int >( ctx->columnRight - size.width() ),
static_cast< int >( ctx->top + ( itemHeight - size.height() ) / 2 ),
static_cast< int >( size.width() ),
static_cast< int >( size.height() ) );
break;
}
}
return settings.symbolSize();
return size;
}

void QgsLayerTreeModelLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &, QJsonObject &json ) const
@@ -531,8 +547,10 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
}

//Consider symbol size for point markers
double height = settings.symbolSize().height();
double width = settings.symbolSize().width();
const double desiredHeight = ctx && ctx->patchSize.height() > 0 ? ctx->patchSize.height() : settings.symbolSize().height();
const double desiredWidth = ctx && ctx->patchSize.width() > 0 ? ctx->patchSize.width() : settings.symbolSize().width();
double height = desiredHeight;
double width = desiredWidth;

//Center small marker symbols
double widthOffset = 0;
@@ -544,19 +562,19 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
double size = markerSymbol->size( *context ) / context->scaleFactor();
height = size;
width = size;
if ( width < settings.symbolSize().width() )
if ( width < desiredWidth )
{
widthOffset = ( settings.symbolSize().width() - width ) / 2.0;
widthOffset = ( desiredWidth - width ) / 2.0;
}
if ( height < settings.symbolSize().height() )
if ( height < desiredHeight )
{
heightOffset = ( settings.symbolSize().height() - height ) / 2.0;
heightOffset = ( desiredHeight - height ) / 2.0;
}
}

if ( ctx && ctx->painter )
{
double currentYCoord = ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2;
double currentYCoord = ctx->top + ( itemHeight - desiredHeight ) / 2;
QPainter *p = ctx->painter;

//setup painter scaling to dots so that raster symbology is drawn to scale
@@ -621,8 +639,8 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
p->restore();
}

return QSizeF( std::max( width + 2 * widthOffset, static_cast< double >( settings.symbolSize().width() ) ),
std::max( height + 2 * heightOffset, static_cast< double >( settings.symbolSize().height() ) ) );
return QSizeF( std::max( width + 2 * widthOffset, static_cast< double >( desiredWidth ) ),
std::max( height + 2 * heightOffset, static_cast< double >( desiredHeight ) ) );
}

void QgsSymbolLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &context, QJsonObject &json ) const
@@ -858,6 +876,22 @@ QVariant QgsRasterSymbolLegendNode::data( int role ) const

QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
{
QSizeF size = settings.symbolSize();
double offsetX = 0;
if ( ctx )
{
if ( ctx->patchSize.width() > 0 )
{
if ( ctx->patchSize.width() < size.width() )
offsetX = ( size.width() - ctx->patchSize.width() ) / 2.0;
size.setWidth( ctx->patchSize.width() );
}
if ( ctx->patchSize.height() > 0 )
{
size.setHeight( ctx->patchSize.height() );
}
}

if ( ctx && ctx->painter )
{
QColor itemColor = mColor;
@@ -885,17 +919,17 @@ QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings,
{
case Qt::AlignLeft:
default:
ctx->painter->drawRect( QRectF( ctx->columnLeft, ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2,
settings.symbolSize().width(), settings.symbolSize().height() ) );
ctx->painter->drawRect( QRectF( ctx->columnLeft + offsetX, ctx->top + ( itemHeight - size.height() ) / 2,
size.width(), size.height() ) );
break;

case Qt::AlignRight:
ctx->painter->drawRect( QRectF( ctx->columnRight - settings.symbolSize().width(), ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2,
settings.symbolSize().width(), settings.symbolSize().height() ) );
ctx->painter->drawRect( QRectF( ctx->columnRight - size.width() - offsetX, ctx->top + ( itemHeight - size.height() ) / 2,
size.width(), size.height() ) );
break;
}
}
return settings.symbolSize();
return size;
}

void QgsRasterSymbolLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &, QJsonObject &json ) const
@@ -87,6 +87,28 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
virtual QString userLabel() const { return mUserLabel; }
virtual void setUserLabel( const QString &userLabel ) { mUserLabel = userLabel; }

/**
* Returns the user (overridden) size for the legend node.
*
* If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
* symbol width or height from QgsLegendSettings.
*
* \see setUserPatchSize()
* \since QGIS 3.14
*/
virtual QSizeF userPatchSize() const;

/**
* Sets the user (overridden) \a size for the legend node.
*
* If either the width or height are non-zero, they will be used when rendering the legend node instead of the default
* symbol width or height from QgsLegendSettings.
*
* \see userPatchSize()
* \since QGIS 3.14
*/
virtual void setUserPatchSize( QSizeF size ) { mUserSize = size; }

virtual bool isScaleOK( double scale ) const { Q_UNUSED( scale ) return true; }

/**
@@ -152,6 +174,15 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
* \since QGIS 3.14
*/
QgsLegendPatchShape patchShape;

/**
* Symbol patch size to render for the node.
*
* If either the width or height are zero, then the default width/height from QgsLegendSettings::symbolSize() should be used instead.
*
* \since QGIS 3.14
*/
QSizeF patchSize;
};

struct ItemMetrics
@@ -229,6 +260,7 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
bool mEmbeddedInParent;
QString mUserLabel;
QgsLegendPatchShape mPatchShape;
QSizeF mUserSize;
};

#include "qgslegendsymbolitem.h"

0 comments on commit 0c64fd7

Please sign in to comment.
You can’t perform that action at this time.