[FEATURE][layouts] Expose choice of arrangement of legends (i.e.

symbols to the left OR symbols to the right of legend text), and
alignment for group/subgroup/item text

Allows creation of right-to-left locale friendly legends. Additionally,
we default to this right-to-left style alignment when creating new
legends under a RTL based locale.
This commit is contained in:
Nyall Dawson 2019-06-25 09:00:21 +10:00
parent 640283f700
commit 6aeedfe20b
36 changed files with 1051 additions and 368 deletions

View File

@ -765,6 +765,8 @@
<file>themes/default/mIconAlignJustify.svg</file>
<file>themes/default/mIconAlignLeft.svg</file>
<file>themes/default/mIconAlignRight.svg</file>
<file>themes/default/mIconArrangeSymbolsLeft.svg</file>
<file>themes/default/mIconArrangeSymbolsRight.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 6.35 6.35"><path style="marker:none" d="M2.646 2.646H5.82v.529H2.646zm0 1.852H4.55v.529H2.646zm0-3.44h2.54v.53h-2.54z" color="#555753" overflow="visible" fill="#555753" filter="url(#filter8750)"/><path fill="#848484" stroke="#5c5c5c" stroke-width=".224" d="M.641.641h1.364V1.74H.641zm0 1.852h1.364v1.099H.641zm0 1.852h1.364v1.099H.641z"/></svg>

After

Width:  |  Height:  |  Size: 420 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 6.35 6.35"><path style="marker:none" d="M.53 2.646h3.174v.529H.53zm0 1.852h1.904v.529H.53zm0-3.44h2.539v.53H.529z" color="#555753" overflow="visible" fill="#555753" filter="url(#filter8750)"/><path d="M4.345.641H5.71v1.1H4.345zm0 1.852H5.71v1.1H4.345zm0 1.852H5.71v1.1H4.345z" fill="#848484" stroke="#5c5c5c" stroke-width=".224"/></svg>

After

Width:  |  Height:  |  Size: 412 B

View File

@ -76,10 +76,23 @@ Default implementation does nothing. *
struct ItemContext
{
ItemContext();
QgsRenderContext *context;
QPainter *painter;
QPointF point;
double labelXOffset;
double top;
double columnLeft;
double columnRight;
double maxSiblingSymbolWidth;
};
struct ItemMetrics

View File

@ -279,6 +279,28 @@ Returns the legend symbol width.
Sets the legend symbol ``width``.
.. seealso:: :py:func:`symbolWidth`
%End
void setSymbolAlignment( Qt::AlignmentFlag alignment );
%Docstring
Sets the ``alignment`` for placement of legend symbols.
Only Qt.AlignLeft or Qt.AlignRight are supported values.
.. seealso:: :py:func:`symbolAlignment`
.. versionadded:: 3.10.0
%End
Qt::AlignmentFlag symbolAlignment() const;
%Docstring
Returns the alignment for placement of legend symbols.
Only Qt.AlignLeft or Qt.AlignRight are supported values.
.. seealso:: :py:func:`setSymbolAlignment`
.. versionadded:: 3.10.0
%End
double symbolHeight() const;

View File

@ -32,18 +32,14 @@ in QgsLegendModel class.
Qt::AlignmentFlag titleAlignment() const;
%Docstring
Returns the alignment of the legend title
:return: Qt.AlignmentFlag for the legend title
Returns the alignment of the legend title.
.. seealso:: :py:func:`setTitleAlignment`
%End
void setTitleAlignment( Qt::AlignmentFlag alignment );
%Docstring
Sets the alignment of the legend title
:param alignment: Text alignment for drawing the legend title
Sets the ``alignment`` of the legend title.
.. seealso:: :py:func:`titleAlignment`
%End
@ -102,6 +98,28 @@ Overrides fontColor()
QSizeF symbolSize() const;
void setSymbolSize( QSizeF s );
void setSymbolAlignment( Qt::AlignmentFlag alignment );
%Docstring
Sets the ``alignment`` for placement of legend symbols.
Only Qt.AlignLeft or Qt.AlignRight are supported values.
.. seealso:: :py:func:`symbolAlignment`
.. versionadded:: 3.10.0
%End
Qt::AlignmentFlag symbolAlignment() const;
%Docstring
Returns the alignment for placement of legend symbols.
Only Qt.AlignLeft or Qt.AlignRight are supported values.
.. seealso:: :py:func:`setSymbolAlignment`
.. versionadded:: 3.10.0
%End
bool drawRasterStroke() const;
%Docstring
Returns whether a stroke will be drawn around raster symbol items.

View File

@ -58,6 +58,24 @@ The font for this style.
void setMargin( double margin );
%Docstring
Sets all margins
%End
Qt::Alignment alignment() const;
%Docstring
Returns the alignment for the legend component.
.. seealso:: :py:func:`setAlignment`
.. versionadded:: 3.10
%End
void setAlignment( Qt::Alignment alignment );
%Docstring
Sets the alignment for the legend component.
.. seealso:: :py:func:`alignment`
.. versionadded:: 3.10
%End
void writeXml( const QString &name, QDomElement &elem, QDomDocument &doc ) const;

View File

@ -49,6 +49,18 @@ Sets the current ``alignment`` choice.
.. seealso:: :py:func:`currentAlignment`
%End
void customiseAlignmentDisplay( Qt::Alignment alignment, const QString &text = QString(), const QIcon &icon = QIcon() );
%Docstring
Sets the ``text`` and ``icon`` to use for a particular ``alignment`` option,
replacing the default text or icon.
If ``text`` or ``icon`` is not specified, they will not be changed from the default.
.. note::
This must be called after first filtering the available alignment options via setAvailableAlignments().
%End
signals:
void changed();

View File

@ -195,6 +195,16 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
// try to find a good map to link the legend with by default
legend->setLinkedMap( findSensibleDefaultLinkedMapItem( legend ) );
if ( QApplication::isRightToLeft() )
{
// for right-to-left locales, use an appropriate default layout
legend->setSymbolAlignment( Qt::AlignRight );
legend->rstyle( QgsLegendStyle::Group ).setAlignment( Qt::AlignRight );
legend->rstyle( QgsLegendStyle::Subgroup ).setAlignment( Qt::AlignRight );
legend->rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( Qt::AlignRight );
legend->setTitleAlignment( Qt::AlignRight );
}
legend->updateLegend();
} );

View File

@ -70,7 +70,10 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
setupUi( this );
connect( mWrapCharLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mWrapCharLineEdit_textChanged );
connect( mTitleLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mTitleLineEdit_textChanged );
connect( mTitleAlignCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutLegendWidget::mTitleAlignCombo_currentIndexChanged );
connect( mTitleAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::titleAlignmentChanged );
connect( mGroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::groupAlignmentChanged );
connect( mSubgroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::subgroupAlignmentChanged );
connect( mItemAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::itemAlignmentChanged );
connect( mColumnCountSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged );
connect( mSplitLayerCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mSplitLayerCheckBox_toggled );
connect( mEqualColumnWidthCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mEqualColumnWidthCheckBox_toggled );
@ -111,6 +114,16 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
mLayerFontButton->setMode( QgsFontButton::ModeQFont );
mItemFontButton->setMode( QgsFontButton::ModeQFont );
mTitleAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
mGroupAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
mSubgroupAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
mItemAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
mArrangementCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignRight );
connect( mArrangementCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::arrangementChanged );
mArrangementCombo->customiseAlignmentDisplay( Qt::AlignLeft, tr( "Symbols on Left" ), QgsApplication::getThemeIcon( QStringLiteral( "/mIconArrangeSymbolsLeft.svg" ) ) );
mArrangementCombo->customiseAlignmentDisplay( Qt::AlignRight, tr( "Symbols on Right" ), QgsApplication::getThemeIcon( QStringLiteral( "/mIconArrangeSymbolsRight.svg" ) ) );
// setup icons
mAddToolButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
mEditPushButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) );
@ -168,11 +181,13 @@ void QgsLayoutLegendWidget::setGuiElements()
return;
}
int alignment = mLegend->titleAlignment() == Qt::AlignLeft ? 0 : mLegend->titleAlignment() == Qt::AlignHCenter ? 1 : 2;
blockAllSignals( true );
mTitleLineEdit->setText( mLegend->title() );
mTitleAlignCombo->setCurrentIndex( alignment );
whileBlocking( mTitleAlignCombo )->setCurrentAlignment( mLegend->titleAlignment() );
whileBlocking( mGroupAlignCombo )->setCurrentAlignment( mLegend->style( QgsLegendStyle::Group ).alignment() );
whileBlocking( mSubgroupAlignCombo )->setCurrentAlignment( mLegend->style( QgsLegendStyle::Subgroup ).alignment() );
whileBlocking( mItemAlignCombo )->setCurrentAlignment( mLegend->style( QgsLegendStyle::SymbolLabel ).alignment() );
whileBlocking( mArrangementCombo )->setCurrentAlignment( mLegend->symbolAlignment() );
mFilterByMapToolButton->setChecked( mLegend->legendFilterByMapEnabled() );
mColumnCountSpinBox->setValue( mLegend->columnCount() );
mSplitLayerCheckBox->setChecked( mLegend->splitLayer() );
@ -240,11 +255,11 @@ void QgsLayoutLegendWidget::mTitleLineEdit_textChanged( const QString &text )
}
}
void QgsLayoutLegendWidget::mTitleAlignCombo_currentIndexChanged( int index )
void QgsLayoutLegendWidget::titleAlignmentChanged()
{
if ( mLegend )
{
Qt::AlignmentFlag alignment = index == 0 ? Qt::AlignLeft : index == 1 ? Qt::AlignHCenter : Qt::AlignRight;
Qt::AlignmentFlag alignment = static_cast< Qt::AlignmentFlag >( static_cast< int >( mTitleAlignCombo->currentAlignment() & Qt::AlignHorizontal_Mask ) );
mLegend->beginCommand( tr( "Change Title Alignment" ) );
mLegend->setTitleAlignment( alignment );
mLegend->update();
@ -252,6 +267,51 @@ void QgsLayoutLegendWidget::mTitleAlignCombo_currentIndexChanged( int index )
}
}
void QgsLayoutLegendWidget::groupAlignmentChanged()
{
if ( mLegend )
{
mLegend->beginCommand( tr( "Change Group Alignment" ) );
mLegend->rstyle( QgsLegendStyle::Group ).setAlignment( mGroupAlignCombo->currentAlignment() );
mLegend->update();
mLegend->endCommand();
}
}
void QgsLayoutLegendWidget::subgroupAlignmentChanged()
{
if ( mLegend )
{
mLegend->beginCommand( tr( "Change Subgroup Alignment" ) );
mLegend->rstyle( QgsLegendStyle::Subgroup ).setAlignment( mSubgroupAlignCombo->currentAlignment() );
mLegend->update();
mLegend->endCommand();
}
}
void QgsLayoutLegendWidget::itemAlignmentChanged()
{
if ( mLegend )
{
mLegend->beginCommand( tr( "Change Item Alignment" ) );
mLegend->rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( mItemAlignCombo->currentAlignment() );
mLegend->update();
mLegend->endCommand();
}
}
void QgsLayoutLegendWidget::arrangementChanged()
{
if ( mLegend )
{
Qt::AlignmentFlag alignment = static_cast< Qt::AlignmentFlag >( static_cast< int >( mArrangementCombo->currentAlignment() & Qt::AlignHorizontal_Mask ) );
mLegend->beginCommand( tr( "Change Legend Arrangement" ) );
mLegend->setSymbolAlignment( alignment );
mLegend->update();
mLegend->endCommand();
}
}
void QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged( int c )
{
if ( mLegend )
@ -1053,7 +1113,7 @@ void QgsLayoutLegendWidget::setCurrentNodeStyleFromAction()
if ( !a || !mItemTreeView->currentNode() )
return;
QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), ( QgsLegendStyle::Style ) a->data().toInt() );
QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), static_cast< QgsLegendStyle::Style >( a->data().toInt() ) );
mLegend->updateFilterByMap();
}
@ -1175,7 +1235,7 @@ QMenu *QgsLayoutLegendMenuProvider::createContextMenu()
QAction *action = menu->addAction( QgsLegendStyle::styleLabel( style ), mWidget, &QgsLayoutLegendWidget::setCurrentNodeStyleFromAction );
action->setCheckable( true );
action->setChecked( currentStyle == style );
action->setData( ( int ) style );
action->setData( static_cast< int >( style ) );
}
return menu;

View File

@ -49,7 +49,6 @@ class QgsLayoutLegendWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayo
void mWrapCharLineEdit_textChanged( const QString &text );
void mTitleLineEdit_textChanged( const QString &text );
void mTitleAlignCombo_currentIndexChanged( int index );
void mColumnCountSpinBox_valueChanged( int c );
void mSplitLayerCheckBox_toggled( bool checked );
void mEqualColumnWidthCheckBox_toggled( bool checked );
@ -108,6 +107,12 @@ class QgsLayoutLegendWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayo
void layerFontChanged();
void itemFontChanged();
void titleAlignmentChanged();
void groupAlignmentChanged();
void subgroupAlignmentChanged();
void itemAlignmentChanged();
void arrangementChanged();
private:
QgsLayoutLegendWidget() = delete;
void blockAllSignals( bool b );

View File

@ -84,8 +84,27 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbol( const QgsLegendSettings &setting
return QSizeF();
if ( ctx && ctx->painter )
symbolIcon.paint( ctx->painter, ctx->point.x(), ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2,
settings.symbolSize().width(), settings.symbolSize().height() );
{
switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
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() ) );
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() ) );
break;
}
}
return settings.symbolSize();
}
@ -117,13 +136,34 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings &set
labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * ( settings.lineSpacing() + textDescent );
double labelX = 0.0, labelY = 0.0;
double labelXMin = 0.0;
double labelXMax = 0.0;
double labelY = 0.0;
if ( ctx && ctx->painter )
{
ctx->painter->setPen( settings.fontColor() );
switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
labelXMin = ctx->columnLeft + std::max( static_cast< double >( symbolSize.width() ), ctx->maxSiblingSymbolWidth )
+ settings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right )
+ settings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left );
labelXMax = ctx->columnRight;
break;
labelX = ctx->point.x() + std::max( static_cast< double >( symbolSize.width() ), ctx->labelXOffset );
labelY = ctx->point.y();
case Qt::AlignRight:
labelXMin = ctx->columnLeft;
// NOTE -- while the below calculations use the flipped margins from the style, that's only done because
// those are the only margins we expose and use for now! (and we expose them as generic margins, not side-specific
// ones) TODO when/if we expose other margin settings, these should be reversed...
labelXMax = ctx->columnRight - std::max( static_cast< double >( symbolSize.width() ), ctx->maxSiblingSymbolWidth )
- settings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right )
- settings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left );
break;
}
labelY = ctx->top;
// Vertical alignment of label with symbol
if ( labelSize.height() < symbolSize.height() )
@ -134,11 +174,27 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings &set
for ( QStringList::ConstIterator itemPart = lines.constBegin(); itemPart != lines.constEnd(); ++itemPart )
{
labelSize.rwidth() = std::max( settings.textWidthMillimeters( symbolLabelFont, *itemPart ), double( labelSize.width() ) );
const double lineWidth = settings.textWidthMillimeters( symbolLabelFont, *itemPart );
labelSize.rwidth() = std::max( lineWidth, double( labelSize.width() ) );
if ( ctx && ctx->painter )
{
settings.drawText( ctx->painter, labelX, labelY, *itemPart, symbolLabelFont );
switch ( settings.style( QgsLegendStyle::SymbolLabel ).alignment() )
{
case Qt::AlignLeft:
default:
settings.drawText( ctx->painter, labelXMin, labelY, *itemPart, symbolLabelFont );
break;
case Qt::AlignRight:
settings.drawText( ctx->painter, labelXMax - lineWidth, labelY, *itemPart, symbolLabelFont );
break;
case Qt::AlignHCenter:
settings.drawText( ctx->painter, labelXMin + ( labelXMax - labelXMin - lineWidth ) / 2.0, labelY, *itemPart, symbolLabelFont );
break;
}
if ( itemPart != ( lines.end() - 1 ) )
labelY += textDescent + settings.lineSpacing() + textHeight;
}
@ -462,8 +518,7 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
if ( ctx && ctx->painter )
{
double currentXPosition = ctx->point.x();
double currentYCoord = ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2;
double currentYCoord = ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2;
QPainter *p = ctx->painter;
//setup painter scaling to dots so that raster symbology is drawn to scale
@ -475,7 +530,18 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
p->save();
p->setRenderHint( QPainter::Antialiasing );
p->translate( currentXPosition + widthOffset, currentYCoord + heightOffset );
switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
p->translate( ctx->columnLeft + widthOffset, currentYCoord + heightOffset );
break;
case Qt::AlignRight:
p->translate( ctx->columnRight - widthOffset - width, currentYCoord + heightOffset );
break;
}
p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
if ( opacity != 255 && settings.useAdvancedEffects() )
{
@ -659,8 +725,19 @@ QSizeF QgsImageLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemCo
if ( ctx && ctx->painter )
{
ctx->painter->drawImage( QRectF( ctx->point.x(), ctx->point.y(), settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ),
switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
ctx->painter->drawImage( QRectF( ctx->columnLeft, ctx->top, settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ),
mImage, QRectF( 0, 0, mImage.width(), mImage.height() ) );
break;
case Qt::AlignRight:
ctx->painter->drawImage( QRectF( ctx->columnRight - settings.wmsLegendSize().width(), ctx->top, settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ),
mImage, QRectF( 0, 0, mImage.width(), mImage.height() ) );
break;
}
}
return settings.wmsLegendSize();
}
@ -724,8 +801,19 @@ QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings,
ctx->painter->setPen( Qt::NoPen );
}
ctx->painter->drawRect( QRectF( ctx->point.x(), ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2,
switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
ctx->painter->drawRect( QRectF( ctx->columnLeft, ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2,
settings.symbolSize().width(), settings.symbolSize().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() ) );
break;
}
}
return settings.symbolSize();
}
@ -829,9 +917,27 @@ QSizeF QgsWmsLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemCont
if ( ctx && ctx->painter )
{
ctx->painter->drawImage( QRectF( ctx->point, settings.wmsLegendSize() ),
switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
ctx->painter->drawImage( QRectF( ctx->columnLeft,
ctx->top,
settings.wmsLegendSize().width(),
settings.wmsLegendSize().height() ),
mImage,
QRectF( QPointF( 0, 0 ), mImage.size() ) );
break;
case Qt::AlignRight:
ctx->painter->drawImage( QRectF( ctx->columnRight - settings.wmsLegendSize().width(),
ctx->top,
settings.wmsLegendSize().width(),
settings.wmsLegendSize().height() ),
mImage,
QRectF( QPointF( 0, 0 ), mImage.size() ) );
break;
}
}
return settings.wmsLegendSize();
}
@ -958,7 +1064,7 @@ QgsLayerTreeModelLegendNode::ItemMetrics QgsDataDefinedSizeLegendNode::draw( con
context.setPainter( ctx->painter );
ctx->painter->save();
ctx->painter->setRenderHint( QPainter::Antialiasing );
ctx->painter->translate( ctx->point );
ctx->painter->translate( ctx->columnLeft, ctx->top );
ctx->painter->scale( 1 / context.scaleFactor(), 1 / context.scaleFactor() );
}

View File

@ -86,14 +86,56 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
struct ItemContext
{
Q_NOWARN_DEPRECATED_PUSH //because of deprecated members
ItemContext() = default;
Q_NOWARN_DEPRECATED_POP
//! Render context, if available
QgsRenderContext *context = nullptr;
//! Painter
QPainter *painter = nullptr;
//! Top-left corner of the legend item
QPointF point;
//! offset from the left side where label should start
double labelXOffset;
/**
* Top-left corner of the legend item.
* \deprecated Use top, columnLeft, columnRight instead.
*/
Q_DECL_DEPRECATED QPointF point;
/**
* Offset from the left side where label should start.
* \deprecated use columnLeft, columnRight instead.
*/
Q_DECL_DEPRECATED double labelXOffset = 0.0;
/**
* Top y-position of legend item.
* \since QGIS 3.10
*/
double top = 0.0;
/**
* Left side of current legend column. This should be used when determining
* where to render legend item content, correctly respecting the symbol and text
* alignment from the legend settings.
* \since QGIS 3.10
*/
double columnLeft = 0.0;
/**
* Right side of current legend column. This should be used when determining
* where to render legend item content, correctly respecting the symbol and text
* alignment from the legend settings.
* \since QGIS 3.10
*/
double columnRight = 0.0;
/**
* Largest symbol width, considering all other sibling legend components associated with
* the current component.
* \since QGIS 3.10
*/
double maxSiblingSymbolWidth = 0.0;
};
struct ItemMetrics

View File

@ -139,6 +139,7 @@ void QgsLayoutItemLegend::paint( QPainter *painter, const QStyleOptionGraphicsIt
attemptResize( newSize );
}
}
QgsLayoutItem::paint( painter, itemStyle, pWidget );
}
@ -366,6 +367,16 @@ void QgsLayoutItemLegend::setSymbolWidth( double w )
mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) );
}
void QgsLayoutItemLegend::setSymbolAlignment( Qt::AlignmentFlag alignment )
{
mSettings.setSymbolAlignment( alignment );
}
Qt::AlignmentFlag QgsLayoutItemLegend::symbolAlignment() const
{
return mSettings.symbolAlignment();
}
double QgsLayoutItemLegend::symbolHeight() const
{
return mSettings.symbolSize().height();
@ -488,6 +499,10 @@ bool QgsLayoutItemLegend::writePropertiesToElement( QDomElement &legendElem, QDo
legendElem.setAttribute( QStringLiteral( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
legendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) );
legendElem.setAttribute( QStringLiteral( "symbolAlignment" ), mSettings.symbolAlignment() );
legendElem.setAttribute( QStringLiteral( "symbolAlignment" ), mSettings.symbolAlignment() );
legendElem.setAttribute( QStringLiteral( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );
legendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterStroke() );
@ -577,6 +592,8 @@ bool QgsLayoutItemLegend::readPropertiesFromElement( const QDomElement &itemElem
mSettings.setColumnSpace( itemElem.attribute( QStringLiteral( "columnSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
mSettings.setSymbolSize( QSizeF( itemElem.attribute( QStringLiteral( "symbolWidth" ), QStringLiteral( "7.0" ) ).toDouble(), itemElem.attribute( QStringLiteral( "symbolHeight" ), QStringLiteral( "14.0" ) ).toDouble() ) );
mSettings.setSymbolAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "symbolAlignment" ), QString::number( Qt::AlignLeft ) ).toInt() ) );
mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( QStringLiteral( "wmsLegendWidth" ), QStringLiteral( "50" ) ).toDouble(), itemElem.attribute( QStringLiteral( "wmsLegendHeight" ), QStringLiteral( "25" ) ).toDouble() ) );
mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), QStringLiteral( "1.0" ) ).toDouble() );
@ -776,7 +793,7 @@ void QgsLayoutItemLegend::doUpdateFilterByMap()
if ( mMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) )
{
int dpi = mLayout->renderContext().dpi();
double dpi = mLayout->renderContext().dpi();
QgsRectangle requestRectangle = mMap->requestedExtent();

View File

@ -266,6 +266,26 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
*/
void setSymbolWidth( double width );
/**
* Sets the \a alignment for placement of legend symbols.
*
* Only Qt::AlignLeft or Qt::AlignRight are supported values.
*
* \see symbolAlignment()
* \since QGIS 3.10.0
*/
void setSymbolAlignment( Qt::AlignmentFlag alignment );
/**
* Returns the alignment for placement of legend symbols.
*
* Only Qt::AlignLeft or Qt::AlignRight are supported values.
*
* \see setSymbolAlignment()
* \since QGIS 3.10.0
*/
Qt::AlignmentFlag symbolAlignment() const;
/**
* Returns the legend symbol height.
* \see setSymbolHeight()

View File

@ -129,19 +129,38 @@ QSizeF QgsLegendRenderer::paintAndDetermineSizeInternal( QgsRenderContext *conte
if ( !rootGroup )
return size;
QList<Atom> atomList = createAtomList( rootGroup, mSettings.splitLayer() );
QList<LegendComponentGroup> atomList = createComponentGroupList( rootGroup, mSettings.splitLayer() );
setColumns( atomList );
qreal maxColumnWidth = 0;
// another iteration -- this one is required to calculate the maximum item width for each
// column. Unfortunately, we can't trust the atom widths at this stage, as they are minimal widths
// only. When actually rendering a symbol node, the text is aligned according to the WIDEST
// symbol in a column. So that means we can't possibly determine the exact size of legend components
// until now. BUUUUUUUUUUUUT. Because everything sucks, we can't even start the actual render of items
// at the same time we calculate this -- legend items REQUIRE the REAL width of the columns in order to
// correctly align right or center-aligned symbols/text. Bah -- A triple iteration it is!
QMap< int, double > maxColumnWidths;
qreal maxEqualColumnWidth = 0;
// temporarily remove painter from context -- we don't need to actually draw anything yet. But we DO need
// to send the full render context so that an expression context is available during the size calculation
QPainter *prevPainter = context ? context->painter() : nullptr;
if ( context )
context->setPainter( nullptr );
for ( const LegendComponentGroup &atom : qgis::as_const( atomList ) )
{
const QSizeF actualSize = drawGroup( atom, context, ColumnContext() );
if ( mSettings.equalColumnWidth() )
{
const auto constAtomList = atomList;
for ( const Atom &atom : constAtomList )
maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
}
else
{
maxColumnWidth = std::max( atom.size.width(), maxColumnWidth );
maxColumnWidths[ atom.column ] = std::max( actualSize.width(), maxColumnWidths.value( atom.column, 0 ) );
}
}
if ( context )
context->setPainter( prevPainter );
//calculate size of title
QSizeF titleSize = drawTitle();
@ -149,48 +168,46 @@ QSizeF QgsLegendRenderer::paintAndDetermineSizeInternal( QgsRenderContext *conte
titleSize.rwidth() += mSettings.boxSpace() * 2.0;
double columnTop = mSettings.boxSpace() + titleSize.height() + mSettings.style( QgsLegendStyle::Title ).margin( QgsLegendStyle::Bottom );
QPointF point( mSettings.boxSpace(), columnTop );
bool firstInColumn = true;
double columnMaxHeight = 0;
qreal columnWidth = 0;
int column = 0;
const auto constAtomList = atomList;
for ( const Atom &atom : constAtomList )
int column = -1;
ColumnContext columnContext;
columnContext.left = mSettings.boxSpace();
columnContext.right = mLegendSize.width() - mSettings.boxSpace();
double currentY = columnTop;
for ( const LegendComponentGroup &atom : qgis::as_const( atomList ) )
{
if ( atom.column > column )
{
// Switch to next column
if ( mSettings.equalColumnWidth() )
{
point.rx() += mSettings.columnSpace() + maxColumnWidth;
}
else
{
point.rx() += mSettings.columnSpace() + columnWidth;
}
point.ry() = columnTop;
columnWidth = 0;
columnContext.left = atom.column > 0 ? columnContext.right + mSettings.columnSpace() : mSettings.boxSpace();
columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( atom.column );
columnContext.right = columnContext.left + columnWidth;
currentY = columnTop;
column++;
firstInColumn = true;
}
if ( !firstInColumn )
{
point.ry() += spaceAboveAtom( atom );
currentY += spaceAboveGroup( atom );
}
QSizeF atomSize = context ? drawAtom( atom, context, point )
: drawAtom( atom, painter, point );
columnWidth = std::max( atomSize.width(), columnWidth );
if ( context )
drawGroup( atom, context, columnContext, currentY );
else if ( painter )
drawGroup( atom, columnContext, painter, currentY );
point.ry() += atom.size.height();
columnMaxHeight = std::max( point.y() - columnTop, columnMaxHeight );
currentY += atom.size.height();
columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
firstInColumn = false;
}
point.rx() += columnWidth + mSettings.boxSpace();
const double totalWidth = columnContext.right + mSettings.boxSpace();
size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
size.rwidth() = point.x();
size.rwidth() = totalWidth;
if ( !mSettings.title().isEmpty() )
{
size.rwidth() = std::max( titleSize.width(), size.width() );
@ -207,32 +224,38 @@ QSizeF QgsLegendRenderer::paintAndDetermineSizeInternal( QgsRenderContext *conte
// Now we have set the correct total item width and can draw the title centered
if ( !mSettings.title().isEmpty() )
{
if ( mSettings.titleAlignment() == Qt::AlignLeft )
{
point.rx() = mSettings.boxSpace();
}
else if ( mSettings.titleAlignment() == Qt::AlignHCenter )
{
point.rx() = size.width() / 2;
}
else
{
point.rx() = size.width() - mSettings.boxSpace();
}
point.ry() = mSettings.boxSpace();
if ( context )
drawTitle( context, point, mSettings.titleAlignment(), size.width() );
drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
else
drawTitle( painter, point, mSettings.titleAlignment(), size.width() );
drawTitle( painter, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
}
return size;
}
QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGroup *parentGroup, bool splitLayer )
void QgsLegendRenderer::widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, const double legendWidth, double &textBoxWidth, double &textBoxLeft )
{
QList<Atom> atoms;
switch ( halignment )
{
default:
textBoxLeft = mSettings.boxSpace();
textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
break;
case Qt::AlignHCenter:
{
// not sure on this logic, I just moved it -- don't blame me for it being totally obscure!
const double centerX = legendWidth / 2;
textBoxWidth = ( std::min( static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
textBoxLeft = centerX - textBoxWidth / 2.;
break;
}
}
}
QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList( QgsLayerTreeGroup *parentGroup, bool splitLayer )
{
QList<LegendComponentGroup> atoms;
if ( !parentGroup ) return atoms;
@ -244,29 +267,29 @@ QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGr
QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
// Group subitems
QList<Atom> groupAtoms = createAtomList( nodeGroup, splitLayer );
QList<LegendComponentGroup> groupAtoms = createComponentGroupList( nodeGroup, splitLayer );
bool hasSubItems = !groupAtoms.empty();
if ( nodeLegendStyle( nodeGroup ) != QgsLegendStyle::Hidden )
{
Nucleon nucleon;
LegendComponent nucleon;
nucleon.item = node;
nucleon.size = drawGroupTitle( nodeGroup );
if ( !groupAtoms.isEmpty() )
{
// Add internal space between this group title and the next nucleon
groupAtoms[0].size.rheight() += spaceAboveAtom( groupAtoms[0] );
groupAtoms[0].size.rheight() += spaceAboveGroup( groupAtoms[0] );
// Prepend this group title to the first atom
groupAtoms[0].nucleons.prepend( nucleon );
groupAtoms[0].components.prepend( nucleon );
groupAtoms[0].size.rheight() += nucleon.size.height();
groupAtoms[0].size.rwidth() = std::max( nucleon.size.width(), groupAtoms[0].size.width() );
}
else
{
// no subitems, append new atom
Atom atom;
atom.nucleons.append( nucleon );
LegendComponentGroup atom;
atom.components.append( nucleon );
atom.size.rwidth() += nucleon.size.width();
atom.size.rheight() += nucleon.size.height();
atom.size.rwidth() = std::max( nucleon.size.width(), atom.size.width() );
@ -284,14 +307,14 @@ QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGr
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
Atom atom;
LegendComponentGroup atom;
if ( nodeLegendStyle( nodeLayer ) != QgsLegendStyle::Hidden )
{
Nucleon nucleon;
LegendComponent nucleon;
nucleon.item = node;
nucleon.size = drawLayerTitle( nodeLayer );
atom.nucleons.append( nucleon );
atom.components.append( nucleon );
atom.size.rwidth() = nucleon.size.width();
atom.size.rheight() = nucleon.size.height();
}
@ -304,13 +327,13 @@ QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGr
if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
continue;
QList<Atom> layerAtoms;
QList<LegendComponentGroup> layerAtoms;
for ( int j = 0; j < legendNodes.count(); j++ )
{
QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
Nucleon symbolNucleon = drawSymbolItem( legendNode );
LegendComponent symbolNucleon = drawSymbolItem( legendNode );
if ( !mSettings.splitLayer() || j == 0 )
{
@ -318,18 +341,18 @@ QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGr
// the width is not correct at this moment, we must align all symbol labels
atom.size.rwidth() = std::max( symbolNucleon.size.width(), atom.size.width() );
// Add symbol space only if there is already title or another item above
if ( !atom.nucleons.isEmpty() )
if ( !atom.components.isEmpty() )
{
// TODO: for now we keep Symbol and SymbolLabel Top margin in sync
atom.size.rheight() += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
}
atom.size.rheight() += symbolNucleon.size.height();
atom.nucleons.append( symbolNucleon );
atom.components.append( symbolNucleon );
}
else
{
Atom symbolAtom;
symbolAtom.nucleons.append( symbolNucleon );
LegendComponentGroup symbolAtom;
symbolAtom.components.append( symbolNucleon );
symbolAtom.size.rwidth() = symbolNucleon.size.width();
symbolAtom.size.rheight() = symbolNucleon.size.height();
layerAtoms.append( symbolAtom );
@ -344,7 +367,7 @@ QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGr
}
void QgsLegendRenderer::setColumns( QList<Atom> &atomList )
void QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &atomList )
{
if ( mSettings.columnCount() == 0 ) return;
@ -352,9 +375,9 @@ void QgsLegendRenderer::setColumns( QList<Atom> &atomList )
double totalHeight = 0;
qreal maxAtomHeight = 0;
const auto constAtomList = atomList;
for ( const Atom &atom : constAtomList )
for ( const LegendComponentGroup &atom : constAtomList )
{
totalHeight += spaceAboveAtom( atom );
totalHeight += spaceAboveGroup( atom );
totalHeight += atom.size.height();
maxAtomHeight = std::max( atom.size.height(), maxAtomHeight );
}
@ -375,10 +398,10 @@ void QgsLegendRenderer::setColumns( QList<Atom> &atomList )
// Recalc average height for remaining columns including current
double avgColumnHeight = ( totalHeight - closedColumnsHeight ) / ( mSettings.columnCount() - currentColumn );
Atom atom = atomList.at( i );
LegendComponentGroup atom = atomList.at( i );
double currentHeight = currentColumnHeight;
if ( currentColumnAtomCount > 0 )
currentHeight += spaceAboveAtom( atom );
currentHeight += spaceAboveGroup( atom );
currentHeight += atom.size.height();
bool canCreateNewColumn = ( currentColumnAtomCount > 0 ) // do not leave empty column
@ -414,39 +437,40 @@ void QgsLegendRenderer::setColumns( QList<Atom> &atomList )
QMap<QString, qreal> maxSymbolWidth;
for ( int i = 0; i < atomList.size(); i++ )
{
Atom &atom = atomList[i];
for ( int j = 0; j < atom.nucleons.size(); j++ )
LegendComponentGroup &atom = atomList[i];
for ( int j = 0; j < atom.components.size(); j++ )
{
if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( atom.nucleons.at( j ).item ) )
if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( atom.components.at( j ).item ) )
{
QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( atom.column );
maxSymbolWidth[key] = std::max( atom.nucleons.at( j ).symbolSize.width(), maxSymbolWidth[key] );
maxSymbolWidth[key] = std::max( atom.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
}
}
}
for ( int i = 0; i < atomList.size(); i++ )
{
Atom &atom = atomList[i];
for ( int j = 0; j < atom.nucleons.size(); j++ )
LegendComponentGroup &atom = atomList[i];
for ( int j = 0; j < atom.components.size(); j++ )
{
if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( atom.nucleons.at( j ).item ) )
if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( atom.components.at( j ).item ) )
{
QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( atom.column );
double space = mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right ) +
mSettings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left );
atom.nucleons[j].labelXOffset = maxSymbolWidth[key] + space;
atom.nucleons[j].size.rwidth() = maxSymbolWidth[key] + space + atom.nucleons.at( j ).labelSize.width();
atom.components[j].labelXOffset = maxSymbolWidth[key] + space;
atom.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
atom.components[j].size.rwidth() = maxSymbolWidth[key] + space + atom.components.at( j ).labelSize.width();
}
}
}
}
QSizeF QgsLegendRenderer::drawTitle( QPainter *painter, QPointF point, Qt::AlignmentFlag halignment, double legendWidth )
QSizeF QgsLegendRenderer::drawTitle( QPainter *painter, double top, Qt::AlignmentFlag halignment, double legendWidth )
{
return drawTitleInternal( nullptr, painter, point, halignment, legendWidth );
return drawTitleInternal( nullptr, painter, top, halignment, legendWidth );
}
QSizeF QgsLegendRenderer::drawTitleInternal( QgsRenderContext *context, QPainter *painter, QPointF point, Qt::AlignmentFlag halignment, double legendWidth )
QSizeF QgsLegendRenderer::drawTitleInternal( QgsRenderContext *context, QPainter *painter, const double top, Qt::AlignmentFlag halignment, double legendWidth )
{
QSizeF size( 0, 0 );
if ( mSettings.title().isEmpty() )
@ -455,7 +479,7 @@ QSizeF QgsLegendRenderer::drawTitleInternal( QgsRenderContext *context, QPainter
}
QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
double y = point.y();
double y = top;
if ( context && context->painter() )
{
@ -469,22 +493,7 @@ QSizeF QgsLegendRenderer::drawTitleInternal( QgsRenderContext *context, QPainter
//calculate width and left pos of rectangle to draw text into
double textBoxWidth;
double textBoxLeft;
switch ( halignment )
{
case Qt::AlignHCenter:
textBoxWidth = ( std::min( static_cast< double >( point.x() ), legendWidth - point.x() ) - mSettings.boxSpace() ) * 2.0;
textBoxLeft = point.x() - textBoxWidth / 2.;
break;
case Qt::AlignRight:
textBoxLeft = mSettings.boxSpace();
textBoxWidth = point.x() - mSettings.boxSpace();
break;
case Qt::AlignLeft:
default:
textBoxLeft = point.x();
textBoxWidth = legendWidth - point.x() - mSettings.boxSpace();
break;
}
widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
QFont titleFont = mSettings.style( QgsLegendStyle::Title ).font();
@ -515,17 +524,17 @@ QSizeF QgsLegendRenderer::drawTitleInternal( QgsRenderContext *context, QPainter
y += mSettings.lineSpacing();
}
}
size.rheight() = y - point.y();
size.rheight() = y - top;
return size;
}
double QgsLegendRenderer::spaceAboveAtom( const Atom &atom )
double QgsLegendRenderer::spaceAboveGroup( const LegendComponentGroup &atom )
{
if ( atom.nucleons.isEmpty() ) return 0;
if ( atom.components.isEmpty() ) return 0;
Nucleon nucleon = atom.nucleons.first();
LegendComponent nucleon = atom.components.first();
if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( nucleon.item ) )
{
@ -546,16 +555,17 @@ double QgsLegendRenderer::spaceAboveAtom( const Atom &atom )
// Draw atom and expand its size (using actual nucleons labelXOffset)
QSizeF QgsLegendRenderer::drawAtom( const Atom &atom, QPainter *painter, QPointF point )
QSizeF QgsLegendRenderer::drawGroup( const LegendComponentGroup &atom, const ColumnContext &columnContext, QPainter *painter, double top )
{
return drawAtomInternal( atom, nullptr, painter, point );
return drawGroupInternal( atom, nullptr, columnContext, painter, top );
}
QSizeF QgsLegendRenderer::drawAtomInternal( const Atom &atom, QgsRenderContext *context, QPainter *painter, QPointF point )
QSizeF QgsLegendRenderer::drawGroupInternal( const LegendComponentGroup &atom, QgsRenderContext *context, const ColumnContext &columnContext, QPainter *painter, const double top )
{
bool first = true;
QSizeF size = QSizeF( atom.size );
for ( const Nucleon &nucleon : qgis::as_const( atom.nucleons ) )
double currentY = top;
for ( const LegendComponent &nucleon : qgis::as_const( atom.components ) )
{
if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( nucleon.item ) )
{
@ -564,12 +574,12 @@ QSizeF QgsLegendRenderer::drawAtomInternal( const Atom &atom, QgsRenderContext *
{
if ( !first )
{
point.ry() += mSettings.style( s ).margin( QgsLegendStyle::Top );
currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
}
if ( context )
drawGroupTitle( groupItem, context, point );
drawGroupTitle( groupItem, context, columnContext, currentY );
else
drawGroupTitle( groupItem, painter, point );
drawGroupTitle( groupItem, columnContext, painter, currentY );
}
}
else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( nucleon.item ) )
@ -579,38 +589,38 @@ QSizeF QgsLegendRenderer::drawAtomInternal( const Atom &atom, QgsRenderContext *
{
if ( !first )
{
point.ry() += mSettings.style( s ).margin( QgsLegendStyle::Top );
currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
}
if ( context )
drawLayerTitle( layerItem, context, point );
drawLayerTitle( layerItem, context, columnContext, currentY );
else
drawLayerTitle( layerItem, painter, point );
drawLayerTitle( layerItem, columnContext, painter, currentY );
}
}
else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( nucleon.item ) )
{
if ( !first )
{
point.ry() += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
currentY += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
}
Nucleon symbolNucleon = context ? drawSymbolItem( legendNode, context, point, nucleon.labelXOffset )
: drawSymbolItem( legendNode, painter, point, nucleon.labelXOffset );
LegendComponent symbolNucleon = context ? drawSymbolItem( legendNode, context, columnContext, currentY, nucleon.maxSiblingSymbolWidth )
: drawSymbolItem( legendNode, columnContext, painter, currentY, nucleon.maxSiblingSymbolWidth );
// expand width, it may be wider because of labelXOffset
size.rwidth() = std::max( symbolNucleon.size.width(), size.width() );
}
point.ry() += nucleon.size.height();
currentY += nucleon.size.height();
first = false;
}
return size;
}
QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QPainter *painter, QPointF point, double labelXOffset )
QgsLegendRenderer::LegendComponent QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, const ColumnContext &columnContext, QPainter *painter, double top, double maxSiblingSymbolWidth )
{
return drawSymbolItemInternal( symbolItem, nullptr, painter, point, labelXOffset );
return drawSymbolItemInternal( symbolItem, columnContext, nullptr, painter, top, maxSiblingSymbolWidth );
}
QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItemInternal( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *context, QPainter *painter, QPointF point, double labelXOffset )
QgsLegendRenderer::LegendComponent QgsLegendRenderer::drawSymbolItemInternal( QgsLayerTreeModelLegendNode *symbolItem, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, double top, double maxSiblingSymbolWidth )
{
QgsLayerTreeModelLegendNode::ItemContext ctx;
ctx.context = context;
@ -624,8 +634,15 @@ QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItemInternal( QgsLayerTr
}
ctx.painter = context ? context->painter() : painter;
ctx.point = point;
ctx.labelXOffset = labelXOffset;
Q_NOWARN_DEPRECATED_PUSH
ctx.point = QPointF( columnContext.left, top );
ctx.labelXOffset = maxSiblingSymbolWidth;
Q_NOWARN_DEPRECATED_POP
ctx.top = top;
ctx.columnLeft = columnContext.left;
ctx.columnRight = columnContext.right;
ctx.maxSiblingSymbolWidth = maxSiblingSymbolWidth;
QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->draw( mSettings, context ? &ctx
: ( painter ? &ctx : nullptr ) );
@ -633,23 +650,30 @@ QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItemInternal( QgsLayerTr
if ( layerScope )
delete context->expressionContext().popScope();
Nucleon nucleon;
LegendComponent nucleon;
nucleon.item = symbolItem;
nucleon.symbolSize = im.symbolSize;
nucleon.labelSize = im.labelSize;
//QgsDebugMsg( QStringLiteral( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ));
double width = std::max( static_cast< double >( im.symbolSize.width() ), labelXOffset ) + im.labelSize.width();
// NOTE -- we hard code left/right margins below, because those are the only ones exposed for use currently.
// ideally we could (should?) expose all these margins as settings, and then adapt the below to respect the current symbol/text alignment
// and consider the correct margin sides...
double width = std::max( static_cast< double >( im.symbolSize.width() ), maxSiblingSymbolWidth )
+ mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right )
+ mSettings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left )
+ im.labelSize.width();
double height = std::max( im.symbolSize.height(), im.labelSize.height() );
nucleon.size = QSizeF( width, height );
return nucleon;
}
QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QPainter *painter, QPointF point )
QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, const ColumnContext &columnContext, QPainter *painter, double top )
{
return drawLayerTitleInternal( nodeLayer, nullptr, painter, point );
return drawLayerTitleInternal( nodeLayer, columnContext, nullptr, painter, top );
}
QSizeF QgsLegendRenderer::drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *context, QPainter *painter, QPointF point )
QSizeF QgsLegendRenderer::drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, const double top )
{
QSizeF size( 0, 0 );
QModelIndex idx = mLegendModel->node2index( nodeLayer );
@ -658,7 +682,7 @@ QSizeF QgsLegendRenderer::drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer,
if ( mLegendModel->data( idx, Qt::DisplayRole ).toString().isEmpty() )
return size;
double y = point.y();
double y = top;
if ( context && context->painter() )
context->painter()->setPen( mSettings.layerFontColor() );
@ -678,21 +702,32 @@ QSizeF QgsLegendRenderer::drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer,
const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(),
context ? context->expressionContext() : tempContext );
int i = 0;
for ( QStringList::ConstIterator layerItemPart = lines.constBegin(); layerItemPart != lines.constEnd(); ++layerItemPart )
{
y += mSettings.fontAscentMillimeters( layerFont );
if ( context && context->painter() )
mSettings.drawText( context->painter(), point.x(), y, *layerItemPart, layerFont );
if ( painter )
mSettings.drawText( painter, point.x(), y, *layerItemPart, layerFont );
if ( QPainter *destPainter = context && context->painter() ? context->painter() : painter )
{
double x = columnContext.left;
if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() != Qt::AlignLeft )
{
const double labelWidth = mSettings.textWidthMillimeters( layerFont, *layerItemPart );
if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignRight )
x = columnContext.right - labelWidth;
else if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter )
x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
}
mSettings.drawText( destPainter, x, y, *layerItemPart, layerFont );
}
qreal width = mSettings.textWidthMillimeters( layerFont, *layerItemPart );
size.rwidth() = std::max( width, size.width() );
if ( layerItemPart != ( lines.end() - 1 ) )
{
y += mSettings.lineSpacing();
}
i++;
}
size.rheight() = y - point.y();
size.rheight() = y - top;
size.rheight() += mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Side::Bottom );
if ( layerScope )
@ -701,17 +736,17 @@ QSizeF QgsLegendRenderer::drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer,
return size;
}
QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QPainter *painter, QPointF point )
QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, const ColumnContext &columnContext, QPainter *painter, double top )
{
return drawGroupTitleInternal( nodeGroup, nullptr, painter, point );
return drawGroupTitleInternal( nodeGroup, columnContext, nullptr, painter, top );
}
QSizeF QgsLegendRenderer::drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *context, QPainter *painter, QPointF point )
QSizeF QgsLegendRenderer::drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, const double top )
{
QSizeF size( 0, 0 );
QModelIndex idx = mLegendModel->node2index( nodeGroup );
double y = point.y();
double y = top;
if ( context && context->painter() )
context->painter()->setPen( mSettings.fontColor() );
@ -727,10 +762,20 @@ QSizeF QgsLegendRenderer::drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup,
for ( QStringList::ConstIterator groupPart = lines.constBegin(); groupPart != lines.constEnd(); ++groupPart )
{
y += mSettings.fontAscentMillimeters( groupFont );
if ( context && context->painter() )
mSettings.drawText( context->painter(), point.x(), y, *groupPart, groupFont );
else if ( painter )
mSettings.drawText( painter, point.x(), y, *groupPart, groupFont );
if ( QPainter *destPainter = context && context->painter() ? context->painter() : painter )
{
double x = columnContext.left;
if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() != Qt::AlignLeft )
{
const double labelWidth = mSettings.textWidthMillimeters( groupFont, *groupPart );
if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignRight )
x = columnContext.right - labelWidth;
else if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter )
x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
}
mSettings.drawText( destPainter, x, y, *groupPart, groupFont );
}
qreal width = mSettings.textWidthMillimeters( groupFont, *groupPart );
size.rwidth() = std::max( width, size.width() );
if ( groupPart != ( lines.end() - 1 ) )
@ -738,7 +783,7 @@ QSizeF QgsLegendRenderer::drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup,
y += mSettings.lineSpacing();
}
}
size.rheight() = y - point.y();
size.rheight() = y - top;
return size;
}
@ -794,29 +839,29 @@ void QgsLegendRenderer::setNodeLegendStyle( QgsLayerTreeNode *node, QgsLegendSty
node->removeCustomProperty( QStringLiteral( "legend/title-style" ) );
}
QSizeF QgsLegendRenderer::drawTitle( QgsRenderContext *rendercontext, QPointF point, Qt::AlignmentFlag halignment, double legendWidth )
QSizeF QgsLegendRenderer::drawTitle( QgsRenderContext *rendercontext, double top, Qt::AlignmentFlag halignment, double legendWidth )
{
return drawTitleInternal( rendercontext, nullptr, point, halignment, legendWidth );
return drawTitleInternal( rendercontext, nullptr, top, halignment, legendWidth );
}
QSizeF QgsLegendRenderer::drawAtom( const Atom &atom, QgsRenderContext *rendercontext, QPointF point )
QSizeF QgsLegendRenderer::drawGroup( const LegendComponentGroup &atom, QgsRenderContext *rendercontext, const ColumnContext &columnContext, double top )
{
return drawAtomInternal( atom, rendercontext, nullptr, point );
return drawGroupInternal( atom, rendercontext, columnContext, nullptr, top );
}
QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *rendercontext, QPointF point, double labelXOffset )
QgsLegendRenderer::LegendComponent QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *rendercontext, const ColumnContext &columnContext, double top, double maxSiblingSymbolWidth )
{
return drawSymbolItemInternal( symbolItem, rendercontext, nullptr, point, labelXOffset );
return drawSymbolItemInternal( symbolItem, columnContext, rendercontext, nullptr, top, maxSiblingSymbolWidth );
}
QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *rendercontext, QPointF point )
QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *rendercontext, const ColumnContext &columnContext, double top )
{
return drawLayerTitleInternal( nodeLayer, rendercontext, nullptr, point );
return drawLayerTitleInternal( nodeLayer, columnContext, rendercontext, nullptr, top );
}
QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *rendercontext, QPointF point )
QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *rendercontext, const ColumnContext &columnContext, double top )
{
return drawGroupTitleInternal( nodeGroup, rendercontext, nullptr, point );
return drawGroupTitleInternal( nodeGroup, columnContext, rendercontext, nullptr, top );
}
void QgsLegendRenderer::drawLegend( QgsRenderContext &context )

View File

@ -119,17 +119,17 @@ class CORE_EXPORT QgsLegendRenderer
#ifndef SIP_RUN
/**
* A legend Nucleon is either a group title, a layer title or a layer child item.
* A legend component is either a group title, a layer title or a layer child item.
*
* E.g. a layer title nucleon is just the layer's title, it does not
* include all of that layer's subitems. Similarly, a group's title nucleon is just
* E.g. a layer title component is just the layer's title, it does not
* include all of that layer's subitems. Similarly, a group's title component is just
* the group title, and does not include the actual content of that group.
*/
class Nucleon
class LegendComponent
{
public:
//! Constructor for Nuclean
Nucleon() = default;
LegendComponent() = default;
QObject *item = nullptr;
@ -139,7 +139,7 @@ class CORE_EXPORT QgsLegendRenderer
//! Label size, not including any preset padding space around the label
QSizeF labelSize;
//! Nucleon size
//! Component size
QSizeF size;
/**
@ -149,12 +149,18 @@ class CORE_EXPORT QgsLegendRenderer
* within the same legend column.
*/
double labelXOffset = 0.0;
/**
* Largest symbol width, considering all other sibling components associated with
* this component.
*/
double maxSiblingSymbolWidth = 0.0;
};
/**
* An Atom is indivisible set of legend Nucleons (i.e. it is indivisible into more columns).
* An component group is an indivisible set of legend components (i.e. it is indivisible into more columns).
*
* An Atom may consist of one or more Nucleon(s), depending on the layer splitting mode:
* A group may consist of one or more component(s), depending on the layer splitting mode:
*
* 1) no layer split: [group_title ...] layer_title layer_item [layer_item ...]
* 2) layer split: [group_title ...] layer_title layer_item
@ -165,20 +171,38 @@ class CORE_EXPORT QgsLegendRenderer
* and it would not be logical to leave a group or layer title at the bottom of a column,
* separated from the actual content of that group or layer.
*/
class Atom
class LegendComponentGroup
{
public:
//! List of child Nucleons belonging to this Atom.
QList<Nucleon> nucleons;
//! List of child components belonging to this group.
QList<LegendComponent> components;
//! Atom size, including internal spacing between Nucleons, but excluding any padding space around the Atom itself.
//! Group size, including internal spacing between components, but excluding any padding space around the group itself.
QSizeF size = QSizeF( 0, 0 );
//! Corresponding column index
int column = 0;
};
/**
* Contains contextual information about the current column being rendered
*/
class ColumnContext
{
public:
ColumnContext()
: left( 0 )
, right( 0 )
{}
//! Left edge of column
double left = 0;
//! Right edge of column
double right = 0;
};
/**
* Draws the legend and returns the actual size of the legend.
*
@ -188,15 +212,15 @@ class CORE_EXPORT QgsLegendRenderer
QSizeF paintAndDetermineSize( QPainter *painter = nullptr );
/**
* Returns a list of Atoms for the specified \a parentGroup, respecting the current layer's
* Returns a list of component groups for the specified \a parentGroup, respecting the current layer's
* splitting settings.
*/
QList<Atom> createAtomList( QgsLayerTreeGroup *parentGroup, bool splitLayer );
QList<LegendComponentGroup> createComponentGroupList( QgsLayerTreeGroup *parentGroup, bool splitLayer );
/**
* Divides a list of Atoms into columns, and sets the column index for each atom in the list.
* Divides a list of component groups into columns, and sets the column index for each group in the list.
*/
void setColumns( QList<Atom> &atomList );
void setColumns( QList<LegendComponentGroup> &groupList );
/**
* Draws a title in the legend using the title font and the specified alignment settings.
@ -205,25 +229,25 @@ class CORE_EXPORT QgsLegendRenderer
*
* If \a painter is NULLPTR, no painting will be attempted, but the required size will still be calculated and returned.
*/
QSizeF drawTitle( QPainter *painter = nullptr, QPointF point = QPointF(), Qt::AlignmentFlag halignment = Qt::AlignLeft, double legendWidth = 0 );
QSizeF drawTitle( QPainter *painter = nullptr, double top = 0, Qt::AlignmentFlag halignment = Qt::AlignLeft, double legendWidth = 0 );
/**
* Returns the calculated padding space required above the given \a atom.
* Returns the calculated padding space required above the given component \a group.
*/
double spaceAboveAtom( const Atom &atom );
double spaceAboveGroup( const LegendComponentGroup &group );
/**
* Draws an \a atom and return its actual size.
* Draws a component \a group and return its actual size.
*
* The \a atom is drawn with the space above it, so that the first atoms in column are all
* The \a group is drawn with the space above it, so that the first groups in a column are all
* aligned to the same line regardless of their style top spacing.
*/
QSizeF drawAtom( const Atom &atom, QPainter *painter = nullptr, QPointF point = QPointF() );
QSizeF drawGroup( const LegendComponentGroup &group, const ColumnContext &columnContext, QPainter *painter = nullptr, double top = 0 );
/**
* Draws the symbol of a given symbol QgsLayerTreeModelLegendNode.
*/
Nucleon drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QPainter *painter = nullptr, QPointF point = QPointF(), double labelXOffset = 0 );
LegendComponent drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, const ColumnContext &columnContext = ColumnContext(), QPainter *painter = nullptr, double top = 0, double maxSiblingSymbolWidth = 0 );
/**
* Draws the title of a layer, given a QgsLayerTreeLayer, and a destination \a painter.
@ -232,13 +256,13 @@ class CORE_EXPORT QgsLegendRenderer
*
* The \a painter may be NULLPTR, in which case on the size is calculated and no painting is attempted.
*/
QSizeF drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QPainter *painter = nullptr, QPointF point = QPointF() );
QSizeF drawLayerTitle( QgsLayerTreeLayer *nodeLayer, const ColumnContext &columnContext = ColumnContext(), QPainter *painter = nullptr, double top = 0 );
/**
* Draws a group item.
* Returns the size of the title.
*/
QSizeF drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QPainter *painter = nullptr, QPointF point = QPointF() );
QSizeF drawGroupTitle( QgsLayerTreeGroup *nodeGroup, const ColumnContext &columnContext = ColumnContext(), QPainter *painter = nullptr, double top = 0 );
/**
* Renders a group item in a \a json object.
@ -262,22 +286,22 @@ class CORE_EXPORT QgsLegendRenderer
*
* If \a context is NULLPTR, no painting will be attempted, but the required size will still be calculated and returned.
*/
QSizeF drawTitle( QgsRenderContext *context, QPointF point = QPointF(), Qt::AlignmentFlag halignment = Qt::AlignLeft, double legendWidth = 0 );
QSizeF drawTitle( QgsRenderContext *context, double top, Qt::AlignmentFlag halignment = Qt::AlignLeft, double legendWidth = 0 );
/**
* Draws an \a atom and return its actual size, using the specified render \a context.
* Draws an \a group and return its actual size, using the specified render \a context.
*
* The \a atom is drawn with the space above it, so that the first atoms in column are all
* The \a group is drawn with the space above it, so that the first groups in a column are all
* aligned to the same line regardless of their style top spacing.
*
* If \a context is NULLPTR, no painting will be attempted, but the required size will still be calculated and returned.
*/
QSizeF drawAtom( const Atom &atom, QgsRenderContext *rendercontext, QPointF point = QPointF() );
QSizeF drawGroup( const LegendComponentGroup &group, QgsRenderContext *rendercontext, const ColumnContext &columnContext, double top = 0 );
/**
* Draws the symbol of a given symbol QgsLayerTreeModelLegendNode, using the specified render \a context.
*/
Nucleon drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *context, QPointF point = QPointF(), double labelXOffset = 0 );
LegendComponent drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *context, const ColumnContext &columnContext, double top, double maxSiblingSymbolWidth = 0 );
/**
* Draws the title of a layer, given a QgsLayerTreeLayer, and a destination render \a context.
@ -286,21 +310,20 @@ class CORE_EXPORT QgsLegendRenderer
*
* The \a context may be NULLPTR, in which case on the size is calculated and no painting is attempted.
*/
QSizeF drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *context, QPointF point = QPointF() );
QSizeF drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *context, const ColumnContext &columnContext, double top = 0 );
/**
* Draws a group's title, using the specified render \a context.
*
* Returns the size of the title.
*/
QSizeF drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *context, QPointF point = QPointF() );
QSizeF drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *context, const ColumnContext &columnContext = ColumnContext(), double top = 0 );
/**
* Returns the style of the given \a node.
*/
QgsLegendStyle::Style nodeLegendStyle( QgsLayerTreeNode *node );
private:
QgsLayerTreeModel *mLegendModel = nullptr;
QgsLegendSettings mSettings;
@ -308,12 +331,14 @@ class CORE_EXPORT QgsLegendRenderer
QSizeF mLegendSize;
#endif
QSizeF drawTitleInternal( QgsRenderContext *context, QPainter *painter, QPointF point, Qt::AlignmentFlag halignment, double legendWidth );
QSizeF drawAtomInternal( const Atom &atom, QgsRenderContext *context, QPainter *painter, QPointF point );
QgsLegendRenderer::Nucleon drawSymbolItemInternal( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *context, QPainter *painter, QPointF point, double labelXOffset );
QSizeF drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *context, QPainter *painter, QPointF point );
QSizeF drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *context, QPainter *painter, QPointF point );
QSizeF drawTitleInternal( QgsRenderContext *context, QPainter *painter, double top, Qt::AlignmentFlag halignment, double legendWidth );
QSizeF drawGroupInternal( const LegendComponentGroup &group, QgsRenderContext *context, const ColumnContext &columnContext, QPainter *painter, double top );
QgsLegendRenderer::LegendComponent drawSymbolItemInternal( QgsLayerTreeModelLegendNode *symbolItem, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, double top, double maxSiblingSymbolWidth );
QSizeF drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, double top );
QSizeF drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, double top );
QSizeF paintAndDetermineSizeInternal( QgsRenderContext *context, QPainter *painter );
void widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, double legendWidth, double &width, double &offset );
};
#endif // QGSLEGENDRENDERER_H

View File

@ -44,16 +44,14 @@ class CORE_EXPORT QgsLegendSettings
QString title() const { return mTitle; }
/**
* Returns the alignment of the legend title
* \returns Qt::AlignmentFlag for the legend title
* \see setTitleAlignment
* Returns the alignment of the legend title.
* \see setTitleAlignment()
*/
Qt::AlignmentFlag titleAlignment() const { return mTitleAlignment; }
/**
* Sets the alignment of the legend title
* \param alignment Text alignment for drawing the legend title
* \see titleAlignment
* Sets the \a alignment of the legend title.
* \see titleAlignment()
*/
void setTitleAlignment( Qt::AlignmentFlag alignment ) { mTitleAlignment = alignment; }
@ -109,6 +107,26 @@ class CORE_EXPORT QgsLegendSettings
QSizeF symbolSize() const {return mSymbolSize;}
void setSymbolSize( QSizeF s ) {mSymbolSize = s;}
/**
* Sets the \a alignment for placement of legend symbols.
*
* Only Qt::AlignLeft or Qt::AlignRight are supported values.
*
* \see symbolAlignment()
* \since QGIS 3.10.0
*/
void setSymbolAlignment( Qt::AlignmentFlag alignment ) { mSymbolAlignment = alignment; }
/**
* Returns the alignment for placement of legend symbols.
*
* Only Qt::AlignLeft or Qt::AlignRight are supported values.
*
* \see setSymbolAlignment()
* \since QGIS 3.10.0
*/
Qt::AlignmentFlag symbolAlignment() const { return mSymbolAlignment; }
/**
* Returns whether a stroke will be drawn around raster symbol items.
* \see setDrawRasterStroke()
@ -327,6 +345,9 @@ class CORE_EXPORT QgsLegendSettings
//! Font color for layers, overrides font color
QColor mLayerFontColor;
//! Symbol alignment
Qt::AlignmentFlag mSymbolAlignment = Qt::AlignLeft;
};

View File

@ -48,16 +48,22 @@ void QgsLegendStyle::setMargin( double margin )
void QgsLegendStyle::writeXml( const QString &name, QDomElement &elem, QDomDocument &doc ) const
{
if ( elem.isNull() ) return;
if ( elem.isNull() )
return;
QDomElement styleElem = doc.createElement( QStringLiteral( "style" ) );
styleElem.setAttribute( QStringLiteral( "name" ), name );
styleElem.setAttribute( QStringLiteral( "alignment" ), QString::number( mAlignment ) );
if ( !qgsDoubleNear( mMarginMap[Top], 0.0 ) ) styleElem.setAttribute( QStringLiteral( "marginTop" ), QString::number( mMarginMap[Top] ) );
if ( !qgsDoubleNear( mMarginMap[Bottom], 0.0 ) ) styleElem.setAttribute( QStringLiteral( "marginBottom" ), QString::number( mMarginMap[Bottom] ) );
if ( !qgsDoubleNear( mMarginMap[Left], 0.0 ) ) styleElem.setAttribute( QStringLiteral( "marginLeft" ), QString::number( mMarginMap[Left] ) );
if ( !qgsDoubleNear( mMarginMap[Right], 0.0 ) ) styleElem.setAttribute( QStringLiteral( "marginRight" ), QString::number( mMarginMap[Right] ) );
if ( !qgsDoubleNear( mMarginMap[Top], 0.0 ) )
styleElem.setAttribute( QStringLiteral( "marginTop" ), QString::number( mMarginMap[Top] ) );
if ( !qgsDoubleNear( mMarginMap[Bottom], 0.0 ) )
styleElem.setAttribute( QStringLiteral( "marginBottom" ), QString::number( mMarginMap[Bottom] ) );
if ( !qgsDoubleNear( mMarginMap[Left], 0.0 ) )
styleElem.setAttribute( QStringLiteral( "marginLeft" ), QString::number( mMarginMap[Left] ) );
if ( !qgsDoubleNear( mMarginMap[Right], 0.0 ) )
styleElem.setAttribute( QStringLiteral( "marginRight" ), QString::number( mMarginMap[Right] ) );
styleElem.appendChild( QgsFontUtils::toXmlElement( mFont, doc, QStringLiteral( "styleFont" ) ) );
@ -78,6 +84,8 @@ void QgsLegendStyle::readXml( const QDomElement &elem, const QDomDocument &doc )
mMarginMap[Bottom] = elem.attribute( QStringLiteral( "marginBottom" ), QStringLiteral( "0" ) ).toDouble();
mMarginMap[Left] = elem.attribute( QStringLiteral( "marginLeft" ), QStringLiteral( "0" ) ).toDouble();
mMarginMap[Right] = elem.attribute( QStringLiteral( "marginRight" ), QStringLiteral( "0" ) ).toDouble();
mAlignment = static_cast< Qt::Alignment >( elem.attribute( QStringLiteral( "alignment" ), QString::number( Qt::AlignLeft ) ).toInt() );
}
QString QgsLegendStyle::styleName( Style s )

View File

@ -80,6 +80,22 @@ class CORE_EXPORT QgsLegendStyle
//! Sets all margins
void setMargin( double margin );
/**
* Returns the alignment for the legend component.
*
* \see setAlignment()
* \since QGIS 3.10
*/
Qt::Alignment alignment() const { return mAlignment; }
/**
* Sets the alignment for the legend component.
*
* \see alignment()
* \since QGIS 3.10
*/
void setAlignment( Qt::Alignment alignment ) { mAlignment = alignment; }
void writeXml( const QString &name, QDomElement &elem, QDomDocument &doc ) const;
void readXml( const QDomElement &elem, const QDomDocument &doc );
@ -97,6 +113,8 @@ class CORE_EXPORT QgsLegendStyle
QFont mFont;
//! Space around element
QMap<Side, double> mMarginMap;
Qt::Alignment mAlignment = Qt::AlignLeft;
};
#endif

View File

@ -48,6 +48,18 @@ void QgsAlignmentComboBox::setCurrentAlignment( Qt::Alignment alignment )
setCurrentIndex( index );
}
void QgsAlignmentComboBox::customiseAlignmentDisplay( Qt::Alignment alignment, const QString &text, const QIcon &icon )
{
const int index = findData( QVariant( alignment ) );
if ( index >= 0 )
{
if ( !text.isEmpty() )
setItemText( index, text );
if ( !icon.isNull() )
setItemIcon( index, icon );
}
}
void QgsAlignmentComboBox::populate()
{
Qt::Alignment prevAlign = currentAlignment();

View File

@ -61,6 +61,16 @@ class GUI_EXPORT QgsAlignmentComboBox : public QComboBox
*/
void setCurrentAlignment( Qt::Alignment alignment );
/**
* Sets the \a text and \a icon to use for a particular \a alignment option,
* replacing the default text or icon.
*
* If \a text or \a icon is not specified, they will not be changed from the default.
*
* \note This must be called after first filtering the available alignment options via setAvailableAlignments().
*/
void customiseAlignmentDisplay( Qt::Alignment alignment, const QString &text = QString(), const QIcon &icon = QIcon() );
signals:
/**

View File

@ -167,8 +167,6 @@ namespace QgsWms
QgsLegendSettings settings = legendSettings();
QgsLayerTreeModelLegendNode::ItemContext ctx;
ctx.painter = painter.get();
ctx.labelXOffset = 0;
ctx.point = QPointF();
nodeModel.drawSymbol( settings, &ctx, size.height() / dpmm );
painter->end();

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>393</width>
<width>323</width>
<height>995</height>
</rect>
</property>
@ -64,8 +64,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>377</width>
<height>1662</height>
<width>312</width>
<height>1459</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainLayout">
@ -84,6 +84,44 @@
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QLineEdit" name="mTitleLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Wrap text on</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="mWrapCharLineEdit">
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsPropertyOverrideButton" name="mLegendTitleDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="mCheckboxResizeContents">
<property name="text">
<string>Resize to fit contents</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="mMapLabel">
<property name="text">
<string>Map</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="mTitleLabel">
<property name="text">
@ -94,70 +132,16 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Title alignment</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="mMapLabel">
<property name="text">
<string>Map</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="mCheckboxResizeContents">
<property name="text">
<string>Resize to fit contents</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Wrap text on</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="mTitleLineEdit"/>
</item>
<item row="0" column="2">
<widget class="QgsPropertyOverrideButton" name="mLegendTitleDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QComboBox" name="mTitleAlignCombo">
<item>
<property name="text">
<string>Left</string>
</property>
</item>
<item>
<property name="text">
<string>Center</string>
</property>
</item>
<item>
<property name="text">
<string>Right</string>
</property>
</item>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QgsLayoutItemComboBox" name="mMapComboBox"/>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLineEdit" name="mWrapCharLineEdit">
<property name="frame">
<bool>true</bool>
<widget class="QgsAlignmentComboBox" name="mArrangementCombo"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Arrangement</string>
</property>
</widget>
</item>
@ -255,7 +239,7 @@
<string/>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionArrowDown.svg</normaloff>:/images/themes/default/mActionArrowDown.svg</iconset>
</property>
<property name="iconSize">
@ -272,7 +256,7 @@
<string/>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionArrowUp.svg</normaloff>:/images/themes/default/mActionArrowUp.svg</iconset>
</property>
<property name="iconSize">
@ -292,7 +276,7 @@
<string>…</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionAddGroup.svg</normaloff>:/images/themes/default/mActionAddGroup.svg</iconset>
</property>
<property name="iconSize">
@ -309,7 +293,7 @@
<string/>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/symbologyAdd.svg</normaloff>:/images/themes/default/symbologyAdd.svg</iconset>
</property>
<property name="iconSize">
@ -326,7 +310,7 @@
<string/>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/symbologyRemove.svg</normaloff>:/images/themes/default/symbologyRemove.svg</iconset>
</property>
<property name="iconSize">
@ -343,7 +327,7 @@
<string/>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/symbologyEdit.svg</normaloff>:/images/themes/default/symbologyEdit.svg</iconset>
</property>
<property name="iconSize">
@ -366,7 +350,7 @@
<string/>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionSum.svg</normaloff>:/images/themes/default/mActionSum.svg</iconset>
</property>
<property name="iconSize">
@ -386,7 +370,7 @@
<string>Filter Legend by Map Content</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionFilter2.svg</normaloff>:/images/themes/default/mActionFilter2.svg</iconset>
</property>
<property name="iconSize">
@ -406,7 +390,7 @@
<string/>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mIconExpressionFilter.svg</normaloff>:/images/themes/default/mIconExpressionFilter.svg</iconset>
</property>
<property name="iconSize">
@ -451,7 +435,7 @@
<enum>Qt::StrongFocus</enum>
</property>
<property name="title">
<string>Fonts</string>
<string>Fonts and Text Formatting</string>
</property>
<property name="syncGroup" stdset="0">
<string notr="true">composeritem</string>
@ -459,34 +443,32 @@
<property name="collapsed" stdset="0">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QgsFontButton" name="mTitleFontButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0">
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Title font</string>
<string>&lt;b&gt;Group Headings&lt;/b&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QgsFontButton" name="mGroupFontButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_28">
<property name="text">
<string>Group font</string>
<string>Font</string>
</property>
</widget>
</item>
<item>
<item row="11" column="1" colspan="2">
<widget class="QgsAlignmentComboBox" name="mItemAlignCombo"/>
</item>
<item row="6" column="0" colspan="3">
<widget class="QLabel" name="label_23">
<property name="text">
<string>&lt;b&gt;Subgroup Headings&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="7" column="1" colspan="2">
<widget class="QgsFontButton" name="mLayerFontButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -499,7 +481,10 @@
</property>
</widget>
</item>
<item>
<item row="5" column="1" colspan="2">
<widget class="QgsAlignmentComboBox" name="mGroupAlignCombo"/>
</item>
<item row="10" column="1" colspan="2">
<widget class="QgsFontButton" name="mItemFontButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -512,7 +497,41 @@
</property>
</widget>
</item>
<item>
<item row="9" column="0" colspan="3">
<widget class="QLabel" name="label_25">
<property name="text">
<string>&lt;b&gt;Item Labels&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Alignment</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QgsFontButton" name="mTitleFontButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Title font</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Alignment</string>
</property>
</widget>
</item>
<item row="12" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_16">
@ -555,6 +574,67 @@
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Font</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Alignment</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Font</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Font</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label_27">
<property name="text">
<string>&lt;b&gt;Legend Title&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="8" column="1" colspan="2">
<widget class="QgsAlignmentComboBox" name="mSubgroupAlignCombo"/>
</item>
<item row="4" column="1" colspan="2">
<widget class="QgsFontButton" name="mGroupFontButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Group font</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QgsAlignmentComboBox" name="mTitleAlignCombo"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_29">
<property name="text">
<string>Alignment</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -1102,15 +1182,20 @@
<extends>QToolButton</extends>
<header location="global">qgslegendfilterbutton.h</header>
</customwidget>
<customwidget>
<class>QgsAlignmentComboBox</class>
<extends>QComboBox</extends>
<header>qgsalignmentcombobox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>scrollArea</tabstop>
<tabstop>mMainPropertiesColGroupBox</tabstop>
<tabstop>mTitleLineEdit</tabstop>
<tabstop>mLegendTitleDDBtn</tabstop>
<tabstop>mTitleAlignCombo</tabstop>
<tabstop>mMapComboBox</tabstop>
<tabstop>mWrapCharLineEdit</tabstop>
<tabstop>mArrangementCombo</tabstop>
<tabstop>mCheckboxResizeContents</tabstop>
<tabstop>mLegendItemColGroupBox</tabstop>
<tabstop>mCheckBoxAutoUpdate</tabstop>
@ -1128,13 +1213,17 @@
<tabstop>mFilterLegendByAtlasCheckBox</tabstop>
<tabstop>mFontsColGroupBox</tabstop>
<tabstop>mTitleFontButton</tabstop>
<tabstop>mTitleAlignCombo</tabstop>
<tabstop>mGroupFontButton</tabstop>
<tabstop>mGroupAlignCombo</tabstop>
<tabstop>mLayerFontButton</tabstop>
<tabstop>mSubgroupAlignCombo</tabstop>
<tabstop>mItemFontButton</tabstop>
<tabstop>mItemAlignCombo</tabstop>
<tabstop>mFontColorButton</tabstop>
<tabstop>mColumnsColGroupBox</tabstop>
<tabstop>mColumnsDDBtn</tabstop>
<tabstop>mColumnCountSpinBox</tabstop>
<tabstop>mColumnsDDBtn</tabstop>
<tabstop>mEqualColumnWidthCheckBox</tabstop>
<tabstop>mSplitLayerCheckBox</tabstop>
<tabstop>mSymbolsColGroupBox</tabstop>
@ -1156,35 +1245,6 @@
<tabstop>mColumnSpaceSpinBox</tabstop>
<tabstop>mLineSpacingSpinBox</tabstop>
</tabstops>
<resources>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
</resources>
<resources/>
<connections/>
</ui>

View File

@ -124,6 +124,13 @@ class TestQgsLegendRenderer : public QObject
void testBasic();
void testBigMarker();
void testRightAlignText();
void testCenterAlignText();
void testLeftAlignTextRightAlignSymbol();
void testCenterAlignTextRightAlignSymbol();
void testRightAlignTextRightAlignSymbol();
void testMapUnits();
void testTallSymbol();
void testLineSpacing();
@ -321,6 +328,124 @@ void TestQgsLegendRenderer::testBigMarker()
QVERIFY( _verifyImage( testName, mReport ) );
}
void TestQgsLegendRenderer::testCenterAlignText()
{
QgsMarkerSymbol *sym = new QgsMarkerSymbol();
sym->setColor( Qt::red );
sym->setSize( sym->size() * 6 );
QgsCategorizedSymbolRenderer *catRenderer = dynamic_cast<QgsCategorizedSymbolRenderer *>( mVL3->renderer() );
QVERIFY( catRenderer );
catRenderer->updateCategorySymbol( 0, sym );
QgsLayerTreeModel legendModel( mRoot );
QgsLegendSettings settings;
settings.rstyle( QgsLegendStyle::Group ).setAlignment( Qt::AlignHCenter );
settings.rstyle( QgsLegendStyle::Subgroup ).setAlignment( Qt::AlignHCenter );
settings.rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( Qt::AlignHCenter );
_setStandardTestFont( settings );
_renderLegend( QStringLiteral( "legend_center_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_center_align_text" ), mReport ) );
settings.setColumnCount( 2 );
_renderLegend( QStringLiteral( "legend_two_cols_center_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_two_cols_center_align_text" ), mReport ) );
}
void TestQgsLegendRenderer::testLeftAlignTextRightAlignSymbol()
{
QgsMarkerSymbol *sym = new QgsMarkerSymbol();
sym->setColor( Qt::red );
sym->setSize( sym->size() * 6 );
QgsCategorizedSymbolRenderer *catRenderer = dynamic_cast<QgsCategorizedSymbolRenderer *>( mVL3->renderer() );
QVERIFY( catRenderer );
catRenderer->updateCategorySymbol( 0, sym );
QgsLayerTreeModel legendModel( mRoot );
QgsLegendSettings settings;
settings.rstyle( QgsLegendStyle::Group ).setAlignment( Qt::AlignLeft );
settings.rstyle( QgsLegendStyle::Subgroup ).setAlignment( Qt::AlignLeft );
settings.rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( Qt::AlignLeft );
settings.setSymbolAlignment( Qt::AlignRight );
_setStandardTestFont( settings );
_renderLegend( QStringLiteral( "legend_right_symbol_left_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_right_symbol_left_align_text" ), mReport ) );
settings.setColumnCount( 2 );
_renderLegend( QStringLiteral( "legend_two_cols_right_align_symbol_left_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_two_cols_right_align_symbol_left_align_text" ), mReport ) );
}
void TestQgsLegendRenderer::testCenterAlignTextRightAlignSymbol()
{
QgsMarkerSymbol *sym = new QgsMarkerSymbol();
sym->setColor( Qt::red );
sym->setSize( sym->size() * 6 );
QgsCategorizedSymbolRenderer *catRenderer = dynamic_cast<QgsCategorizedSymbolRenderer *>( mVL3->renderer() );
QVERIFY( catRenderer );
catRenderer->updateCategorySymbol( 0, sym );
QgsLayerTreeModel legendModel( mRoot );
QgsLegendSettings settings;
settings.rstyle( QgsLegendStyle::Group ).setAlignment( Qt::AlignHCenter );
settings.rstyle( QgsLegendStyle::Subgroup ).setAlignment( Qt::AlignHCenter );
settings.rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( Qt::AlignHCenter );
settings.setSymbolAlignment( Qt::AlignRight );
_setStandardTestFont( settings );
_renderLegend( QStringLiteral( "legend_right_symbol_center_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_right_symbol_center_align_text" ), mReport ) );
settings.setColumnCount( 2 );
_renderLegend( QStringLiteral( "legend_two_cols_right_align_symbol_center_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_two_cols_right_align_symbol_center_align_text" ), mReport ) );
}
void TestQgsLegendRenderer::testRightAlignTextRightAlignSymbol()
{
QgsMarkerSymbol *sym = new QgsMarkerSymbol();
sym->setColor( Qt::red );
sym->setSize( sym->size() * 6 );
QgsCategorizedSymbolRenderer *catRenderer = dynamic_cast<QgsCategorizedSymbolRenderer *>( mVL3->renderer() );
QVERIFY( catRenderer );
catRenderer->updateCategorySymbol( 0, sym );
QgsLayerTreeModel legendModel( mRoot );
QgsLegendSettings settings;
settings.rstyle( QgsLegendStyle::Group ).setAlignment( Qt::AlignRight );
settings.rstyle( QgsLegendStyle::Subgroup ).setAlignment( Qt::AlignRight );
settings.rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( Qt::AlignRight );
settings.setSymbolAlignment( Qt::AlignRight );
_setStandardTestFont( settings );
_renderLegend( QStringLiteral( "legend_right_symbol_right_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_right_symbol_right_align_text" ), mReport ) );
settings.setColumnCount( 2 );
_renderLegend( QStringLiteral( "legend_two_cols_right_align_symbol_right_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_two_cols_right_align_symbol_right_align_text" ), mReport ) );
}
void TestQgsLegendRenderer::testRightAlignText()
{
QgsMarkerSymbol *sym = new QgsMarkerSymbol();
sym->setColor( Qt::red );
sym->setSize( sym->size() * 6 );
QgsCategorizedSymbolRenderer *catRenderer = dynamic_cast<QgsCategorizedSymbolRenderer *>( mVL3->renderer() );
QVERIFY( catRenderer );
catRenderer->updateCategorySymbol( 0, sym );
QgsLayerTreeModel legendModel( mRoot );
QgsLegendSettings settings;
settings.rstyle( QgsLegendStyle::Group ).setAlignment( Qt::AlignRight );
settings.rstyle( QgsLegendStyle::Subgroup ).setAlignment( Qt::AlignRight );
settings.rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( Qt::AlignRight );
_setStandardTestFont( settings );
_renderLegend( QStringLiteral( "legend_right_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_right_align_text" ), mReport ) );
settings.setColumnCount( 2 );
_renderLegend( QStringLiteral( "legend_two_cols_right_align_text" ), &legendModel, settings );
QVERIFY( _verifyImage( QStringLiteral( "legend_two_cols_right_align_text" ), mReport ) );
}
void TestQgsLegendRenderer::testMapUnits()
{
QString testName = QStringLiteral( "legend_mapunits" );

View File

@ -10,7 +10,7 @@ __author__ = '(C) 2017 by Nyall Dawson'
__date__ = '24/10/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
from qgis.PyQt.QtCore import QRectF
from qgis.PyQt.QtCore import QRectF, QDir
from qgis.PyQt.QtGui import QColor
from qgis.core import (QgsPrintLayout,
@ -52,8 +52,17 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
def setUpClass(cls):
cls.item_class = QgsLayoutItemLegend
def setUp(self):
self.report = "<h1>Python QgsLayoutItemLegend Tests</h1>\n"
def tearDown(self):
report_file_path = "%s/qgistest.html" % QDir.tempPath()
with open(report_file_path, 'a') as report_file:
report_file.write(self.report)
def testInitialSizeSymbolMapUnits(self):
"""Test initial size of legend with a symbol size in map units"""
QgsProject.instance().removeAllMapLayers()
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
@ -89,6 +98,7 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
'composer_legend_mapunits', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)
# resize with non-top-left reference point
@ -147,6 +157,7 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
'composer_legend_size_content', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])
@ -191,6 +202,7 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
'composer_legend_noresize', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])
@ -235,6 +247,7 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
'composer_legend_noresize_crop', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])
@ -360,6 +373,7 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase):
'composer_legend_expressions', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB