diff --git a/images/images.qrc b/images/images.qrc
index 997e1fc88f2..43595ac9aea 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -765,6 +765,8 @@
themes/default/mIconAlignJustify.svg
themes/default/mIconAlignLeft.svg
themes/default/mIconAlignRight.svg
+ themes/default/mIconArrangeSymbolsLeft.svg
+ themes/default/mIconArrangeSymbolsRight.svg
qgis_tips/symbol_levels.png
diff --git a/images/themes/default/mIconArrangeSymbolsLeft.svg b/images/themes/default/mIconArrangeSymbolsLeft.svg
new file mode 100644
index 00000000000..2d5799c46f7
--- /dev/null
+++ b/images/themes/default/mIconArrangeSymbolsLeft.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/themes/default/mIconArrangeSymbolsRight.svg b/images/themes/default/mIconArrangeSymbolsRight.svg
new file mode 100644
index 00000000000..400c3d1041f
--- /dev/null
+++ b/images/themes/default/mIconArrangeSymbolsRight.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in b/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in
index dabaa89f18a..4f59c513d94 100644
--- a/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in
+++ b/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in
@@ -76,10 +76,23 @@ Default implementation does nothing. *
struct ItemContext
{
+ ItemContext();
+
QgsRenderContext *context;
QPainter *painter;
- QPointF point;
- double labelXOffset;
+
+ QPointF point;
+
+ double labelXOffset;
+
+ double top;
+
+ double columnLeft;
+
+ double columnRight;
+
+ double maxSiblingSymbolWidth;
+
};
struct ItemMetrics
diff --git a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in
index 89abc9e25dc..30d7ed9686b 100644
--- a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in
+++ b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in
@@ -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;
diff --git a/python/core/auto_generated/qgslegendsettings.sip.in b/python/core/auto_generated/qgslegendsettings.sip.in
index 7247813d597..3997c0efc61 100644
--- a/python/core/auto_generated/qgslegendsettings.sip.in
+++ b/python/core/auto_generated/qgslegendsettings.sip.in
@@ -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.
diff --git a/python/core/auto_generated/qgslegendstyle.sip.in b/python/core/auto_generated/qgslegendstyle.sip.in
index 0375f260f59..3b950262b3c 100644
--- a/python/core/auto_generated/qgslegendstyle.sip.in
+++ b/python/core/auto_generated/qgslegendstyle.sip.in
@@ -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;
diff --git a/python/gui/auto_generated/qgsalignmentcombobox.sip.in b/python/gui/auto_generated/qgsalignmentcombobox.sip.in
index 1006b083c55..429a5685cca 100644
--- a/python/gui/auto_generated/qgsalignmentcombobox.sip.in
+++ b/python/gui/auto_generated/qgsalignmentcombobox.sip.in
@@ -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();
diff --git a/src/app/layout/qgslayoutapputils.cpp b/src/app/layout/qgslayoutapputils.cpp
index 19e761221fc..db734a19b6e 100644
--- a/src/app/layout/qgslayoutapputils.cpp
+++ b/src/app/layout/qgslayoutapputils.cpp
@@ -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();
} );
diff --git a/src/app/layout/qgslayoutlegendwidget.cpp b/src/app/layout/qgslayoutlegendwidget.cpp
index e98907aff58..b9ba7269886 100644
--- a/src/app/layout/qgslayoutlegendwidget.cpp
+++ b/src/app/layout/qgslayoutlegendwidget.cpp
@@ -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( &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;
diff --git a/src/app/layout/qgslayoutlegendwidget.h b/src/app/layout/qgslayoutlegendwidget.h
index 460959cdbe2..125eb906fdf 100644
--- a/src/app/layout/qgslayoutlegendwidget.h
+++ b/src/app/layout/qgslayoutlegendwidget.h
@@ -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 );
diff --git a/src/core/layertree/qgslayertreemodellegendnode.cpp b/src/core/layertree/qgslayertreemodellegendnode.cpp
index 790ec9fb1da..d19d215c846 100644
--- a/src/core/layertree/qgslayertreemodellegendnode.cpp
+++ b/src/core/layertree/qgslayertreemodellegendnode.cpp
@@ -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() ),
- mImage, QRectF( 0, 0, mImage.width(), mImage.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,
- settings.symbolSize().width(), settings.symbolSize().height() ) );
+ 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() ),
- mImage,
- QRectF( QPointF( 0, 0 ), mImage.size() ) );
+ 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() );
}
diff --git a/src/core/layertree/qgslayertreemodellegendnode.h b/src/core/layertree/qgslayertreemodellegendnode.h
index 9a049d293d7..5008893273b 100644
--- a/src/core/layertree/qgslayertreemodellegendnode.h
+++ b/src/core/layertree/qgslayertreemodellegendnode.h
@@ -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
diff --git a/src/core/layout/qgslayoutitemlegend.cpp b/src/core/layout/qgslayoutitemlegend.cpp
index f516f5eb272..8769b6a0114 100644
--- a/src/core/layout/qgslayoutitemlegend.cpp
+++ b/src/core/layout/qgslayoutitemlegend.cpp
@@ -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();
diff --git a/src/core/layout/qgslayoutitemlegend.h b/src/core/layout/qgslayoutitemlegend.h
index c6a74fb2a55..8c42ff31fbf 100644
--- a/src/core/layout/qgslayoutitemlegend.h
+++ b/src/core/layout/qgslayoutitemlegend.h
@@ -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()
diff --git a/src/core/qgslegendrenderer.cpp b/src/core/qgslegendrenderer.cpp
index 5d91b4a576f..9ea4c717874 100644
--- a/src/core/qgslegendrenderer.cpp
+++ b/src/core/qgslegendrenderer.cpp
@@ -129,19 +129,38 @@ QSizeF QgsLegendRenderer::paintAndDetermineSizeInternal( QgsRenderContext *conte
if ( !rootGroup )
return size;
- QList atomList = createAtomList( rootGroup, mSettings.splitLayer() );
+ QList atomList = createComponentGroupList( rootGroup, mSettings.splitLayer() );
setColumns( atomList );
- qreal maxColumnWidth = 0;
- if ( mSettings.equalColumnWidth() )
+ // 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 auto constAtomList = atomList;
- for ( const Atom &atom : constAtomList )
+ const QSizeF actualSize = drawGroup( atom, context, ColumnContext() );
+ if ( mSettings.equalColumnWidth() )
{
- maxColumnWidth = std::max( atom.size.width(), maxColumnWidth );
+ maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
+ }
+ else
+ {
+ 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::createAtomList( QgsLayerTreeGroup *parentGroup, bool splitLayer )
+void QgsLegendRenderer::widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, const double legendWidth, double &textBoxWidth, double &textBoxLeft )
{
- QList 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::createComponentGroupList( QgsLayerTreeGroup *parentGroup, bool splitLayer )
+{
+ QList atoms;
if ( !parentGroup ) return atoms;
@@ -244,29 +267,29 @@ QList QgsLegendRenderer::createAtomList( QgsLayerTreeGr
QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
// Group subitems
- QList groupAtoms = createAtomList( nodeGroup, splitLayer );
+ QList 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::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::createAtomList( QgsLayerTreeGr
if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
continue;
- QList layerAtoms;
+ QList 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::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::createAtomList( QgsLayerTreeGr
}
-void QgsLegendRenderer::setColumns( QList &atomList )
+void QgsLegendRenderer::setColumns( QList &atomList )
{
if ( mSettings.columnCount() == 0 ) return;
@@ -352,9 +375,9 @@ void QgsLegendRenderer::setColumns( QList &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 &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 &atomList )
QMap 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( atom.nucleons.at( j ).item ) )
+ if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast( 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( atom.nucleons.at( j ).item ) )
+ if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast( 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( 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( 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( 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( 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 )
diff --git a/src/core/qgslegendrenderer.h b/src/core/qgslegendrenderer.h
index 6b3934f8b42..bb6d81ab536 100644
--- a/src/core/qgslegendrenderer.h
+++ b/src/core/qgslegendrenderer.h
@@ -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 nucleons;
+ //! List of child components belonging to this group.
+ QList 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 createAtomList( QgsLayerTreeGroup *parentGroup, bool splitLayer );
+ QList 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 &atomList );
+ void setColumns( QList &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
diff --git a/src/core/qgslegendsettings.h b/src/core/qgslegendsettings.h
index e39f2a326cd..8b578a85df4 100644
--- a/src/core/qgslegendsettings.h
+++ b/src/core/qgslegendsettings.h
@@ -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;
};
diff --git a/src/core/qgslegendstyle.cpp b/src/core/qgslegendstyle.cpp
index b844d4d4c0a..3d4c5d94bb3 100644
--- a/src/core/qgslegendstyle.cpp
+++ b/src/core/qgslegendstyle.cpp
@@ -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 )
diff --git a/src/core/qgslegendstyle.h b/src/core/qgslegendstyle.h
index 853f8265043..7758c17ae45 100644
--- a/src/core/qgslegendstyle.h
+++ b/src/core/qgslegendstyle.h
@@ -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 mMarginMap;
+
+ Qt::Alignment mAlignment = Qt::AlignLeft;
};
#endif
diff --git a/src/gui/qgsalignmentcombobox.cpp b/src/gui/qgsalignmentcombobox.cpp
index 17dde47c50f..35a582ba76d 100644
--- a/src/gui/qgsalignmentcombobox.cpp
+++ b/src/gui/qgsalignmentcombobox.cpp
@@ -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();
diff --git a/src/gui/qgsalignmentcombobox.h b/src/gui/qgsalignmentcombobox.h
index 91d0638ebcc..d66e63bf7c7 100644
--- a/src/gui/qgsalignmentcombobox.h
+++ b/src/gui/qgsalignmentcombobox.h
@@ -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:
/**
diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp
index 76633b2783d..bf7d7ca6369 100644
--- a/src/server/services/wms/qgswmsrenderer.cpp
+++ b/src/server/services/wms/qgswmsrenderer.cpp
@@ -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();
diff --git a/src/ui/layout/qgslayoutlegendwidgetbase.ui b/src/ui/layout/qgslayoutlegendwidgetbase.ui
index 1b0bbbf8197..f9cec60afec 100644
--- a/src/ui/layout/qgslayoutlegendwidgetbase.ui
+++ b/src/ui/layout/qgslayoutlegendwidgetbase.ui
@@ -6,7 +6,7 @@
0
0
- 393
+ 323
995
@@ -64,8 +64,8 @@
0
0
- 377
- 1662
+ 312
+ 1459
@@ -84,6 +84,44 @@
false
+ -
+
+
+ -
+
+
+ Wrap text on
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ …
+
+
+
+ -
+
+
+ Resize to fit contents
+
+
+
+ -
+
+
+ Map
+
+
+
-
@@ -94,70 +132,16 @@
- -
-
-
- Title alignment
-
-
-
- -
-
-
- Map
-
-
-
- -
-
-
- Resize to fit contents
-
-
-
- -
-
-
- Wrap text on
-
-
-
- -
-
-
- -
-
-
- …
-
-
-
-
-
-
-
-
- Left
-
-
- -
-
- Center
-
-
- -
-
- Right
-
-
-
-
- -
-
-
-
- true
+
+
+ -
+
+
+ Arrangement
@@ -255,7 +239,7 @@
-
+
:/images/themes/default/mActionArrowDown.svg:/images/themes/default/mActionArrowDown.svg
@@ -272,7 +256,7 @@
-
+
:/images/themes/default/mActionArrowUp.svg:/images/themes/default/mActionArrowUp.svg
@@ -292,7 +276,7 @@
…
-
+
:/images/themes/default/mActionAddGroup.svg:/images/themes/default/mActionAddGroup.svg
@@ -309,7 +293,7 @@
-
+
:/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg
@@ -326,7 +310,7 @@
-
+
:/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg
@@ -343,7 +327,7 @@
-
+
:/images/themes/default/symbologyEdit.svg:/images/themes/default/symbologyEdit.svg
@@ -366,7 +350,7 @@
-
+
:/images/themes/default/mActionSum.svg:/images/themes/default/mActionSum.svg
@@ -386,7 +370,7 @@
Filter Legend by Map Content
-
+
:/images/themes/default/mActionFilter2.svg:/images/themes/default/mActionFilter2.svg
@@ -406,7 +390,7 @@
-
+
:/images/themes/default/mIconExpressionFilter.svg:/images/themes/default/mIconExpressionFilter.svg
@@ -451,7 +435,7 @@
Qt::StrongFocus
- Fonts
+ Fonts and Text Formatting
composeritem
@@ -459,34 +443,32 @@
true
-
- -
-
-
-
- 0
- 0
-
-
+
+
-
+
- Title font
+ <b>Group Headings</b>
- -
-
-
-
- 0
- 0
-
-
+
-
+
- Group font
+ Font
- -
+
-
+
+
+ -
+
+
+ <b>Subgroup Headings</b>
+
+
+
+ -
@@ -499,7 +481,10 @@
- -
+
-
+
+
+ -
@@ -512,7 +497,41 @@
- -
+
-
+
+
+ <b>Item Labels</b>
+
+
+
+ -
+
+
+ Alignment
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Title font
+
+
+
+ -
+
+
+ Alignment
+
+
+
+ -
-
@@ -555,6 +574,67 @@
+ -
+
+
+ Font
+
+
+
+ -
+
+
+ Alignment
+
+
+
+ -
+
+
+ Font
+
+
+
+ -
+
+
+ Font
+
+
+
+ -
+
+
+ <b>Legend Title</b>
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Group font
+
+
+
+ -
+
+
+ -
+
+
+ Alignment
+
+
+
@@ -1102,15 +1182,20 @@
QToolButton
+
+ QgsAlignmentComboBox
+ QComboBox
+
+
scrollArea
mMainPropertiesColGroupBox
mTitleLineEdit
mLegendTitleDDBtn
- mTitleAlignCombo
mMapComboBox
mWrapCharLineEdit
+ mArrangementCombo
mCheckboxResizeContents
mLegendItemColGroupBox
mCheckBoxAutoUpdate
@@ -1128,13 +1213,17 @@
mFilterLegendByAtlasCheckBox
mFontsColGroupBox
mTitleFontButton
+ mTitleAlignCombo
mGroupFontButton
+ mGroupAlignCombo
mLayerFontButton
+ mSubgroupAlignCombo
mItemFontButton
+ mItemAlignCombo
mFontColorButton
mColumnsColGroupBox
- mColumnsDDBtn
mColumnCountSpinBox
+ mColumnsDDBtn
mEqualColumnWidthCheckBox
mSplitLayerCheckBox
mSymbolsColGroupBox
@@ -1156,35 +1245,6 @@
mColumnSpaceSpinBox
mLineSpacingSpinBox
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/tests/src/core/testqgslegendrenderer.cpp b/tests/src/core/testqgslegendrenderer.cpp
index e0f8d31ccc5..e8c6df29fd7 100644
--- a/tests/src/core/testqgslegendrenderer.cpp
+++ b/tests/src/core/testqgslegendrenderer.cpp
@@ -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( 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( 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( 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( 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( 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" );
diff --git a/tests/src/python/test_qgslayoutlegend.py b/tests/src/python/test_qgslayoutlegend.py
index 47f77f2e293..21bd4441a86 100644
--- a/tests/src/python/test_qgslayoutlegend.py
+++ b/tests/src/python/test_qgslayoutlegend.py
@@ -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 = "Python QgsLayoutItemLegend Tests
\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()])
diff --git a/tests/testdata/control_images/legend/expected_legend_center_align_text/expected_legend_center_align_text.png b/tests/testdata/control_images/legend/expected_legend_center_align_text/expected_legend_center_align_text.png
new file mode 100644
index 00000000000..628426cdcaa
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_center_align_text/expected_legend_center_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_right_align_text/expected_legend_right_align_text.png b/tests/testdata/control_images/legend/expected_legend_right_align_text/expected_legend_right_align_text.png
new file mode 100644
index 00000000000..0f8f617dc22
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_right_align_text/expected_legend_right_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_right_symbol_center_align_text/expected_legend_right_symbol_center_align_text.png b/tests/testdata/control_images/legend/expected_legend_right_symbol_center_align_text/expected_legend_right_symbol_center_align_text.png
new file mode 100644
index 00000000000..fdec437db75
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_right_symbol_center_align_text/expected_legend_right_symbol_center_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_right_symbol_left_align_text/expected_legend_right_symbol_left_align_text.png b/tests/testdata/control_images/legend/expected_legend_right_symbol_left_align_text/expected_legend_right_symbol_left_align_text.png
new file mode 100644
index 00000000000..79104a369bf
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_right_symbol_left_align_text/expected_legend_right_symbol_left_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_right_symbol_right_align_text/expected_legend_right_symbol_right_align_text.png b/tests/testdata/control_images/legend/expected_legend_right_symbol_right_align_text/expected_legend_right_symbol_right_align_text.png
new file mode 100644
index 00000000000..14177fb2a94
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_right_symbol_right_align_text/expected_legend_right_symbol_right_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_two_cols_center_align_text/expected_legend_two_cols_center_align_text.png b/tests/testdata/control_images/legend/expected_legend_two_cols_center_align_text/expected_legend_two_cols_center_align_text.png
new file mode 100644
index 00000000000..f5dec9d3543
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_two_cols_center_align_text/expected_legend_two_cols_center_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_center_align_text/expected_legend_two_cols_right_align_symbol_center_align_text.png b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_center_align_text/expected_legend_two_cols_right_align_symbol_center_align_text.png
new file mode 100644
index 00000000000..be79fce0db7
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_center_align_text/expected_legend_two_cols_right_align_symbol_center_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_left_align_text/expected_legend_two_cols_right_align_symbol_left_align_text.png b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_left_align_text/expected_legend_two_cols_right_align_symbol_left_align_text.png
new file mode 100644
index 00000000000..daeb63e6797
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_left_align_text/expected_legend_two_cols_right_align_symbol_left_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_right_align_text/expected_legend_two_cols_right_align_symbol_right_align_text.png b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_right_align_text/expected_legend_two_cols_right_align_symbol_right_align_text.png
new file mode 100644
index 00000000000..c0a4df504c7
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_symbol_right_align_text/expected_legend_two_cols_right_align_symbol_right_align_text.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_text/expected_legend_two_cols_right_align_text.png b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_text/expected_legend_two_cols_right_align_text.png
new file mode 100644
index 00000000000..43af3251356
Binary files /dev/null and b/tests/testdata/control_images/legend/expected_legend_two_cols_right_align_text/expected_legend_two_cols_right_align_text.png differ