Skip to content

Commit

Permalink
[FEATURE] Allow overriding the legend patch size on a per-item basis
Browse files Browse the repository at this point in the history
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 28, 2020
1 parent 18547ec commit 0c64fd7
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 66 deletions.
24 changes: 24 additions & 0 deletions python/core/auto_generated/layertree/qgslayertreelayer.sip.in
Expand Up @@ -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

Expand Down
Expand Up @@ -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();
Expand Down Expand Up @@ -100,6 +124,8 @@ Default implementation does nothing. *
double maxSiblingSymbolWidth;

QgsLegendPatchShape patchShape;

QSizeF patchSize;
};

struct ItemMetrics
Expand Down
24 changes: 24 additions & 0 deletions python/core/auto_generated/qgsmaplayerlegend.sip.in
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions src/core/layertree/qgslayertreelayer.cpp
Expand Up @@ -18,6 +18,7 @@
#include "qgslayertreeutils.h"
#include "qgsmaplayer.h"
#include "qgsproject.h"
#include "qgssymbollayerutils.h"


QgsLayerTreeLayer::QgsLayerTreeLayer( QgsMapLayer *layer )
Expand All @@ -40,6 +41,7 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( const QgsLayerTreeLayer &other )
, mRef( other.mRef )
, mLayerName( other.mLayerName )
, mPatchShape( other.mPatchShape )
, mPatchSize( other.mPatchSize )
{
attachToLayer();
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 );

Expand Down
23 changes: 23 additions & 0 deletions src/core/layertree/qgslayertreelayer.h
Expand Up @@ -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:

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

QgsLegendPatchShape mPatchShape;
QSizeF mPatchSize;
};


Expand Down
80 changes: 57 additions & 23 deletions src/core/layertree/qgslayertreemodellegendnode.cpp
Expand Up @@ -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 )
{
Expand All @@ -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 );
Expand All @@ -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() )
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions src/core/layertree/qgslayertreemodellegendnode.h
Expand Up @@ -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; }

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -229,6 +260,7 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
bool mEmbeddedInParent;
QString mUserLabel;
QgsLegendPatchShape mPatchShape;
QSizeF mUserSize;
};

#include "qgslegendsymbolitem.h"
Expand Down

0 comments on commit 0c64fd7

Please sign in to comment.