From 6aeedfe20bfabbbe89b1e9ecb64df018b831b6f2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jun 2019 09:00:21 +1000 Subject: [PATCH] [FEATURE][layouts] Expose choice of arrangement of legends (i.e. symbols to the left OR symbols to the right of legend text), and alignment for group/subgroup/item text Allows creation of right-to-left locale friendly legends. Additionally, we default to this right-to-left style alignment when creating new legends under a RTL based locale. --- images/images.qrc | 2 + .../default/mIconArrangeSymbolsLeft.svg | 1 + .../default/mIconArrangeSymbolsRight.svg | 1 + .../qgslayertreemodellegendnode.sip.in | 17 +- .../layout/qgslayoutitemlegend.sip.in | 22 ++ .../auto_generated/qgslegendsettings.sip.in | 30 +- .../core/auto_generated/qgslegendstyle.sip.in | 18 + .../qgsalignmentcombobox.sip.in | 12 + src/app/layout/qgslayoutapputils.cpp | 10 + src/app/layout/qgslayoutlegendwidget.cpp | 76 +++- src/app/layout/qgslayoutlegendwidget.h | 7 +- .../layertree/qgslayertreemodellegendnode.cpp | 142 +++++++- .../layertree/qgslayertreemodellegendnode.h | 50 ++- src/core/layout/qgslayoutitemlegend.cpp | 19 +- src/core/layout/qgslayoutitemlegend.h | 20 + src/core/qgslegendrenderer.cpp | 341 ++++++++++-------- src/core/qgslegendrenderer.h | 103 ++++-- src/core/qgslegendsettings.h | 33 +- src/core/qgslegendstyle.cpp | 18 +- src/core/qgslegendstyle.h | 18 + src/gui/qgsalignmentcombobox.cpp | 12 + src/gui/qgsalignmentcombobox.h | 10 + src/server/services/wms/qgswmsrenderer.cpp | 2 - src/ui/layout/qgslayoutlegendwidgetbase.ui | 314 +++++++++------- tests/src/core/testqgslegendrenderer.cpp | 125 +++++++ tests/src/python/test_qgslayoutlegend.py | 16 +- .../expected_legend_center_align_text.png | Bin 0 -> 16498 bytes .../expected_legend_right_align_text.png | Bin 0 -> 15273 bytes ..._legend_right_symbol_center_align_text.png | Bin 0 -> 16433 bytes ...ed_legend_right_symbol_left_align_text.png | Bin 0 -> 15257 bytes ...d_legend_right_symbol_right_align_text.png | Bin 0 -> 15128 bytes ...cted_legend_two_cols_center_align_text.png | Bin 0 -> 16797 bytes ...s_right_align_symbol_center_align_text.png | Bin 0 -> 16838 bytes ...ols_right_align_symbol_left_align_text.png | Bin 0 -> 15526 bytes ...ls_right_align_symbol_right_align_text.png | Bin 0 -> 15029 bytes ...ected_legend_two_cols_right_align_text.png | Bin 0 -> 15009 bytes 36 files changed, 1051 insertions(+), 368 deletions(-) create mode 100644 images/themes/default/mIconArrangeSymbolsLeft.svg create mode 100644 images/themes/default/mIconArrangeSymbolsRight.svg create mode 100644 tests/testdata/control_images/legend/expected_legend_center_align_text/expected_legend_center_align_text.png create mode 100644 tests/testdata/control_images/legend/expected_legend_right_align_text/expected_legend_right_align_text.png create mode 100644 tests/testdata/control_images/legend/expected_legend_right_symbol_center_align_text/expected_legend_right_symbol_center_align_text.png create mode 100644 tests/testdata/control_images/legend/expected_legend_right_symbol_left_align_text/expected_legend_right_symbol_left_align_text.png create mode 100644 tests/testdata/control_images/legend/expected_legend_right_symbol_right_align_text/expected_legend_right_symbol_right_align_text.png create mode 100644 tests/testdata/control_images/legend/expected_legend_two_cols_center_align_text/expected_legend_two_cols_center_align_text.png create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 tests/testdata/control_images/legend/expected_legend_two_cols_right_align_text/expected_legend_two_cols_right_align_text.png 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
qgslegendfilterbutton.h
+ + QgsAlignmentComboBox + QComboBox +
qgsalignmentcombobox.h
+
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 0000000000000000000000000000000000000000..628426cdcaa92349a8b0959726eb4602154bcefa GIT binary patch literal 16498 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

rd!V&M?sfFuT@B3n7V@m?DgjP2IpeNq|=qFhz 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..79104a369bf035777c3e57b88f9b10bd4cd1c72c GIT binary patch literal 15257 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 z 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 rh*h;pRdECuE;$(cxYAHIOu-?Zp`;q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f5dec9d35436a98fc76088c73e40f82096c23615 GIT binary patch literal 16797 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 Z&su#g(o{nN|K0*gRr##aTSbHW{||!MFY5pR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..be79fce0db7cf1c251bd388cfedbf11c1df60716 GIT binary patch literal 16838 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 VCuE`*@PiphN7Z%IGF2>Z{5O-Tt>FLw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..daeb63e67975ca3c788dea262ade2c023e2db35d GIT binary patch literal 15526 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c0a4df504c79ba4fc0143f6513af7d3467b4c35d GIT binary patch literal 15029 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..43af32513562bd44459a2de853eb9c36f181887a GIT binary patch literal 15009 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 PbBdCaQj{zZH}w8r7yUN4 literal 0 HcmV?d00001